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 Names = new Dictionary { { "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 ElementProps = new Dictionary { { "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 Build(Catalog catalog, BuildFile file, AvatarInfo info, Dictionary avatar) { var bonus = new Dictionary(); 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(); 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 { 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 }, }; } } }