Files
heros_web/scripts/verify-camp-reward-data.mjs
2026-07-05 08:23:44 +09:00

75 lines
2.6 KiB
JavaScript

import { readFileSync } from 'node:fs';
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));
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 ${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],
index,
offset: match.index ?? 0
}));
}
function validateItemRewards(body, index, offset) {
const entries = [...body.matchAll(/'((?:\\'|[^'])*)'/g)].map((match) => match[1].replace(/\\'/g, "'"));
if (entries.length === 0) {
return;
}
const labels = new Set();
entries.forEach((reward, rewardIndex) => {
const parsed = parseRewardLabel(reward);
if (!parsed.label) {
errors.push(`${sourcePath}:${lineNumber(offset)} itemRewards[${index}][${rewardIndex}] has an empty parsed label`);
}
if (!Number.isInteger(parsed.amount) || parsed.amount < 1) {
errors.push(`${sourcePath}:${lineNumber(offset)} itemRewards[${index}][${rewardIndex}] has invalid amount ${parsed.amount}`);
}
if (labels.has(parsed.label)) {
errors.push(`${sourcePath}:${lineNumber(offset)} itemRewards[${index}] has duplicate label "${parsed.label}"`);
}
labels.add(parsed.label);
});
}
function validateNumericProperties(propertyName, { min, max }) {
const pattern = new RegExp(`${propertyName}:\\s*(-?\\d+)`, 'g');
for (const match of source.matchAll(pattern)) {
const value = Number(match[1]);
if (!Number.isInteger(value) || value < min || value > max) {
errors.push(`${sourcePath}:${lineNumber(match.index ?? 0)} ${propertyName} has out-of-range value ${value}`);
}
}
}
function parseRewardLabel(reward) {
const normalized = String(reward).replace(/\s+/g, ' ').trim();
const match = normalized.match(/^(.*?)(?:\s*[+xX]?\s*(\d+))$/);
if (!match) {
return { label: normalized, amount: 1 };
}
return {
label: match[1].trim() || normalized,
amount: Number(match[2] ?? 1)
};
}
function lineNumber(offset) {
return source.slice(0, offset).split(/\r?\n/).length;
}