e76d88e0c7
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>
234 lines
7.3 KiB
JavaScript
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 '';
|
|
}
|
|
}
|