const fs = require('node:fs'); const path = require('node:path'); const genshin = require('genshin-db'); const opts = { matchCategories: true, verboseCategories: true, resultLanguage: 'English', queryLanguages: ['English'] }; function asArray(value) { return Array.isArray(value) ? value : []; } const characters = asArray(genshin.characters('names', opts)) .map((entry) => ({ id: entry.id, name: entry.name, rarity: entry.rarity, element: entry.elementText, weapon: entry.weaponText, })) .filter((entry) => entry.name) .sort((a, b) => a.name.localeCompare(b.name)); const artifacts = asArray(genshin.artifacts('names', opts)) .map((entry) => ({ id: entry.id, name: entry.name, rarityList: entry.rarityList ?? [], pieces: [entry.flower, entry.plume, entry.sands, entry.goblet, entry.circlet] .filter(Boolean) .map((piece) => ({ name: piece.name, relicType: piece.relicType })), })) .filter((entry) => entry.name) .sort((a, b) => a.name.localeCompare(b.name)); const artifactPieces = artifacts .flatMap((set) => set.pieces.map((piece) => ({ name: piece.name, setName: set.name, slot: slotFromRelicType(piece.relicType), relicType: piece.relicType, })), ) .sort((a, b) => a.name.localeCompare(b.name)); const slotByPiece = Object.fromEntries(artifactPieces.map((piece) => [piece.name, piece.slot])); const setByPiece = Object.fromEntries(artifactPieces.map((piece) => [piece.name, piece.setName])); const mainStats = [ 'Elemental Mastery', 'Energy Recharge', 'CRIT Rate', 'CRIT DMG', 'Healing Bonus', 'ATK%', 'HP%', 'DEF%', 'ATK', 'HP', 'DEF', 'Hydro DMG Bonus', 'Pyro DMG Bonus', 'Electro DMG Bonus', 'Cryo DMG Bonus', 'Dendro DMG Bonus', 'Anemo DMG Bonus', 'Geo DMG Bonus', 'Physical DMG Bonus', ]; const substats = ['CRIT DMG', 'CRIT Rate', 'Energy Recharge', 'Elemental Mastery', 'ATK', 'ATK%', 'HP', 'HP%', 'DEF', 'DEF%']; const 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 mainStatValueReferences = { 'Flower of Life': [{ stat: 'HP', base: 717, max: 4780 }], 'Plume of Death': [{ stat: 'ATK', base: 47, max: 311 }], 'Sands of Eon': [ { stat: 'HP%', base: 7.0, max: 46.6 }, { stat: 'ATK%', base: 7.0, max: 46.6 }, { stat: 'DEF%', base: 8.7, max: 58.3 }, { stat: 'Energy Recharge', base: 7.8, max: 51.8 }, { stat: 'Elemental Mastery', base: 28, max: 187 }, ], 'Goblet of Eonothem': [ { stat: 'HP%', base: 7.0, max: 46.6 }, { stat: 'ATK%', base: 7.0, max: 46.6 }, { stat: 'DEF%', base: 8.7, max: 58.3 }, { stat: 'Elemental Mastery', base: 28, max: 187 }, { stat: 'Hydro DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Pyro DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Electro DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Cryo DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Dendro DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Anemo DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Geo DMG Bonus', base: 7.0, max: 46.6 }, { stat: 'Physical DMG Bonus', base: 8.7, max: 58.3 }, ], 'Circlet of Logos': [ { stat: 'HP%', base: 7.0, max: 46.6 }, { stat: 'ATK%', base: 7.0, max: 46.6 }, { stat: 'DEF%', base: 8.7, max: 58.3 }, { stat: 'Elemental Mastery', base: 28, max: 187 }, { stat: 'CRIT Rate', base: 4.7, max: 31.1 }, { stat: 'CRIT DMG', base: 9.3, max: 62.2 }, { stat: 'Healing Bonus', base: 5.4, max: 35.9 }, ], }; const data = { schemaVersion: 2, generatedAt: new Date().toISOString(), source: { package: 'genshin-db', version: require('genshin-db/package.json').version, resultLanguage: opts.resultLanguage, }, sourceVersions: { genshinDb: require('genshin-db/package.json').version, resultLanguage: opts.resultLanguage, }, sourceVersion: `genshin-db@${require('genshin-db/package.json').version}`, characters, artifactSets: artifacts, artifactPieces, slotByPiece, setByPiece, slots: ['Flower of Life', 'Plume of Death', 'Sands of Eon', 'Goblet of Eonothem', 'Circlet of Logos'], mainStats, mainStatsBySlot, mainStatValueReferences, substats, stats: { main: mainStats, mainBySlot: mainStatsBySlot, sub: substats, }, aliases: { stats: { 'Crit Damage': 'CRIT DMG', 'Critical Damage': 'CRIT DMG', 'Crit Rate': 'CRIT Rate', 'Critical Rate': 'CRIT Rate', 'Energy Recharge %': 'Energy Recharge', 'Elemental Master': 'Elemental Mastery', 'Elemental Masterie': 'Elemental Mastery', 'Heal Bonus': 'Healing Bonus', }, textReplacements: { 'CIT DMG': 'CRIT DMG', 'CRIT DMG+I': 'CRIT DMG+1', 'CRIT Rate+Z': 'CRIT Rate+2', 'Energv Recharge': 'Energy Recharge', 'Elemental Masterv': 'Elemental Mastery', 'Equipped;': 'Equipped:', }, slotAliases: { 'Sands of Eon Vi': 'Sands of Eon', 'Sands of Eon V': 'Sands of Eon', 'Sands of Eon 2': 'Sands of Eon', 'Flower of Life 2': 'Flower of Life', 'Flower of Lif': 'Flower of Life', 'Goblet of Eonotherm': 'Goblet of Eonothem', 'Goblet of Eonothemn': 'Goblet of Eonothem', 'Circlet of Logas': 'Circlet of Logos', 'Circlet of Loges': 'Circlet of Logos', }, setAliases: { 'Viridescent Venere': 'Viridescent Venerer', 'Maiden Beloved:': 'Maiden Beloved', 'Gladiators Finale': "Gladiator's Finale", }, pieceAliases: { 'A Note in Springs Leich': "A Note in Spring's Leich", 'Viridescent Vencrers Vessel': "Viridescent Venerer's Vessel", 'Holy Crown of the Believer ': 'Holy Crown of the Believer', }, characterAliases: { 'Citlall': 'Citlali', 'Sandrone ': 'Sandrone', 'Qiqi ': 'Qiqi', }, }, uiProfiles: { artifactDetailEn: { language: 'English', supportedResolutions: ['1920x1080', '2560x1440', '3840x2160'], detailPanel: { x: 0.5, y: 0.05, width: 0.45, height: 0.9 }, note: 'Relative profile used as scanner contract; runtime crops may tune offsets from review samples.', }, }, }; const outPath = path.join(process.cwd(), 'src', 'data', 'genshinGameData.json'); fs.mkdirSync(path.dirname(outPath), { recursive: true }); fs.writeFileSync(outPath, JSON.stringify(data, null, 2) + '\n'); console.log(`generated ${outPath}`); console.log(`${characters.length} characters, ${artifacts.length} artifact sets`); console.log(`${artifactPieces.length} artifact pieces, ${mainStats.length} main stats, ${substats.length} substats`); function slotFromRelicType(relicType) { 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 ''; } }