diff --git a/scripts/verify-battle-save-normalization.mjs b/scripts/verify-battle-save-normalization.mjs index 66283f6..137395b 100644 --- a/scripts/verify-battle-save-normalization.mjs +++ b/scripts/verify-battle-save-normalization.mjs @@ -16,6 +16,8 @@ try { mapWidth: 12, mapHeight: 8, validUnitIds: new Set(['liu-bei', 'guan-yu', 'rebel-1']), + validAllyUnitIds: new Set(['liu-bei', 'guan-yu']), + validEnemyUnitIds: new Set(['rebel-1']), validUsableIds: new Set(['roar', 'fireTactic', 'aid', 'bean', 'salve', 'wine']), validItemIds: new Set(['bean', 'salve', 'wine']), validEquipmentItemIds: new Set(['training-sword', 'cloth-armor', 'grain-pouch']), @@ -55,6 +57,8 @@ try { ['unknown attack intent unit id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'ghost-unit' }] }], ['self-targeting attack intent', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'liu-bei' }] }], ['duplicate attack intent attacker id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'rebel-1' }, { attackerId: 'liu-bei', targetId: 'rebel-1' }] }], + ['enemy attack intent attacker', { attackIntents: [{ attackerId: 'rebel-1', targetId: 'liu-bei' }] }], + ['ally attack intent target', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'guan-yu' }] }], ['defeated attack intent attacker', { units: patchUnit(0, { hp: 0 }) }], ['defeated attack intent target', { units: patchUnit(2, { hp: 0 }) }], ['invalid units array', { units: {} }], diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index d200053..8bed02f 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -11351,6 +11351,8 @@ export class BattleScene extends Phaser.Scene { mapWidth: battleMap.width, mapHeight: battleMap.height, validUnitIds: new Set(battleUnits.map((unit) => unit.id)), + validAllyUnitIds: new Set(battleUnits.filter((unit) => unit.faction === 'ally').map((unit) => unit.id)), + validEnemyUnitIds: new Set(battleUnits.filter((unit) => unit.faction === 'enemy').map((unit) => unit.id)), validUsableIds: new Set(Object.keys(usableCatalog)), validItemIds: new Set( Object.values(usableCatalog) diff --git a/src/game/state/battleSaveState.ts b/src/game/state/battleSaveState.ts index 41e5e74..e7af56c 100644 --- a/src/game/state/battleSaveState.ts +++ b/src/game/state/battleSaveState.ts @@ -73,6 +73,8 @@ type BattleSaveValidationOptions = { mapWidth?: number; mapHeight?: number; validUnitIds?: ReadonlySet; + validAllyUnitIds?: ReadonlySet; + validEnemyUnitIds?: ReadonlySet; validUsableIds?: ReadonlySet; validItemIds?: ReadonlySet; validEquipmentItemIds?: ReadonlySet; @@ -159,7 +161,7 @@ export function isValidBattleSaveState(state: unknown, options: BattleSaveValida return false; } - if (!areAttackIntentsTargetingLiveUnits(attackIntents, units)) { + if (!areAttackIntentsTargetingLiveUnits(attackIntents, units, options)) { return false; } @@ -281,9 +283,19 @@ function areSavedBattleUnitsValid(units: unknown[], options: BattleSaveValidatio return [...seenUnitIds].every((unitId) => options.validUnitIds?.has(unitId)); } -function areAttackIntentsTargetingLiveUnits(attackIntents: BattleSaveAttackIntent[], units: SavedBattleUnitState[]) { +function areAttackIntentsTargetingLiveUnits( + attackIntents: BattleSaveAttackIntent[], + units: SavedBattleUnitState[], + options: BattleSaveValidationOptions +) { const liveUnitIds = new Set(units.filter((unit) => unit.hp > 0).map((unit) => unit.id)); - return attackIntents.every((intent) => liveUnitIds.has(intent.attackerId) && liveUnitIds.has(intent.targetId)); + return attackIntents.every( + (intent) => + liveUnitIds.has(intent.attackerId) && + liveUnitIds.has(intent.targetId) && + (!options.validAllyUnitIds || options.validAllyUnitIds.has(intent.attackerId)) && + (!options.validEnemyUnitIds || options.validEnemyUnitIds.has(intent.targetId)) + ); } function areEffectsTargetingLiveUnits(