242 lines
13 KiB
C#
242 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
|
||
namespace CapyLauncher
|
||
{
|
||
public partial class BattlePage : UserControl
|
||
{
|
||
static readonly string[] Types = { "DEFAULT", "MOC", "PF", "AS", "SU", "AA" };
|
||
static readonly string[] TypeNames = { "Normal", "Memory of Chaos", "Pure Fiction", "Apocalyptic Shadow", "Simulated Universe", "Anomaly Arbitration" };
|
||
|
||
Catalog catalog;
|
||
BuildFile file;
|
||
Action changed;
|
||
List<PickItem> monsters, stages, buffs, subs;
|
||
|
||
public BattlePage()
|
||
{
|
||
InitializeComponent();
|
||
}
|
||
|
||
public void Init(Catalog catalog, BuildFile file, Action changed, string battleSource)
|
||
{
|
||
this.catalog = catalog;
|
||
this.file = file;
|
||
this.changed = changed;
|
||
monsters = catalog.Monsters.Select(m => new PickItem { Id = m.Id, Title = m.Name, Sub = m.Extra + " #" + m.Id, Accent = RankBrush(m.Extra), Tag = m }).ToList();
|
||
stages = catalog.Stages.Select(s => new PickItem { Id = s.Id, Title = s.Name, Sub = s.Extra + " #" + s.Id, Accent = Ui.Brush("Blue"), Tag = s }).ToList();
|
||
buffs = catalog.Buffs.Select(b => new PickItem { Id = b.Id, Title = b.Name, Sub = "#" + b.Id, Accent = Ui.Brush("Purple"), Tag = b }).ToList();
|
||
subs = catalog.SubAffixes.Select(a => new PickItem { Id = a.Id, Title = a.Property, Sub = a.Format(a.Base) + " per roll", Accent = Ui.Brush("Ok"), Tag = a, IconPath = Icons.Property(a.Prop) }).ToList();
|
||
SourceHint.Text = battleSource == "stage"
|
||
? "Your config has battle_source set to \"stage\", so fights use the game's own enemies and this setup is ignored."
|
||
: battleSource == "srtools"
|
||
? "Every fight uses this setup (battle_source is \"srtools\"), as long as it has a stage and at least one wave."
|
||
: "Overworld fights use this setup (battle_source is \"auto\"; calyxes keep their own enemies), as long as it has a stage and at least one wave.";
|
||
Draw(true);
|
||
}
|
||
|
||
public void Reload() => Draw(false);
|
||
|
||
static System.Windows.Media.Brush RankBrush(string rank) =>
|
||
Ui.Brush(rank == "Boss" ? "Fail" : rank == "Mini-boss" ? "Warn" : rank == "Elite" ? "Purple" : "Faint");
|
||
|
||
Dictionary<string, object> Battle => file.Battle;
|
||
|
||
List<List<object>> Waves()
|
||
{
|
||
var waves = BuildFile.List(Battle, "monsters");
|
||
for (int i = 0; i < waves.Count; i++)
|
||
{
|
||
if (!(waves[i] is List<object>))
|
||
waves[i] = waves[i] is System.Collections.IEnumerable items ? items.Cast<object>().ToList() : new List<object>();
|
||
}
|
||
return waves.Cast<List<object>>().ToList();
|
||
}
|
||
|
||
void Edit(Action action, bool redraw = true)
|
||
{
|
||
action();
|
||
file.Touch();
|
||
changed();
|
||
if (redraw)
|
||
Draw(false);
|
||
}
|
||
|
||
void Draw(bool fresh)
|
||
{
|
||
double offset = Scroll.VerticalOffset;
|
||
Stack.Children.Clear();
|
||
Stack.Children.Add(SetupSection());
|
||
Stack.Children.Add(WavesSection());
|
||
Stack.Children.Add(BlessingSection());
|
||
Stack.Children.Add(StatsSection());
|
||
Stack.Children.Add(LineupSection());
|
||
if (fresh)
|
||
{
|
||
Scroll.ScrollToTop();
|
||
Ui.FadeIn(Stack);
|
||
}
|
||
else
|
||
{
|
||
Scroll.UpdateLayout();
|
||
Scroll.ScrollToVerticalOffset(offset);
|
||
}
|
||
}
|
||
|
||
UIElement SetupSection()
|
||
{
|
||
var body = new StackPanel();
|
||
string type = BuildFile.Str(Battle, "battle_type", "DEFAULT");
|
||
body.Children.Add(Ui.Row((Ui.Text("Mode", "Dim", 12.5), Ui.Px(130)),
|
||
(Ui.Segments(TypeNames, Math.Max(0, Array.IndexOf(Types, type)), v => Edit(() => Battle["battle_type"] = Types[v])), Ui.Star())));
|
||
|
||
int stageId = BuildFile.Int(Battle, "stage_id");
|
||
var picker = new Picker("Pick a stage") { Items = stages };
|
||
var known = stages.FirstOrDefault(s => s.Id == stageId);
|
||
if (stageId != 0)
|
||
picker.Show(known ?? new PickItem { Title = "Stage " + stageId, Sub = "#" + stageId });
|
||
picker.Picked += item => Edit(() => Battle["stage_id"] = item.Id, false);
|
||
body.Children.Add(Ui.Row((Ui.Text("Stage", "Dim", 12.5), Ui.Px(130)), (picker, Ui.Star())));
|
||
|
||
body.Children.Add(Ui.Row((Ui.Text("Cycle limit", "Dim", 12.5), Ui.Px(130)),
|
||
(Ui.Stepper(0, 99, BuildFile.Int(Battle, "cycle_count"), v => Edit(() => Battle["cycle_count"] = v, false)), Ui.Auto),
|
||
(Ui.Text(" 0 keeps the stage's own limit", "Faint", 12), Ui.Star())));
|
||
if (type == "SU")
|
||
{
|
||
body.Children.Add(Ui.Row((Ui.Text("Path resonance", "Dim", 12.5), Ui.Px(130)),
|
||
(Ui.Stepper(0, 999999, BuildFile.Int(Battle, "path_resonance_id"), v => Edit(() => Battle["path_resonance_id"] = v, false), 140), Ui.Auto)));
|
||
}
|
||
return Ui.Section("Setup", body, "");
|
||
}
|
||
|
||
UIElement WavesSection()
|
||
{
|
||
var body = new StackPanel();
|
||
var waves = Waves();
|
||
for (int w = 0; w < waves.Count; w++)
|
||
{
|
||
int waveIndex = w;
|
||
var wave = waves[w];
|
||
var card = new StackPanel();
|
||
var head = new DockPanel { Margin = new Thickness(0, 0, 0, 10) };
|
||
var drop = Ui.Button("Remove wave", (s, e) => Edit(() => BuildFile.List(Battle, "monsters").RemoveAt(waveIndex)), "", size: 11.5);
|
||
DockPanel.SetDock(drop, Dock.Right);
|
||
head.Children.Add(drop);
|
||
head.Children.Add(Ui.Text("Wave " + (w + 1), "Text", 14));
|
||
card.Children.Add(head);
|
||
|
||
foreach (var entry in wave.OfType<Dictionary<string, object>>().ToList())
|
||
{
|
||
var monster = entry;
|
||
int id = BuildFile.Int(monster, "monster_id");
|
||
var info = catalog.Monsters.FirstOrDefault(m => m.Id == id);
|
||
var name = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
|
||
var title = new StackPanel { Orientation = Orientation.Horizontal };
|
||
title.Children.Add(new Border { Width = 8, Height = 8, CornerRadius = new CornerRadius(4), Background = RankBrush(info?.Extra), Margin = new Thickness(0, 0, 8, 0), VerticalAlignment = VerticalAlignment.Center });
|
||
title.Children.Add(Ui.Text(info?.Name ?? "Unknown enemy", "Text", 13));
|
||
name.Children.Add(title);
|
||
name.Children.Add(Ui.Text((info?.Extra ?? "") + " #" + id, "Faint", 11.5));
|
||
var level = Ui.Stepper(1, 100, BuildFile.Int(monster, "level", 1), v => Edit(() => monster["level"] = v, false), 92);
|
||
var amount = Ui.Stepper(1, 5, BuildFile.Int(monster, "amount", 1), v => Edit(() => monster["amount"] = v, false), 84);
|
||
var remove = Ui.Button("", (s, e) => Edit(() => wave.Remove(monster)), "", size: 10);
|
||
remove.Padding = new Thickness(8, 5, 8, 5);
|
||
card.Children.Add(Ui.Row((name, Ui.Star()),
|
||
(Ui.Text("Lv", "Faint", 11.5), Ui.Px(22)), (level, Ui.Auto), (null, Ui.Px(12)),
|
||
(Ui.Text("×", "Faint", 12), Ui.Px(14)), (amount, Ui.Auto), (null, Ui.Px(10)), (remove, Ui.Auto)));
|
||
}
|
||
var add = new Picker("Add an enemy…") { Items = monsters };
|
||
add.Picked += item => Edit(() => wave.Add(new Dictionary<string, object> { ["monster_id"] = item.Id, ["amount"] = 1, ["level"] = 95 }));
|
||
card.Children.Add(add);
|
||
body.Children.Add(new Border { Background = Ui.Brush("Field"), CornerRadius = new CornerRadius(12), Padding = new Thickness(14), Margin = new Thickness(0, 0, 0, 10), Child = card });
|
||
}
|
||
var addWave = Ui.Button("Add wave", (s, e) => Edit(() => BuildFile.List(Battle, "monsters").Add(new List<object>())), "");
|
||
addWave.HorizontalAlignment = HorizontalAlignment.Left;
|
||
body.Children.Add(addWave);
|
||
return Ui.Section("Enemies", body, "");
|
||
}
|
||
|
||
UIElement BlessingSection()
|
||
{
|
||
var body = new StackPanel();
|
||
var list = BuildFile.List(Battle, "blessings");
|
||
foreach (var entry in list.OfType<Dictionary<string, object>>().ToList())
|
||
{
|
||
var blessing = entry;
|
||
int id = BuildFile.Int(blessing, "id");
|
||
var info = catalog.Buffs.FirstOrDefault(b => b.Id == id);
|
||
var name = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
|
||
name.Children.Add(Ui.Text(info?.Name ?? "Buff " + id, "Text", 13));
|
||
name.Children.Add(Ui.Text("#" + id, "Faint", 11.5));
|
||
var level = Ui.Stepper(1, 20, BuildFile.Int(blessing, "level", 1), v => Edit(() => blessing["level"] = v, false), 92);
|
||
var remove = Ui.Button("", (s, e) => Edit(() => list.Remove(blessing)), "", size: 10);
|
||
remove.Padding = new Thickness(8, 5, 8, 5);
|
||
body.Children.Add(Ui.Row((name, Ui.Star()), (Ui.Text("Lv", "Faint", 11.5), Ui.Px(22)), (level, Ui.Auto), (null, Ui.Px(10)), (remove, Ui.Auto)));
|
||
}
|
||
var add = new Picker("Add a blessing or buff…") { Items = buffs };
|
||
add.Picked += item => Edit(() => list.Add(new Dictionary<string, object> { ["level"] = 1, ["id"] = item.Id }));
|
||
body.Children.Add(add);
|
||
return Ui.Section("Blessings", body, "");
|
||
}
|
||
|
||
UIElement StatsSection()
|
||
{
|
||
var body = new StackPanel();
|
||
body.Children.Add(Ui.Text("Added to every character in the fight, on top of their relics.", "Faint", 12));
|
||
body.Children.Add(new Border { Height = 10 });
|
||
var list = BuildFile.List(Battle, "custom_stats");
|
||
foreach (var entry in list.OfType<Dictionary<string, object>>().ToList())
|
||
{
|
||
var stat = entry;
|
||
int id = BuildFile.Int(stat, "sub_affix_id");
|
||
var affix = catalog.Sub(id);
|
||
var value = Ui.Text(affix == null ? "" : affix.Format(affix.Base * BuildFile.Int(stat, "count") + affix.Step * BuildFile.Int(stat, "step")), "Ok", 12.5);
|
||
var count = Ui.Stepper(0, 999, BuildFile.Int(stat, "count"), v => Edit(() => stat["count"] = v), 96);
|
||
var remove = Ui.Button("", (s, e) => Edit(() => list.Remove(stat)), "", size: 10);
|
||
remove.Padding = new Thickness(8, 5, 8, 5);
|
||
body.Children.Add(Ui.Row((Ui.Text(affix?.Property ?? "Stat " + id, "Text", 13), Ui.Star()), (value, Ui.Px(90)),
|
||
(Ui.Text("rolls", "Faint", 11.5), Ui.Px(36)), (count, Ui.Auto), (null, Ui.Px(10)), (remove, Ui.Auto)));
|
||
}
|
||
var add = new Picker("Add a stat…") { Items = subs };
|
||
add.Picked += item => Edit(() => list.Add(new Dictionary<string, object> { ["sub_affix_id"] = item.Id, ["count"] = 1, ["step"] = 0 }));
|
||
body.Children.Add(add);
|
||
return Ui.Section("Extra stats", body, "");
|
||
}
|
||
|
||
UIElement LineupSection()
|
||
{
|
||
var body = new StackPanel();
|
||
body.Children.Add(Ui.Text("Fight with this team instead of your current lineup. Leave all four empty to use your lineup.", "Faint", 12));
|
||
body.Children.Add(new Border { Height = 10 });
|
||
var lineup = BuildFile.Dict(Battle, "custom_battle_lineup");
|
||
var owned = catalog.Avatars.Where(a => file.Avatar(a.Id) != null).OrderBy(a => a.Name)
|
||
.Select(a => new PickItem { Id = a.Id, Title = a.Name, Sub = a.Element + " " + a.Path, Accent = Palette.Solid(a.Element), Tag = a, IconPath = Icons.Character(a.Id) }).ToList();
|
||
owned.Insert(0, new PickItem { Id = 0, Title = "Empty", Sub = "", Accent = Ui.Brush("Faint") });
|
||
var grid = new System.Windows.Controls.Primitives.UniformGrid { Columns = 2 };
|
||
for (int slot = 0; slot < 4; slot++)
|
||
{
|
||
string key = slot.ToString();
|
||
int id = BuildFile.Int(lineup, key);
|
||
var picker = new Picker("Slot " + (slot + 1) + " empty") { Items = owned, Margin = new Thickness(0, 0, 10, 10) };
|
||
var who = catalog.Avatar(id);
|
||
if (who != null)
|
||
picker.Show(new PickItem { Title = who.Name, Sub = "Slot " + (slot + 1), IconPath = Icons.Character(who.Id) });
|
||
picker.Picked += item => Edit(() =>
|
||
{
|
||
var team = BuildFile.Dict(Battle, "custom_battle_lineup");
|
||
if (item.Id == 0) team.Remove(key);
|
||
else team[key] = item.Id;
|
||
if (team.Count == 0) Battle.Remove("custom_battle_lineup");
|
||
});
|
||
grid.Children.Add(picker);
|
||
}
|
||
body.Children.Add(grid);
|
||
if (lineup.Count == 0)
|
||
Battle.Remove("custom_battle_lineup");
|
||
return Ui.Section("Team", body, "");
|
||
}
|
||
}
|
||
}
|