Files
CapySR/gui/BattlePage.xaml.cs
T
2026-10-17 20:42:15 -07:00

242 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, "");
}
}
}