Validate camp battle id references

This commit is contained in:
2026-07-05 15:48:48 +09:00
parent ea9a1ca795
commit 8b6b8cdf54

View File

@@ -1,28 +1,42 @@
import { readFileSync } from 'node:fs';
import { createServer } from 'vite';
const sourcePath = 'src/game/scenes/CampScene.ts';
const source = readFileSync(sourcePath, 'utf8');
const errors = [];
const itemRewardArrays = collectItemRewardArrays(source);
itemRewardArrays.forEach(({ body, index, offset }) => validateItemRewards(body, index, offset));
const campBattleIdKeys = collectCampBattleIdKeys(source);
const dialogueEvents = collectCampEvents(source, 'campDialogues');
const visitEvents = collectCampEvents(source, 'campVisits');
validateCampEventCollection(dialogueEvents, 'campDialogues');
validateCampEventCollection(visitEvents, 'campVisits');
validateNumericProperties('gold', { min: 1, max: 20000 });
validateNumericProperties('bondExp', { min: 0, max: 200 });
validateNumericProperties('rewardExp', { min: 1, max: 200 });
const server = await createServer({
logLevel: 'error',
server: { middlewareMode: true },
appType: 'custom'
});
if (errors.length > 0) {
throw new Error(`Camp reward data verification failed:\n${errors.map((error) => `- ${error}`).join('\n')}`);
try {
const { battleScenarios, defaultBattleScenario } = await server.ssrLoadModule('/src/game/data/battles.ts');
const itemRewardArrays = collectItemRewardArrays(source);
itemRewardArrays.forEach(({ body, index, offset }) => validateItemRewards(body, index, offset));
const campBattleIdEntries = collectCampBattleIdEntries(source, defaultBattleScenario.id);
const campBattleIdKeys = new Set(campBattleIdEntries.map((entry) => entry.key));
validateCampBattleIdEntries(campBattleIdEntries, battleScenarios);
const dialogueEvents = collectCampEvents(source, 'campDialogues');
const visitEvents = collectCampEvents(source, 'campVisits');
validateCampEventCollection(dialogueEvents, 'campDialogues', campBattleIdKeys);
validateCampEventCollection(visitEvents, 'campVisits', campBattleIdKeys);
validateNumericProperties('gold', { min: 1, max: 20000 });
validateNumericProperties('bondExp', { min: 0, max: 200 });
validateNumericProperties('rewardExp', { min: 1, max: 200 });
if (errors.length > 0) {
throw new Error(`Camp reward data verification failed:\n${errors.map((error) => `- ${error}`).join('\n')}`);
}
console.log(
`Verified ${dialogueEvents.length} camp dialogues, ${visitEvents.length} camp visits, ${campBattleIdEntries.length} camp battle ids, ${itemRewardArrays.length} camp item reward lists, and camp reward numeric fields.`
);
} finally {
await server.close();
}
console.log(
`Verified ${dialogueEvents.length} camp dialogues, ${visitEvents.length} camp visits, ${itemRewardArrays.length} camp item reward lists, and camp reward numeric fields.`
);
function collectItemRewardArrays(text) {
return [...text.matchAll(/itemRewards:\s*\[([\s\S]*?)\]/g)].map((match, index) => ({
body: match[1],
@@ -63,9 +77,52 @@ function validateNumericProperties(propertyName, { min, max }) {
}
}
function collectCampBattleIdKeys(text) {
function collectCampBattleIdEntries(text, defaultBattleScenarioId) {
const objectSource = extractConstObject(text, 'campBattleIds');
return new Set([...objectSource.matchAll(/^\s*(\w+):/gm)].map((match) => match[1]));
const objectOffset = text.indexOf(objectSource);
return [...objectSource.matchAll(/^\s*(\w+):\s*([^,\n]+)/gm)].map((match) => {
const expression = match[2].trim();
return {
key: match[1],
expression,
value: parseCampBattleIdExpression(expression, defaultBattleScenarioId),
offset: objectOffset + (match.index ?? 0)
};
});
}
function parseCampBattleIdExpression(expression, defaultBattleScenarioId) {
const stringMatch = /^'([^']+)'$/.exec(expression);
if (stringMatch) {
return stringMatch[1];
}
if (expression === 'defaultBattleScenario.id') {
return defaultBattleScenarioId;
}
return '';
}
function validateCampBattleIdEntries(entries, battleScenarios) {
const knownBattleIds = new Set(Object.keys(battleScenarios));
const seenBattleIds = new Map();
entries.forEach((entry) => {
const context = `campBattleIds.${entry.key}`;
if (!entry.value) {
errors.push(`${sourcePath}:${lineNumber(entry.offset)} ${context} uses unsupported expression "${entry.expression}"`);
return;
}
if (!knownBattleIds.has(entry.value)) {
errors.push(`${sourcePath}:${lineNumber(entry.offset)} ${context} references unknown battle id "${entry.value}"`);
}
if (seenBattleIds.has(entry.value)) {
errors.push(
`${sourcePath}:${lineNumber(entry.offset)} ${context} duplicates battle id "${entry.value}" first used by campBattleIds.${seenBattleIds.get(
entry.value
)}`
);
}
seenBattleIds.set(entry.value, entry.key);
});
}
function collectCampEvents(text, arrayName) {
@@ -76,7 +133,7 @@ function collectCampEvents(text, arrayName) {
}));
}
function validateCampEventCollection(events, collectionName) {
function validateCampEventCollection(events, collectionName, campBattleIdKeys) {
const seenIds = new Map();
if (events.length === 0) {
errors.push(`${sourcePath}: ${collectionName} has no entries`);