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

151 lines
8.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace CapyLauncher
{
public class StatLine
{
public string Label;
public string Prop;
public double Base;
public double Bonus;
public bool Percent;
public string Icon;
public double Total => Base + Bonus;
public string Show(double value) => Percent ? (value * 100).ToString("0.0") + "%" : Label == "SPD" ? value.ToString("0.#") : Math.Floor(value).ToString("#,0");
}
public static class StatSheet
{
static readonly Dictionary<string, string> Names = new Dictionary<string, string>
{
{ "HPDelta", "HP" }, { "AttackDelta", "ATK" }, { "DefenceDelta", "DEF" }, { "SpeedDelta", "SPD" },
{ "HPAddedRatio", "HP" }, { "AttackAddedRatio", "ATK" }, { "DefenceAddedRatio", "DEF" }, { "SpeedAddedRatio", "SPD" },
{ "CriticalChanceBase", "CRIT Rate" }, { "CriticalDamageBase", "CRIT DMG" }, { "StatusProbabilityBase", "Effect Hit Rate" },
{ "StatusResistanceBase", "Effect RES" }, { "BreakDamageAddedRatioBase", "Break Effect" }, { "HealRatioBase", "Outgoing Healing" },
{ "SPRatioBase", "Energy Regen" }, { "PhysicalAddedRatio", "Physical DMG" }, { "FireAddedRatio", "Fire DMG" },
{ "IceAddedRatio", "Ice DMG" }, { "ThunderAddedRatio", "Lightning DMG" }, { "WindAddedRatio", "Wind DMG" },
{ "QuantumAddedRatio", "Quantum DMG" }, { "ImaginaryAddedRatio", "Imaginary DMG" }, { "ElationDamageAddedRatioBase", "Elation DMG" },
};
public static string Describe(StatBonus bonus)
{
string name = Names.TryGetValue(bonus.Prop ?? "", out string found) ? found : bonus.Prop;
bool flat = bonus.Prop == "HPDelta" || bonus.Prop == "AttackDelta" || bonus.Prop == "DefenceDelta" || bonus.Prop == "SpeedDelta";
return name + " +" + (flat ? bonus.Value.ToString("0.#") : (bonus.Value * 100).ToString("0.#") + "%");
}
static readonly Dictionary<string, string> ElementProps = new Dictionary<string, string>
{
{ "Physical", "PhysicalAddedRatio" }, { "Fire", "FireAddedRatio" }, { "Ice", "IceAddedRatio" },
{ "Lightning", "ThunderAddedRatio" }, { "Wind", "WindAddedRatio" }, { "Quantum", "QuantumAddedRatio" },
{ "Imaginary", "ImaginaryAddedRatio" },
};
static double[] Step(double[][] growth, int promotion)
{
for (int p = Math.Min(promotion, growth.Length - 1); p >= 0; p--)
{
if (growth[p] != null)
return growth[p];
}
return null;
}
public static List<StatLine> Build(Catalog catalog, BuildFile file, AvatarInfo info, Dictionary<string, object> avatar)
{
var bonus = new Dictionary<string, double>();
void Add(string prop, double value)
{
if (string.IsNullOrEmpty(prop)) return;
bonus.TryGetValue(prop, out double now);
bonus[prop] = now + value;
}
int level = BuildFile.Int(avatar, "level", 1);
int promotion = BuildFile.Int(avatar, "promotion", BuildFile.PromotionFor(level));
double[] g = Step(info.Growth, promotion) ?? new double[10];
double hp = g[0] + g[1] * (level - 1), atk = g[2] + g[3] * (level - 1), def = g[4] + g[5] * (level - 1);
double spd = g[6], critRate = g[7], critDmg = g[8];
var cone = file.LightconeOf(info.Id);
var coneInfo = cone == null ? null : catalog.Lightcone(BuildFile.Int(cone, "item_id"));
if (coneInfo != null)
{
int coneLevel = BuildFile.Int(cone, "level", 1);
double[] c = Step(coneInfo.Growth, BuildFile.Int(cone, "promotion", BuildFile.PromotionFor(coneLevel)));
if (c != null)
{
hp += c[0] + c[1] * (coneLevel - 1);
atk += c[2] + c[3] * (coneLevel - 1);
def += c[4] + c[5] * (coneLevel - 1);
}
int rank = Math.Max(1, Math.Min(5, BuildFile.Int(cone, "rank", 1)));
if (coneInfo.Path == info.Path && coneInfo.Passive[rank - 1] != null)
{
foreach (StatBonus b in coneInfo.Passive[rank - 1]) Add(b.Prop, b.Value);
}
}
foreach (Anchor anchor in info.Anchors)
{
if (anchor.Stats.Count > 0 && BuildFile.AnchorLevel(avatar, anchor) > 0)
{
foreach (StatBonus b in anchor.Stats) Add(b.Prop, b.Value);
}
}
var sets = new Dictionary<int, int>();
for (int slot = 1; slot <= 6; slot++)
{
var relic = file.RelicOf(info.Id, slot);
if (relic == null) continue;
int relicLevel = BuildFile.Int(relic, "level");
AffixInfo main = catalog.Main(slot, BuildFile.Int(relic, "main_affix_id"));
if (main != null) Add(main.Prop, main.Base + main.PerLevel * relicLevel);
foreach (SubStat sub in BuildFile.Subs(relic))
{
AffixInfo affix = catalog.Sub(sub.Id);
if (affix != null) Add(affix.Prop, affix.Base * sub.Count + affix.Step * sub.Step);
}
int setId = BuildFile.Int(relic, "relic_set_id");
sets.TryGetValue(setId, out int count);
sets[setId] = count + 1;
}
foreach (var pair in sets)
{
RelicSetInfo set = catalog.RelicSet(pair.Key);
if (set == null) continue;
foreach (var need in set.Bonus)
{
if (pair.Value >= need.Key)
{
foreach (StatBonus b in need.Value) Add(b.Prop, b.Value);
}
}
}
double Get(string prop) => bonus.TryGetValue(prop, out double v) ? v : 0;
string element = ElementProps.TryGetValue(info.Element ?? "", out string found) ? found : null;
return new List<StatLine>
{
new StatLine { Label = "HP", Prop = "HP", Icon = Icons.Property("HPDelta"), Base = hp, Bonus = hp * Get("HPAddedRatio") + Get("HPDelta") },
new StatLine { Label = "ATK", Prop = "ATK", Icon = Icons.Property("AttackDelta"), Base = atk, Bonus = atk * Get("AttackAddedRatio") + Get("AttackDelta") },
new StatLine { Label = "DEF", Prop = "DEF", Icon = Icons.Property("DefenceDelta"), Base = def, Bonus = def * Get("DefenceAddedRatio") + Get("DefenceDelta") },
new StatLine { Label = "SPD", Prop = "SPD", Icon = Icons.Property("SpeedDelta"), Base = spd, Bonus = spd * Get("SpeedAddedRatio") + Get("SpeedDelta") },
new StatLine { Label = "CRIT Rate", Prop = "CRIT Rate", Icon = Icons.Property("CriticalChanceBase"), Base = critRate, Bonus = Get("CriticalChanceBase"), Percent = true },
new StatLine { Label = "CRIT DMG", Prop = "CRIT DMG", Icon = Icons.Property("CriticalDamageBase"), Base = critDmg, Bonus = Get("CriticalDamageBase"), Percent = true },
new StatLine { Label = "Break Effect", Prop = "Break Effect", Icon = Icons.Property("BreakDamageAddedRatioBase"), Bonus = Get("BreakDamageAddedRatioBase"), Percent = true },
new StatLine { Label = "Outgoing Healing", Prop = "Outgoing Healing", Icon = Icons.Property("HealRatioBase"), Bonus = Get("HealRatioBase"), Percent = true },
new StatLine { Label = "Energy Regen Rate", Prop = "Energy Regen Rate", Icon = Icons.Property("SPRatioBase"), Base = 1, Bonus = Get("SPRatioBase"), Percent = true },
new StatLine { Label = "Effect Hit Rate", Prop = "Effect Hit Rate", Icon = Icons.Property("StatusProbabilityBase"), Bonus = Get("StatusProbabilityBase"), Percent = true },
new StatLine { Label = "Effect RES", Prop = "Effect RES", Icon = Icons.Property("StatusResistanceBase"), Bonus = Get("StatusResistanceBase"), Percent = true },
new StatLine { Label = info.Element + " DMG Boost", Prop = info.Element + " DMG", Icon = Icons.ElementDamage(info.Element), Bonus = (element == null ? 0 : Get(element)) + Get("AllDamageTypeAddedRatio"), Percent = true },
};
}
}
}