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