import gameData from "../data/genshinGameData.json" with { type: "json" }; import { simplifyForMatch } from "./fuzzyMatch.js"; type ArtifactPiece = { name: string; setName: string; slot: string; relicType: string; }; type Character = { name: string; }; type GenshinGameDataContract = typeof gameData & { artifactPieces?: ArtifactPiece[]; stats?: { main?: string[]; mainBySlot?: Record; sub?: string[]; }; aliases?: { stats?: Record; textReplacements?: Record; slotAliases?: Record; setAliases?: Record; pieceAliases?: Record; characterAliases?: Record; }; mainStatsBySlot?: Record; mainStatValueReferences?: Record>; characters?: Character[]; }; export const genshinGameData = gameData as GenshinGameDataContract; export const slotNames = genshinGameData.slots; export const statAliases = genshinGameData.aliases?.stats ?? {}; export const textReplacements = genshinGameData.aliases?.textReplacements ?? {}; export const slotAliases = genshinGameData.aliases?.slotAliases ?? {}; export const setAliases = genshinGameData.aliases?.setAliases ?? {}; export const pieceAliases = genshinGameData.aliases?.pieceAliases ?? {}; export const characterAliases = genshinGameData.aliases?.characterAliases ?? {}; export const knownSets = genshinGameData.artifactSets.map((set) => set.name); export const knownCharacters = (genshinGameData.characters ?? []).map((character) => character.name); export const sourceVersion = genshinGameData.sourceVersion ?? "unknown"; export const dataGeneratedAt = (genshinGameData as { generatedAt?: string }).generatedAt ?? ""; export const fixedMainStatBySlot: Record = { "Flower of Life": "HP", "Plume of Death": "ATK", }; const bundledMainStats = genshinGameData.stats?.main ?? genshinGameData.mainStats; const bundledSubstats = genshinGameData.stats?.sub ?? genshinGameData.substats; export const mainStatsBySlot: Record = genshinGameData.stats?.mainBySlot ?? genshinGameData.mainStatsBySlot ?? { "Flower of Life": ["HP"], "Plume of Death": ["ATK"], "Sands of Eon": ["HP%", "ATK%", "DEF%", "Energy Recharge", "Elemental Mastery"], "Goblet of Eonothem": [ "HP%", "ATK%", "DEF%", "Elemental Mastery", "Hydro DMG Bonus", "Pyro DMG Bonus", "Electro DMG Bonus", "Cryo DMG Bonus", "Dendro DMG Bonus", "Anemo DMG Bonus", "Geo DMG Bonus", "Physical DMG Bonus", ], "Circlet of Logos": ["HP%", "ATK%", "DEF%", "Elemental Mastery", "CRIT Rate", "CRIT DMG", "Healing Bonus"], }; const fallbackArtifactPieces = genshinGameData.artifactSets.flatMap((artifactSet) => artifactSet.pieces.map((piece) => ({ name: piece.name, setName: artifactSet.name, slot: slotFromRelicType(piece.relicType), relicType: piece.relicType, })), ); export const artifactPieces: ArtifactPiece[] = (genshinGameData.artifactPieces ?? fallbackArtifactPieces) .filter((piece) => piece.name && piece.setName && piece.slot); export const pieceToSet = new Map(artifactPieces.map((piece) => [piece.name, piece.setName])); export const pieceToSlot = new Map(artifactPieces.map((piece) => [piece.name, piece.slot])); export const knownPieceNames = artifactPieces.map((piece) => piece.name); export const globalMainStats = unique([ ...bundledMainStats, ...Object.keys(statAliases), ]); export const globalSubstats = unique([ ...bundledSubstats, ...Object.keys(statAliases), ]); export const mainStatValueReferences = genshinGameData.mainStatValueReferences ?? {}; export function allowedMainStatsForSlot(slot: string) { return mainStatsBySlot[slot] ?? globalMainStats; } export function canonicalStatName(raw: string) { return statAliases[raw] ?? raw; } export function normalizeSlotAlias(raw: string) { const simplified = simplifyForMatch(raw); const aliasMatch = Object.entries(slotAliases).find(([alias]) => simplifyForMatch(alias) === simplified); if (aliasMatch) return aliasMatch[1]; const direct = slotNames.find((slot) => simplifyForMatch(slot) === simplified); return direct ?? ""; } export function normalizeSetAlias(raw: string) { const simplified = simplifyForMatch(raw); const aliasMatch = Object.entries(setAliases).find(([alias]) => simplifyForMatch(alias) === simplified); if (aliasMatch) return aliasMatch[1]; const direct = knownSets.find((set) => simplifyForMatch(set) === simplified); return direct ?? ""; } export function normalizePieceAlias(raw: string) { const simplified = simplifyForMatch(raw); const aliasMatch = Object.entries(pieceAliases).find(([alias]) => simplifyForMatch(alias) === simplified); if (aliasMatch) return aliasMatch[1]; const direct = knownPieceNames.find((piece) => simplifyForMatch(piece) === simplified); return direct ?? ""; } export function normalizeCharacterAlias(raw: string) { const simplified = simplifyForMatch(raw); const aliasMatch = Object.entries(characterAliases).find(([alias]) => simplifyForMatch(alias) === simplified); if (aliasMatch) return aliasMatch[1]; const direct = knownCharacters.find((character) => simplifyForMatch(character) === simplified); return direct ?? ""; } function unique(values: string[]) { return [...new Set(values)]; } function slotFromRelicType(relicType: string) { switch (relicType) { case "EQUIP_BRACER": return "Flower of Life"; case "EQUIP_NECKLACE": return "Plume of Death"; case "EQUIP_SHOES": return "Sands of Eon"; case "EQUIP_RING": return "Goblet of Eonothem"; case "EQUIP_DRESS": return "Circlet of Logos"; default: return ""; } }