Files
genshin-assistant/scripts/generate-genshin-data.cjs
T
AzuTear e76d88e0c7 chore: initialize repository baseline
Import the existing Electron + React + TypeScript app as the version-control
baseline before the scanner rework (C# input/capture sidecar, resolution-anchored
layout profiles, OCR preprocessing, eval harness, rescan-merge, GOOD interop).

Housekeeping in this commit:
- Remove orphaned temp_inputhelper_block.ts (duplicate of the input-helper script).
- Ignore .claude/scheduled_tasks.lock local session state.
- Add .gitattributes to normalize line endings (LF in repo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 20:31:01 +02:00

234 lines
7.3 KiB
JavaScript

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 '';
}
}