Validate battle save unit references
This commit is contained in:
@@ -15,9 +15,10 @@ try {
|
|||||||
expectedBattleId: 'first-battle-zhuo-commandery',
|
expectedBattleId: 'first-battle-zhuo-commandery',
|
||||||
mapWidth: 12,
|
mapWidth: 12,
|
||||||
mapHeight: 8,
|
mapHeight: 8,
|
||||||
validUnitIds: new Set(['liu-bei'])
|
validUnitIds: new Set(['liu-bei', 'guan-yu', 'rebel-1'])
|
||||||
};
|
};
|
||||||
const validState = createValidBattleSaveState();
|
const validState = createValidBattleSaveState();
|
||||||
|
const patchUnit = (index, patch) => validState.units.map((unit, unitIndex) => (unitIndex === index ? { ...unit, ...patch } : unit));
|
||||||
|
|
||||||
assert(normalizeBattleSaveSlot(0, 3) === 1, 'Expected slot 0 to normalize to slot 1.');
|
assert(normalizeBattleSaveSlot(0, 3) === 1, 'Expected slot 0 to normalize to slot 1.');
|
||||||
assert(normalizeBattleSaveSlot(Number.NaN, 3) === 1, 'Expected NaN slot to normalize to slot 1.');
|
assert(normalizeBattleSaveSlot(Number.NaN, 3) === 1, 'Expected NaN slot to normalize to slot 1.');
|
||||||
@@ -35,19 +36,26 @@ try {
|
|||||||
['invalid activeFaction', { activeFaction: 'neutral' }],
|
['invalid activeFaction', { activeFaction: 'neutral' }],
|
||||||
['invalid rosterTab', { rosterTab: 'neutral' }],
|
['invalid rosterTab', { rosterTab: 'neutral' }],
|
||||||
['invalid actedUnitIds', { actedUnitIds: 'liu-bei' }],
|
['invalid actedUnitIds', { actedUnitIds: 'liu-bei' }],
|
||||||
|
['unknown acted unit id', { actedUnitIds: ['ghost-unit'] }],
|
||||||
['invalid battleLog', { battleLog: 'hit' }],
|
['invalid battleLog', { battleLog: 'hit' }],
|
||||||
['invalid attackIntents', { attackIntents: [{ attackerId: 'liu-bei' }] }],
|
['invalid attackIntents', { attackIntents: [{ attackerId: 'liu-bei' }] }],
|
||||||
|
['unknown attack intent unit id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'ghost-unit' }] }],
|
||||||
['invalid units array', { units: {} }],
|
['invalid units array', { units: {} }],
|
||||||
['invalid unit hp', { units: [{ ...validState.units[0], hp: 99, maxHp: 30 }] }],
|
['invalid unit hp', { units: patchUnit(0, { hp: 99, maxHp: 30 }) }],
|
||||||
['invalid unit x', { units: [{ ...validState.units[0], x: 12 }] }],
|
['invalid unit x', { units: patchUnit(0, { x: 12 }) }],
|
||||||
['invalid unit direction', { units: [{ ...validState.units[0], direction: 'down' }] }],
|
['invalid unit direction', { units: patchUnit(0, { direction: 'down' }) }],
|
||||||
['unknown unit id', { units: [{ ...validState.units[0], id: 'ghost-unit' }] }],
|
['unknown unit id', { units: patchUnit(0, { id: 'ghost-unit' }) }],
|
||||||
['duplicate unit id', { units: [{ ...validState.units[0] }, { ...validState.units[0], x: 2 }] }],
|
['duplicate unit id', { units: [validState.units[0], { ...validState.units[0], x: 2 }, validState.units[2]] }],
|
||||||
['invalid bonds', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei'] }] }],
|
['invalid bonds', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei'] }] }],
|
||||||
|
['unknown bond unit id', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei', 'ghost-unit'] }] }],
|
||||||
['invalid item stock count', { itemStocks: { 'liu-bei': { bean: -1 } } }],
|
['invalid item stock count', { itemStocks: { 'liu-bei': { bean: -1 } } }],
|
||||||
|
['unknown item stock unit id', { itemStocks: { 'ghost-unit': { bean: 1 } } }],
|
||||||
['invalid buff turns', { battleBuffs: [{ ...validState.battleBuffs[0], turns: 0 }] }],
|
['invalid buff turns', { battleBuffs: [{ ...validState.battleBuffs[0], turns: 0 }] }],
|
||||||
|
['unknown buff unit id', { battleBuffs: [{ ...validState.battleBuffs[0], unitId: 'ghost-unit' }] }],
|
||||||
['invalid status kind', { battleStatuses: [{ ...validState.battleStatuses[0], kind: 'poison' }] }],
|
['invalid status kind', { battleStatuses: [{ ...validState.battleStatuses[0], kind: 'poison' }] }],
|
||||||
|
['unknown status unit id', { battleStatuses: [{ ...validState.battleStatuses[0], unitId: 'ghost-unit' }] }],
|
||||||
['invalid stats shape', { battleStats: { 'liu-bei': { damageDealt: 10 } } }],
|
['invalid stats shape', { battleStats: { 'liu-bei': { damageDealt: 10 } } }],
|
||||||
|
['unknown stats unit id', { battleStats: { 'ghost-unit': validState.battleStats['liu-bei'] } }],
|
||||||
['invalid enemy usable keys', { enemyUsableUseKeys: [1] }],
|
['invalid enemy usable keys', { enemyUsableUseKeys: [1] }],
|
||||||
['invalid triggered events', { triggeredBattleEvents: [1] }]
|
['invalid triggered events', { triggeredBattleEvents: [1] }]
|
||||||
];
|
];
|
||||||
@@ -58,7 +66,7 @@ try {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
!isValidBattleSaveState(validState, { ...options, validUnitIds: new Set(['liu-bei', 'guan-yu']) }),
|
!isValidBattleSaveState(validState, { ...options, validUnitIds: new Set(['liu-bei', 'guan-yu', 'rebel-1', 'zhang-fei']) }),
|
||||||
'Expected save missing a current battle unit to be rejected.'
|
'Expected save missing a current battle unit to be rejected.'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -92,6 +100,32 @@ function createValidBattleSaveState() {
|
|||||||
y: 2,
|
y: 2,
|
||||||
direction: 'south',
|
direction: 'south',
|
||||||
equipment: {}
|
equipment: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'guan-yu',
|
||||||
|
level: 2,
|
||||||
|
exp: 5,
|
||||||
|
hp: 32,
|
||||||
|
maxHp: 34,
|
||||||
|
attack: 14,
|
||||||
|
move: 4,
|
||||||
|
x: 2,
|
||||||
|
y: 2,
|
||||||
|
direction: 'south',
|
||||||
|
equipment: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rebel-1',
|
||||||
|
level: 1,
|
||||||
|
exp: 0,
|
||||||
|
hp: 20,
|
||||||
|
maxHp: 20,
|
||||||
|
attack: 8,
|
||||||
|
move: 3,
|
||||||
|
x: 8,
|
||||||
|
y: 4,
|
||||||
|
direction: 'west',
|
||||||
|
equipment: {}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
bonds: [
|
bonds: [
|
||||||
|
|||||||
@@ -116,20 +116,20 @@ export function isValidBattleSaveState(state: unknown, options: BattleSaveValida
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isStringArray(state.actedUnitIds) ||
|
!isUnitIdArray(state.actedUnitIds, options) ||
|
||||||
!isBattleLog(state.battleLog) ||
|
!isBattleLog(state.battleLog) ||
|
||||||
!isAttackIntentArray(state.attackIntents) ||
|
!isAttackIntentArray(state.attackIntents, options) ||
|
||||||
!areSavedBattleUnitsValid(state.units, options) ||
|
!areSavedBattleUnitsValid(state.units, options) ||
|
||||||
!isBondArray(state.bonds)
|
!isBondArray(state.bonds, options)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isOptionalNestedCountRecord(state.itemStocks) ||
|
!isOptionalNestedCountRecord(state.itemStocks, options) ||
|
||||||
!isOptionalBuffArray(state.battleBuffs) ||
|
!isOptionalBuffArray(state.battleBuffs, options) ||
|
||||||
!isOptionalStatusArray(state.battleStatuses) ||
|
!isOptionalStatusArray(state.battleStatuses, options) ||
|
||||||
!isOptionalStatsRecord(state.battleStats) ||
|
!isOptionalStatsRecord(state.battleStats, options) ||
|
||||||
!isOptionalStringArray(state.enemyUsableUseKeys) ||
|
!isOptionalStringArray(state.enemyUsableUseKeys) ||
|
||||||
!isOptionalStringArray(state.triggeredBattleEvents)
|
!isOptionalStringArray(state.triggeredBattleEvents)
|
||||||
) {
|
) {
|
||||||
@@ -171,6 +171,10 @@ function isStringArray(value: unknown) {
|
|||||||
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUnitIdArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
|
return Array.isArray(value) && value.every((entry) => typeof entry === 'string' && isKnownUnitId(entry, options));
|
||||||
|
}
|
||||||
|
|
||||||
function isOptionalStringArray(value: unknown) {
|
function isOptionalStringArray(value: unknown) {
|
||||||
return value === undefined || isStringArray(value);
|
return value === undefined || isStringArray(value);
|
||||||
}
|
}
|
||||||
@@ -179,10 +183,17 @@ function isBattleLog(value: unknown) {
|
|||||||
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAttackIntentArray(value: unknown) {
|
function isAttackIntentArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
return (
|
return (
|
||||||
Array.isArray(value) &&
|
Array.isArray(value) &&
|
||||||
value.every((intent) => isRecord(intent) && typeof intent.attackerId === 'string' && typeof intent.targetId === 'string')
|
value.every(
|
||||||
|
(intent) =>
|
||||||
|
isRecord(intent) &&
|
||||||
|
typeof intent.attackerId === 'string' &&
|
||||||
|
typeof intent.targetId === 'string' &&
|
||||||
|
isKnownUnitId(intent.attackerId, options) &&
|
||||||
|
isKnownUnitId(intent.targetId, options)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +249,7 @@ function isSavedBattleUnitState(value: unknown, options: BattleSaveValidationOpt
|
|||||||
return value.direction === undefined || unitDirections.has(value.direction as UnitDirection);
|
return value.direction === undefined || unitDirections.has(value.direction as UnitDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isBondArray(value: unknown) {
|
function isBondArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
return (
|
return (
|
||||||
Array.isArray(value) &&
|
Array.isArray(value) &&
|
||||||
value.every(
|
value.every(
|
||||||
@@ -247,7 +258,7 @@ function isBondArray(value: unknown) {
|
|||||||
typeof bond.id === 'string' &&
|
typeof bond.id === 'string' &&
|
||||||
Array.isArray(bond.unitIds) &&
|
Array.isArray(bond.unitIds) &&
|
||||||
bond.unitIds.length === 2 &&
|
bond.unitIds.length === 2 &&
|
||||||
bond.unitIds.every((unitId) => typeof unitId === 'string') &&
|
bond.unitIds.every((unitId) => typeof unitId === 'string' && isKnownUnitId(unitId, options)) &&
|
||||||
isPositiveInteger(bond.level) &&
|
isPositiveInteger(bond.level) &&
|
||||||
isNonNegativeFiniteNumber(bond.exp) &&
|
isNonNegativeFiniteNumber(bond.exp) &&
|
||||||
isNonNegativeFiniteNumber(bond.battleExp)
|
isNonNegativeFiniteNumber(bond.battleExp)
|
||||||
@@ -255,7 +266,7 @@ function isBondArray(value: unknown) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptionalNestedCountRecord(value: unknown) {
|
function isOptionalNestedCountRecord(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -264,12 +275,15 @@ function isOptionalNestedCountRecord(value: unknown) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(value).every(
|
return Object.entries(value).every(
|
||||||
(stocks) => isRecord(stocks) && Object.values(stocks).every((count) => Number.isInteger(count) && Number(count) >= 0)
|
([unitId, stocks]) =>
|
||||||
|
isKnownUnitId(unitId, options) &&
|
||||||
|
isRecord(stocks) &&
|
||||||
|
Object.values(stocks).every((count) => Number.isInteger(count) && Number(count) >= 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptionalBuffArray(value: unknown) {
|
function isOptionalBuffArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
return (
|
return (
|
||||||
value === undefined ||
|
value === undefined ||
|
||||||
(Array.isArray(value) &&
|
(Array.isArray(value) &&
|
||||||
@@ -277,6 +291,7 @@ function isOptionalBuffArray(value: unknown) {
|
|||||||
(buff) =>
|
(buff) =>
|
||||||
isRecord(buff) &&
|
isRecord(buff) &&
|
||||||
typeof buff.unitId === 'string' &&
|
typeof buff.unitId === 'string' &&
|
||||||
|
isKnownUnitId(buff.unitId, options) &&
|
||||||
typeof buff.label === 'string' &&
|
typeof buff.label === 'string' &&
|
||||||
isPositiveInteger(buff.turns) &&
|
isPositiveInteger(buff.turns) &&
|
||||||
isFiniteNumber(buff.attackBonus) &&
|
isFiniteNumber(buff.attackBonus) &&
|
||||||
@@ -286,7 +301,7 @@ function isOptionalBuffArray(value: unknown) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptionalStatusArray(value: unknown) {
|
function isOptionalStatusArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
return (
|
return (
|
||||||
value === undefined ||
|
value === undefined ||
|
||||||
(Array.isArray(value) &&
|
(Array.isArray(value) &&
|
||||||
@@ -294,6 +309,7 @@ function isOptionalStatusArray(value: unknown) {
|
|||||||
(status) =>
|
(status) =>
|
||||||
isRecord(status) &&
|
isRecord(status) &&
|
||||||
typeof status.unitId === 'string' &&
|
typeof status.unitId === 'string' &&
|
||||||
|
isKnownUnitId(status.unitId, options) &&
|
||||||
isBattleStatusKind(status.kind) &&
|
isBattleStatusKind(status.kind) &&
|
||||||
typeof status.label === 'string' &&
|
typeof status.label === 'string' &&
|
||||||
isPositiveInteger(status.turns) &&
|
isPositiveInteger(status.turns) &&
|
||||||
@@ -302,7 +318,7 @@ function isOptionalStatusArray(value: unknown) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptionalStatsRecord(value: unknown) {
|
function isOptionalStatsRecord(value: unknown, options: BattleSaveValidationOptions) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -311,8 +327,9 @@ function isOptionalStatsRecord(value: unknown) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(value).every(
|
return Object.entries(value).every(
|
||||||
(stats) =>
|
([unitId, stats]) =>
|
||||||
|
isKnownUnitId(unitId, options) &&
|
||||||
isRecord(stats) &&
|
isRecord(stats) &&
|
||||||
isNonNegativeFiniteNumber(stats.damageDealt) &&
|
isNonNegativeFiniteNumber(stats.damageDealt) &&
|
||||||
isNonNegativeFiniteNumber(stats.damageTaken) &&
|
isNonNegativeFiniteNumber(stats.damageTaken) &&
|
||||||
@@ -321,3 +338,7 @@ function isOptionalStatsRecord(value: unknown) {
|
|||||||
isNonNegativeFiniteNumber(stats.support)
|
isNonNegativeFiniteNumber(stats.support)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isKnownUnitId(unitId: string, options: BattleSaveValidationOptions) {
|
||||||
|
return !options.validUnitIds || options.validUnitIds.has(unitId);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user