574 lines
29 KiB
C#
574 lines
29 KiB
C#
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<string, string, string, bool, System.Threading.Tasks.Task<bool>> ask;
|
|
readonly List<CharRow> rows = new List<CharRow>();
|
|
string element;
|
|
AvatarInfo current;
|
|
|
|
public BuildsPage()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
public void Init(Catalog catalog, BuildFile file, Action changed, Func<string, string, string, bool, System.Threading.Tasks.Task<bool>> 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<string, object> 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<string, object> 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<string, object> 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<AffixInfo> 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<SubStat> 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<Dictionary<string, object>>())
|
|
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<Dictionary<string, object>>())
|
|
cone["rank"] = 5;
|
|
file.Touch();
|
|
AfterBulk();
|
|
}
|
|
|
|
void AfterBulk()
|
|
{
|
|
changed();
|
|
foreach (CharRow row in rows) row.Refresh(file);
|
|
if (current != null) Redraw();
|
|
}
|
|
}
|
|
}
|