import { calculateSubstats, MAIN_STATS } from "./generate"; import type { Build, CatalogBaseStat } from "./types"; export interface FinalStat { key: string; value: number; percent?: boolean; } type Bonuses = Record; function addCatalogStats(stats: CatalogBaseStat[], bonuses: Bonuses) { for (const stat of stats) { const value = Number(stat.value); if (!Number.isFinite(value)) continue; const key = stat.key.toLowerCase().replaceAll(" ", ""); if (key === "em") bonuses.em += value; else if (key === "er" || key === "energyrecharge%") bonuses.er += value / 100; else if (key === "cr" || key === "critrate%") bonuses.cr += value / 100; else if (key === "cd" || key === "critdmg%") bonuses.cd += value / 100; else if (key === "hp%") bonuses["hp%"] += value / 100; else if (key === "atk%") bonuses["atk%"] += value / 100; else if (key === "def%") bonuses["def%"] += value / 100; else if (key.endsWith("%")) { const bonusKey = key.slice(0, -1); bonuses[bonusKey] = (bonuses[bonusKey] ?? 0) + value / 100; } } } function baseValue(stats: CatalogBaseStat[], key: string): number { return Number(stats.find((stat) => stat.key.toLowerCase() === key)?.value ?? 0); } export function calculateFinalStats(build: Build, characterStats: CatalogBaseStat[], weaponStats: CatalogBaseStat[]): FinalStat[] { const bonuses: Bonuses = { em: 0, er: 1, cr: 0.05, cd: 0.5, "hp%": 0, "atk%": 0, "def%": 0 }; addCatalogStats(characterStats.filter((stat) => !["hp", "atk", "def"].includes(stat.key.toLowerCase())), bonuses); addCatalogStats(weaponStats.filter((stat) => stat.key.toLowerCase() !== "atk"), bonuses); const artifactStats: Bonuses = { hp: 4780, atk: 311, def: 0, em: 0, er: 0, cr: 0, cd: 0, "hp%": 0, "atk%": 0, "def%": 0 }; for (const key of build.importedStats ? [] : [build.sands, build.goblet, build.circlet]) { const bonusKey = key.endsWith("%") && !["hp%", "atk%", "def%"].includes(key) ? key.slice(0, -1) : key; artifactStats[bonusKey] = (artifactStats[bonusKey] ?? 0) + MAIN_STATS[key]; } const substats = build.importedStats ?? calculateSubstats(build); if (build.importedStats) { artifactStats.hp = 0; artifactStats.atk = 0; } for (const [key, value] of Object.entries(substats)) { const bonusKey = key.endsWith("%") && !["hp%", "atk%", "def%"].includes(key) ? key.slice(0, -1) : key; artifactStats[bonusKey] = (artifactStats[bonusKey] ?? 0) + value; } for (const [key, value] of Object.entries(artifactStats)) bonuses[key] = (bonuses[key] ?? 0) + value; const characterAtk = baseValue(characterStats, "atk"); const weaponAtk = baseValue(weaponStats, "atk"); const core: FinalStat[] = [ { key: "HP", value: baseValue(characterStats, "hp") * (1 + bonuses["hp%"]) + bonuses.hp }, { key: "ATK", value: (characterAtk + weaponAtk) * (1 + bonuses["atk%"]) + bonuses.atk }, { key: "DEF", value: baseValue(characterStats, "def") * (1 + bonuses["def%"]) + bonuses.def }, { key: "EM", value: bonuses.em }, { key: "ER", value: bonuses.er, percent: true }, { key: "CR", value: bonuses.cr, percent: true }, { key: "CD", value: bonuses.cd, percent: true }, ]; const known = new Set(["hp", "atk", "def", "em", "er", "cr", "cd", "hp%", "atk%", "def%"]); const extras = Object.entries(bonuses) .filter(([key, value]) => !known.has(key) && value !== 0) .map(([key, value]) => ({ key, value, percent: true })); return [...core, ...extras]; } export function formatFinalStat(stat: FinalStat): string { const value = stat.percent ? `${(stat.value * 100).toLocaleString("en-US", { maximumFractionDigits: 2 })}%` : Math.round(stat.value).toLocaleString("en-US"); return `${stat.key.toUpperCase()} ${value}`; }