diff --git a/scripts/verify-battle-save-normalization.mjs b/scripts/verify-battle-save-normalization.mjs index 2bfe0a3..9224d91 100644 --- a/scripts/verify-battle-save-normalization.mjs +++ b/scripts/verify-battle-save-normalization.mjs @@ -72,6 +72,12 @@ try { ['duplicate unit id', { units: [validState.units[0], { ...validState.units[0], x: 2 }, validState.units[2]] }], ['invalid bonds', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei'] }] }], ['unknown bond unit id', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei', 'ghost-unit'] }] }], + ['invalid bond level', { bonds: [{ ...validState.bonds[0], level: 0 }] }], + ['too high bond level', { bonds: [{ ...validState.bonds[0], level: 101 }] }], + ['invalid bond exp', { bonds: [{ ...validState.bonds[0], exp: -1 }] }], + ['invalid bond exp threshold', { bonds: [{ ...validState.bonds[0], level: 1, exp: 100 }] }], + ['invalid max-level bond exp threshold', { bonds: [{ ...validState.bonds[0], level: 100, exp: 101 }] }], + ['invalid bond battle exp', { bonds: [{ ...validState.bonds[0], battleExp: 1.5 }] }], ['invalid item stock count', { itemStocks: { 'liu-bei': { bean: -1 } } }], ['unknown item stock unit id', { itemStocks: { 'ghost-unit': { bean: 1 } } }], ['unknown item stock item id', { itemStocks: { 'liu-bei': { phantomItem: 1 } } }], diff --git a/src/game/state/battleSaveState.ts b/src/game/state/battleSaveState.ts index ea2f6ff..e784af1 100644 --- a/src/game/state/battleSaveState.ts +++ b/src/game/state/battleSaveState.ts @@ -162,6 +162,10 @@ function isNonNegativeFiniteNumber(value: unknown) { return typeof value === 'number' && Number.isFinite(value) && value >= 0; } +function isNonNegativeInteger(value: unknown) { + return Number.isInteger(value) && Number(value) >= 0; +} + function isFiniteNumber(value: unknown) { return typeof value === 'number' && Number.isFinite(value); } @@ -313,13 +317,22 @@ function isBondArray(value: unknown, options: BattleSaveValidationOptions) { Array.isArray(bond.unitIds) && bond.unitIds.length === 2 && bond.unitIds.every((unitId) => typeof unitId === 'string' && isKnownUnitId(unitId, options)) && - isPositiveInteger(bond.level) && - isNonNegativeFiniteNumber(bond.exp) && - isNonNegativeFiniteNumber(bond.battleExp) + isValidBondProgress(bond.level, bond.exp) && + isNonNegativeInteger(bond.battleExp) ) ); } +function isValidBondProgress(level: unknown, exp: unknown) { + return ( + isPositiveInteger(level) && + Number(level) <= 100 && + Number.isInteger(exp) && + Number(exp) >= 0 && + (Number(level) >= 100 ? Number(exp) <= 100 : Number(exp) < 100) + ); +} + function isOptionalItemStockRecord(value: unknown, options: BattleSaveValidationOptions) { if (value === undefined) { return true;