using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; namespace CapyLauncher { public class CharRow : INotifyPropertyChanged { ImageSource icon, elementIcon; bool requested; public AvatarInfo Info { get; set; } public Visibility InitialsVisibility => icon == null ? Visibility.Visible : Visibility.Hidden; public ImageSource Icon { get { Request(); return icon; } } public ImageSource ElementIcon { get { Request(); return elementIcon; } } async void Request() { if (requested) return; requested = true; elementIcon = await Icons.Get(Icons.Element(Info.Element)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ElementIcon))); icon = await Icons.Get(Icons.Character(Info.Id)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Icon))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InitialsVisibility))); } public string Name => Info.Name; public string Initials => Info.Initials; public Brush Badge { get; set; } public string Stars => new string('★', Math.Max(0, Info.Rarity)); public Brush StarBrush => Palette.Rarity(Info.Rarity); public string Sub { get; private set; } public double Opacity { get; private set; } public event PropertyChangedEventHandler PropertyChanged; public void Refresh(BuildFile file) { var avatar = file.Avatar(Info.Id); Sub = avatar == null ? "Not added · " + Info.Element : "E" + BuildFile.Rank(avatar) + " · Lv " + BuildFile.Int(avatar, "level", 1) + " · " + Info.Path; Opacity = avatar == null ? 0.5 : 1; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Sub))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Opacity))); } } public partial class BuildsPage : UserControl { static readonly string[] Elements = { "Physical", "Fire", "Ice", "Lightning", "Wind", "Quantum", "Imaginary" }; static readonly string[] SlotNames = { "", "Head", "Hands", "Body", "Feet", "Planar Sphere", "Link Rope" }; Catalog catalog; BuildFile file; Action changed; Func> ask; readonly List rows = new List(); string element; AvatarInfo current; public BuildsPage() { InitializeComponent(); } public void Init(Catalog catalog, BuildFile file, Action changed, Func> ask) { this.catalog = catalog; this.file = file; this.changed = changed; this.ask = ask; string version = string.IsNullOrEmpty(catalog.Version) ? "Unknown" : catalog.Version; VersionTag.Text = version.Contains("v3") ? "v3" : version; VersionText.Text = "Honkai: Star Rail " + version; VersionCounts.Text = catalog.Avatars.Count + " characters · " + catalog.Lightcones.Count + " light cones · " + catalog.RelicSets.Count + " relic sets · " + catalog.Monsters.Count.ToString("#,0") + " enemies"; rows.Clear(); foreach (AvatarInfo info in catalog.Avatars.OrderByDescending(a => a.Rarity).ThenBy(a => a.Name)) { var row = new CharRow { Info = info, Badge = Palette.Element(info.Element) }; row.Refresh(file); rows.Add(row); } BuildFilters(); Filter(); if (current != null) Show(current); } void Redraw() { double offset = EditorScroll.VerticalOffset; Show(current, false); EditorScroll.UpdateLayout(); EditorScroll.ScrollToVerticalOffset(offset); } public void Reload() { foreach (CharRow row in rows) row.Refresh(file); Filter(); if (current != null) Redraw(); } void Changed() { changed(); rows.FirstOrDefault(r => r.Info == current)?.Refresh(file); OwnedCount.Text = file.Avatars.Count + " of " + rows.Count; } // ---- list ---- void BuildFilters() { ElementFilters.Children.Clear(); string group = "elements"; var all = new RadioButton { Content = "All", GroupName = group, Style = (Style)FindResource("Seg"), IsChecked = element == null, Height = 26, MinWidth = 30, FontSize = 11.5, Margin = new Thickness(0, 0, 4, 4), Padding = new Thickness(0) }; all.Checked += (s, e) => { element = null; Filter(); }; ElementFilters.Children.Add(all); foreach (string name in Elements) { var dot = Icons.View(Icons.Element(name), 18, new Border { Width = 10, Height = 10, CornerRadius = new CornerRadius(5), Background = Palette.Element(name) }); var option = new RadioButton { Content = dot, GroupName = group, Style = (Style)FindResource("Seg"), MinWidth = 26, Width = 28, Height = 26, Padding = new Thickness(0), ToolTip = name, IsChecked = element == name, Margin = new Thickness(0, 0, 4, 4) }; option.Checked += (s, e) => { element = name; Filter(); }; ElementFilters.Children.Add(option); } } void Filter_Changed(object sender, RoutedEventArgs e) => Filter(); void Filter() { if (file == null) return; string query = SearchBox.Text.Trim(); var shown = rows.Where(r => (element == null || r.Info.Element == element) && (OwnedOnly.IsChecked != true || file.Avatar(r.Info.Id) != null) && (query.Length == 0 || r.Name.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0 || r.Info.Path.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0 || r.Info.Id.ToString().StartsWith(query))) .ToList(); CharRow selected = CharList.SelectedItem as CharRow; CharList.ItemsSource = shown; if (selected != null && shown.Contains(selected)) CharList.SelectedItem = selected; OwnedCount.Text = file.Avatars.Count + " of " + rows.Count; } void CharList_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (CharList.SelectedItem is CharRow row && row.Info != current) Show(row.Info); } // ---- editor ---- void Show(AvatarInfo info, bool fresh = true) { current = info; NoPick.Visibility = Visibility.Collapsed; Editor.Visibility = Visibility.Visible; var avatar = file.Avatar(info.Id); HeaderBadge.Content = Ui.Portrait(info, 64); HeaderName.Text = info.Name; HeaderChips.Children.Clear(); HeaderChips.Children.Add(Ui.Chip(info.Element, Palette.Solid(info.Element), Icons.Element(info.Element))); HeaderChips.Children.Add(Ui.Chip(info.Path, Ui.Brush("Pink"), Icons.Path(info.Path))); HeaderChips.Children.Add(Ui.Chip(new string('★', info.Rarity), Palette.Rarity(info.Rarity))); HeaderChips.Children.Add(Ui.Chip("#" + info.Id, Ui.Brush("Faint"))); HeaderActions.Children.Clear(); EditorStack.Children.Clear(); if (avatar == null) { var add = Ui.Button("Add to my characters", (s, e) => { file.AddAvatar(info); Changed(); Redraw(); }, "", primary: true); HeaderActions.Children.Add(add); var empty = new StackPanel { Margin = new Thickness(0, 60, 0, 0), HorizontalAlignment = HorizontalAlignment.Center }; empty.Children.Add(Ui.Text(info.Name + " isn't in your build yet.", "Dim", 14)); empty.Children.Add(Ui.Text("Adding them gives level 80, E0 and every trace maxed.", "Faint", 12.5)); EditorStack.Children.Add(empty); if (fresh) Ui.FadeIn(EditorStack); return; } HeaderActions.Children.Add(Ui.Button("Max out", (s, e) => { file.MaxAvatar(avatar, info, false); Changed(); Redraw(); }, "")); var remove = Ui.Button("", async (s, e) => { if (await ask("Remove " + info.Name + "?", "They leave your build, and their light cone and relics go back to the bag.", "Remove", true)) { file.RemoveAvatar(info.Id); Changed(); Redraw(); } }, ""); remove.Margin = new Thickness(8, 0, 0, 0); remove.ToolTip = "Remove from my characters"; HeaderActions.Children.Add(remove); EditorStack.Children.Add(StatsSection(info, avatar)); EditorStack.Children.Add(CharacterSection(info, avatar)); EditorStack.Children.Add(TraceSection(info, avatar)); EditorStack.Children.Add(LightconeSection(info)); EditorStack.Children.Add(RelicSection(info)); if (fresh) { EditorScroll.ScrollToTop(); Dispatcher.BeginInvoke(new Action(() => EditorScroll.ScrollToTop()), System.Windows.Threading.DispatcherPriority.Loaded); Ui.FadeIn(EditorStack); } } UIElement StatsSection(AvatarInfo info, Dictionary avatar) { var grid = new UniformGrid { Columns = 2 }; foreach (StatLine line in StatSheet.Build(catalog, file, info, avatar)) { var row = new DockPanel { Margin = new Thickness(0, 0, 4, 0) }; var statIcon = Icons.View(line.Icon, 22); statIcon.Margin = new Thickness(0, 0, 10, 0); DockPanel.SetDock(statIcon, Dock.Left); row.Children.Add(statIcon); var values = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; values.Children.Add(new TextBlock { Text = line.Show(line.Total), FontWeight = FontWeights.SemiBold, FontSize = 14, VerticalAlignment = VerticalAlignment.Center }); if (line.Base > 0 && line.Bonus > 0 && !line.Percent) { values.Children.Add(new TextBlock { Text = " " + line.Show(line.Base), Foreground = Ui.Brush("Faint"), FontSize = 11.5, VerticalAlignment = VerticalAlignment.Center }); values.Children.Add(new TextBlock { Text = " +" + line.Show(line.Bonus), Foreground = Ui.Brush("Ok"), FontSize = 11.5, VerticalAlignment = VerticalAlignment.Center }); } DockPanel.SetDock(values, Dock.Right); row.Children.Add(values); row.Children.Add(Ui.Text(line.Label, "Dim", 12.5)); grid.Children.Add(new Border { BorderBrush = Ui.Brush("SoftLine"), BorderThickness = new Thickness(0, 0, 0, 1), Padding = new Thickness(0, 8, 0, 8), Margin = new Thickness(0, 0, 14, 0), Child = row }); } return Ui.Section("Stats", grid, "\uE9D2"); } UIElement CharacterSection(AvatarInfo info, Dictionary avatar) { var body = new StackPanel(); body.Children.Add(Ui.Row((Ui.Text("Level", "Dim", 12.5), Ui.Px(120)), (Ui.Slider(1, 80, BuildFile.Int(avatar, "level", 1), v => { file.SetLevel(avatar, v); Changed(); }, v => "Lv " + v), Ui.Star()))); var ranks = Enumerable.Range(0, info.MaxRank + 1).Select(r => "E" + r); body.Children.Add(Ui.Row((Ui.Text("Eidolons", "Dim", 12.5), Ui.Px(120)), (Ui.Segments(ranks, BuildFile.Rank(avatar), v => { file.SetRank(avatar, v); Changed(); }), Ui.Star()))); if (info.Sp > 0) { int max = BuildFile.Int(avatar, "sp_max", info.Sp); body.Children.Add(Ui.Row((Ui.Text("Starting energy", "Dim", 12.5), Ui.Px(120)), (Ui.Slider(0, max, BuildFile.Int(avatar, "sp_value"), v => { avatar["sp_value"] = v; file.Touch(); Changed(); }, v => v + "/" + max), Ui.Star()))); } if (info.Enhanced) { body.Children.Add(Ui.Row((Ui.Text("Version", "Dim", 12.5), Ui.Px(120)), (Ui.Toggle("Enhanced", BuildFile.Int(avatar, "enhanced_id") != 0, on => { avatar["enhanced_id"] = on ? 1 : 0; file.Touch(); Changed(); }), Ui.Star()))); } return Ui.Section("Character", body, ""); } UIElement TraceSection(AvatarInfo info, Dictionary avatar) { var body = new StackPanel(); foreach (Anchor anchor in info.Anchors.Where(a => a.Max > 1)) { Anchor mine = anchor; body.Children.Add(Ui.Row((Labelled(Icons.Skill(info.Id, anchor), anchor.Label), Ui.Px(150)), (Ui.Slider(1, anchor.Max, Math.Max(1, BuildFile.AnchorLevel(avatar, anchor)), v => { file.SetAnchor(avatar, mine, v); Changed(); }, v => "Lv " + v), Ui.Star()))); } var minor = new WrapPanel { Margin = new Thickness(0, 4, 0, 0) }; foreach (Anchor anchor in info.Anchors.Where(a => a.Max <= 1)) { Anchor mine = anchor; string label = anchor.Stats.Count > 0 ? StatSheet.Describe(anchor.Stats[0]) : anchor.Label; var toggle = Ui.Toggle("", BuildFile.AnchorLevel(avatar, anchor) > 0, on => { file.SetAnchor(avatar, mine, on ? 1 : 0); Changed(); }); toggle.Content = Labelled(Icons.Skill(info.Id, anchor), label, "Text"); toggle.Width = 200; toggle.Margin = new Thickness(0, 0, 8, 10); minor.Children.Add(toggle); } body.Children.Add(minor); var buttons = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 4, 0, 0) }; buttons.Children.Add(Ui.Button("Max all traces", (s, e) => { foreach (Anchor anchor in info.Anchors) file.SetAnchor(avatar, anchor, anchor.Max); Changed(); Redraw(); }, "")); body.Children.Add(buttons); return Ui.Section("Traces", body, ""); } static FrameworkElement Labelled(string icon, string text, string color = "Dim") { var row = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; var view = Icons.View(icon, 24, new Border { Width = 6, Height = 6, CornerRadius = new CornerRadius(3), Background = Ui.Brush("Faint") }); view.Margin = new Thickness(0, 0, 9, 0); row.Children.Add(view); row.Children.Add(Ui.Text(text, color, 12.5)); return row; } UIElement LightconeSection(AvatarInfo info) { var body = new StackPanel(); var cone = file.LightconeOf(info.Id); var picker = new Picker("No light cone") { Items = catalog.Lightcones.OrderByDescending(l => l.Path == info.Path).ThenByDescending(l => l.Rarity).ThenBy(l => l.Name) .Select(l => new PickItem { Id = l.Id, Title = l.Name, Sub = new string('★', l.Rarity) + " " + l.Path, Accent = Palette.Rarity(l.Rarity), Tag = l, IconPath = Icons.Lightcone(l.Id) }).ToList(), }; var known = cone == null ? null : catalog.Lightcone(BuildFile.Int(cone, "item_id")); if (cone != null) picker.Show(new PickItem { Title = known?.Name ?? "Light cone " + BuildFile.Int(cone, "item_id"), Sub = known == null ? "" : new string('★', known.Rarity) + " " + known.Path, IconPath = Icons.Lightcone(BuildFile.Int(cone, "item_id")) }); picker.Picked += item => { file.EquipLightcone(info.Id, item.Id); Changed(); Redraw(); }; var art = cone == null ? null : Icons.View(Icons.Lightcone(BuildFile.Int(cone, "item_id")), 96); body.Children.Add(Ui.Row((Ui.Text("Light cone", "Dim", 12.5), Ui.Px(120)), (picker, Ui.Star()))); if (art != null) { art.HorizontalAlignment = HorizontalAlignment.Left; art.Margin = new Thickness(120, 0, 0, 10); body.Children.Add(art); } if (cone != null) { if (known != null && known.Path != info.Path) body.Children.Add(Ui.Row((null, Ui.Px(120)), (Ui.Text("Different path, so its passive won't apply", "Warn", 12), Ui.Star()))); body.Children.Add(Ui.Row((Ui.Text("Level", "Dim", 12.5), Ui.Px(120)), (Ui.Slider(1, 80, BuildFile.Int(cone, "level", 1), v => { file.SetLightconeLevel(cone, v); Changed(); }, v => "Lv " + v), Ui.Star()))); body.Children.Add(Ui.Row((Ui.Text("Superimposition", "Dim", 12.5), Ui.Px(120)), (Ui.Segments(Enumerable.Range(1, 5).Select(r => "S" + r), BuildFile.Int(cone, "rank", 1) - 1, v => { cone["rank"] = v + 1; file.Touch(); Changed(); }), Ui.Star()))); var remove = Ui.Button("Take it off", (s, e) => { file.UnequipLightcone(info.Id); Changed(); Redraw(); }, ""); remove.HorizontalAlignment = HorizontalAlignment.Left; body.Children.Add(remove); } return Ui.Section("Light cone", body, ""); } UIElement RelicSection(AvatarInfo info) { var grid = new UniformGrid { Columns = 2 }; for (int slot = 1; slot <= 6; slot++) grid.Children.Add(RelicCard(info, slot)); var body = new StackPanel(); body.Children.Add(grid); return Ui.Section("Relics", body, ""); } UIElement RelicCard(AvatarInfo info, int slot) { var relic = file.RelicOf(info.Id, slot); var card = new StackPanel(); var head = new DockPanel { Margin = new Thickness(0, 0, 0, 10) }; var title = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; if (relic != null) { var piece = Icons.View(Icons.RelicPiece(BuildFile.Int(relic, "relic_set_id"), slot), 34); piece.Margin = new Thickness(0, 0, 10, 0); title.Children.Add(piece); } title.Children.Add(new TextBlock { Text = SlotNames[slot], FontWeight = FontWeights.SemiBold, VerticalAlignment = VerticalAlignment.Center }); bool planar = slot >= 5; var sets = catalog.RelicSets.Where(s => s.Planar == planar && s.Pieces.ContainsKey(slot)).OrderBy(s => s.Name).ToList(); if (relic == null) { head.Children.Add(title); card.Children.Add(head); var add = Ui.Button("Add " + SlotNames[slot].ToLowerInvariant(), (s, e) => { var set = sets.First(); int main = catalog.MainAffixes.TryGetValue(slot, out var mains) ? mains.First().Id : 1; var created = file.PutRelic(info.Id, slot, set, main); file.SetSubs(created, DefaultSubs(slot, main)); Changed(); Redraw(); }, ""); add.HorizontalAlignment = HorizontalAlignment.Left; card.Children.Add(add); return Wrap(card); } var remove = Ui.Button("", (s, e) => { file.RemoveRelic(info.Id, slot); Changed(); Redraw(); }, "", size: 10); remove.Padding = new Thickness(7, 4, 7, 4); remove.ToolTip = "Remove this relic"; DockPanel.SetDock(remove, Dock.Right); head.Children.Add(remove); head.Children.Add(title); card.Children.Add(head); int setId = BuildFile.Int(relic, "relic_set_id"); var setPicker = new Picker("Pick a set") { Items = sets.Select(s => new PickItem { Id = s.Id, Title = s.Name, Sub = SetSummary(s), Accent = Ui.Brush(planar ? "Blue" : "Pink"), Tag = s, IconPath = Icons.RelicSet(s.Id) }).ToList(), }; var currentSet = catalog.RelicSet(setId); setPicker.Show(new PickItem { Title = currentSet?.Name ?? "Set " + setId, IconPath = Icons.RelicSet(setId) }); setPicker.Picked += item => { file.PutRelic(info.Id, slot, (RelicSetInfo)item.Tag, BuildFile.Int(relic, "main_affix_id")); Changed(); Redraw(); }; card.Children.Add(setPicker); int level = BuildFile.Int(relic, "level"); int mainId = BuildFile.Int(relic, "main_affix_id"); catalog.MainAffixes.TryGetValue(slot, out List mainList); AffixInfo mainAffix = catalog.Main(slot, mainId); string MainValue(AffixInfo a, int lv) => a == null ? "" : a.Format(a.Base + a.PerLevel * lv); var mainRow = new DockPanel { Margin = new Thickness(0, 10, 0, 0) }; if (mainList != null && mainList.Count > 1) { var mainPicker = new Picker("Main stat") { Items = mainList.Select(a => new PickItem { Id = a.Id, Title = a.Property, Sub = MainValue(a, 15), Accent = Ui.Brush("Warn"), Tag = a, IconPath = Icons.Property(a.Prop) }).ToList(), }; mainPicker.Show(new PickItem { Title = mainAffix?.Property ?? "Main stat " + mainId, Sub = MainValue(mainAffix, level), IconPath = Icons.Property(mainAffix?.Prop) }); mainPicker.Picked += item => { relic["main_affix_id"] = item.Id; var subs = BuildFile.Subs(relic).Where(s => catalog.Sub(s.Id)?.Property != ((AffixInfo)item.Tag).Property).ToList(); file.SetSubs(relic, subs); Changed(); Redraw(); }; mainRow.Children.Add(mainPicker); } else { var fixedMain = Labelled(Icons.Property(mainAffix?.Prop), (mainAffix?.Property ?? "Main stat") + " " + MainValue(mainAffix, level), "Text"); mainRow.Children.Add(new Border { Background = Ui.Brush("Field"), CornerRadius = new CornerRadius(9), Padding = new Thickness(9, 5, 11, 5), Child = fixedMain }); } card.Children.Add(mainRow); card.Children.Add(new Border { Margin = new Thickness(0, 10, 0, 6), Child = Ui.Slider(0, 15, level, v => { relic["level"] = v; file.Touch(); Changed(); }, v => "+" + v), }); var subsNow = BuildFile.Subs(relic); int rolls = subsNow.Sum(s => s.Count); var subHead = new DockPanel { Margin = new Thickness(0, 4, 0, 6) }; var rollText = Ui.Text(rolls + "/9 rolls", rolls > 9 ? "Warn" : "Faint", 11); DockPanel.SetDock(rollText, Dock.Right); subHead.Children.Add(rollText); subHead.Children.Add(Ui.Text("SUBSTATS", "Dim", 10.5)); card.Children.Add(subHead); for (int i = 0; i < 4; i++) { int index = i; SubStat sub = index < subsNow.Count ? subsNow[index] : null; AffixInfo affix = sub == null ? null : catalog.Sub(sub.Id); var taken = subsNow.Where((s, n) => n != index).Select(s => s.Id).ToHashSet(); var picker = new Picker("Empty") { Items = catalog.SubAffixes.Where(a => !taken.Contains(a.Id) && a.Property != mainAffix?.Property) .Select(a => new PickItem { Id = a.Id, Title = a.Property, Sub = a.Format(a.Base), Accent = Ui.Brush("Ok"), Tag = a, IconPath = Icons.Property(a.Prop) }).ToList(), Padding = new Thickness(9, 5, 8, 5), MinHeight = 30, }; if (sub != null) picker.Show(new PickItem { Title = affix?.Property ?? "Sub " + sub.Id, Sub = affix == null ? "" : affix.Format(affix.Base * sub.Count + affix.Step * sub.Step), IconPath = Icons.Property(affix?.Prop) }); picker.Picked += item => { var list = BuildFile.Subs(relic); if (index < list.Count) list[index].Id = item.Id; else list.Add(new SubStat { Id = item.Id, Count = 1, Step = 2 }); file.SetSubs(relic, list); Changed(); Redraw(); }; UIElement rollsBox = null; if (sub != null) { rollsBox = Ui.Stepper(1, 6, sub.Count, v => { var list = BuildFile.Subs(relic); list[index].Count = v; list[index].Step = Math.Min(list[index].Step, v * 2); file.SetSubs(relic, list); Changed(); Redraw(); }, 84); ((FrameworkElement)rollsBox).ToolTip = "Rolls"; ((FrameworkElement)rollsBox).Margin = new Thickness(6, 0, 0, 0); } card.Children.Add(Ui.Row((picker, Ui.Star()), (rollsBox, Ui.Auto))); } var perfect = Ui.Button("Perfect rolls", (s, e) => { var list = BuildFile.Subs(relic); foreach (SubStat s2 in list) s2.Step = s2.Count * 2; file.SetSubs(relic, list); Changed(); Redraw(); }, "", size: 11.5); perfect.HorizontalAlignment = HorizontalAlignment.Left; perfect.ToolTip = "Every roll at its highest value"; card.Children.Add(perfect); return Wrap(card); } static string SetSummary(RelicSetInfo set) { var parts = set.Bonus.OrderBy(b => b.Key).Where(b => b.Value.Count > 0).Select(b => b.Key + "pc " + string.Join(", ", b.Value.Select(StatSheet.Describe))); string text = string.Join(" · ", parts); return text.Length > 0 ? text : "#" + set.Id; } List DefaultSubs(int slot, int mainId) { string main = catalog.Main(slot, mainId)?.Property; var wanted = new[] { "CRIT Rate", "CRIT DMG", "ATK%", "SPD" }; var picks = catalog.SubAffixes.Where(a => a.Property != main).OrderBy(a => Array.IndexOf(wanted, a.Property) is int n && n >= 0 ? n : 99).Take(4).ToList(); return picks.Select((a, n) => new SubStat { Id = a.Id, Count = n == 0 ? 3 : 2, Step = n == 0 ? 6 : 4 }).ToList(); } static Border Wrap(UIElement content) => new Border { Background = Ui.Brush("Field"), CornerRadius = new CornerRadius(12), Padding = new Thickness(14), Margin = new Thickness(0, 0, 10, 10), Child = content, }; // ---- everyone ---- async void MaxEveryone_Click(object sender, RoutedEventArgs e) { if (!await ask("Max everyone?", "Every character you have goes to level 80 with every trace maxed. Eidolons stay as they are.", "Max them", false)) return; foreach (CharRow row in rows) { var avatar = file.Avatar(row.Info.Id); if (avatar != null) file.MaxAvatar(avatar, row.Info, false); } foreach (var cone in file.Lightcones.OfType>()) file.SetLightconeLevel(cone, 80); AfterBulk(); } async void E6S5_Click(object sender, RoutedEventArgs e) { if (!await ask("E6 S5 everyone?", "Every character you have goes to their highest eidolon, and every light cone to S5.", "Do it", false)) return; foreach (CharRow row in rows) { var avatar = file.Avatar(row.Info.Id); if (avatar != null) file.SetRank(avatar, row.Info.MaxRank); } foreach (var cone in file.Lightcones.OfType>()) cone["rank"] = 5; file.Touch(); AfterBulk(); } void AfterBulk() { changed(); foreach (CharRow row in rows) row.Refresh(file); if (current != null) Redraw(); } } }