Hopefully fixed extraneous reset calls and call timing

This commit is contained in:
Bertie690 2025-05-17 21:45:15 -04:00
parent 5532432085
commit a42b7ed9f7
6 changed files with 231 additions and 148 deletions

View File

@ -86,7 +86,7 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
/**
* Wrapper function to handle the actual "switching out" of Pokemon.
* @param switchOutTarget - The {@linkcode Pokemon} (player or enemy) attempting to switch out.
* @param switchOutTarget - The {@linkcode Pokemon} (player or enemy) to be switched switch out.
*/
protected doSwitch(switchOutTarget: Pokemon): void {
if (switchOutTarget instanceof PlayerPokemon) {

View File

@ -7771,8 +7771,9 @@ const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target
const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined;
const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => {
const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField());
const player = user.isPlayer();
const otherPartyIndices = globalScene.getBackupPartyMemberIndices(player, !player ? (user as EnemyPokemon).trainerSlot : undefined)
return otherPartyIndices.length > 0;
};
const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(PokemonType.GHOST);

View File

@ -5034,6 +5034,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
for (const tag of source.summonData.tags) {
// Skip non-Baton Passable tags (or telekinesis for mega gengar; cf. https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move))
if (
!tag.isBatonPassable ||
(tag.tagType === BattlerTagType.TELEKINESIS &&
@ -6314,20 +6315,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param destroy - Whether to destroy this Pokemon once it leaves the field; default `false`
* @remarks
* This **SHOULD NOT** be called when a `SummonPhase` or `SwitchSummonPhase` is already being added,
* which can lead to premature resetting of {@klinkcode turnData} and {@linkcode summonData}.
* which can lead to premature resetting of {@linkcode turnData} and {@linkcode summonData}.
*/
leaveField(clearEffects = true, hideInfo = true, destroy = false) {
console.log(`leaveField called on Pokemon ${this.name}`)
this.resetSprite();
this.resetTurnData();
globalScene
.getField(true)
.filter(p => p !== this)
.getField(true)
.filter(p => p !== this)
.forEach(p => p.removeTagsBySourceId(this.id));
if (clearEffects) {
this.destroySubstitute();
this.resetSummonData();
this.resetTurnData();
}
if (hideInfo) {
this.hideInfo();

View File

@ -61,8 +61,7 @@ export class FaintPhase extends PokemonPhase {
faintPokemon.getTag(BattlerTagType.GRUDGE)?.lapse(faintPokemon, BattlerTagLapseType.CUSTOM, this.source);
}
faintPokemon.resetSummonData();
// Check for reviver seed
if (!this.preventInstantRevive) {
const instantReviveModifier = globalScene.applyModifier(
PokemonInstantReviveModifier,
@ -71,6 +70,7 @@ export class FaintPhase extends PokemonPhase {
) as PokemonInstantReviveModifier;
if (instantReviveModifier) {
faintPokemon.resetSummonData();
faintPokemon.loseHeldItem(instantReviveModifier);
globalScene.updateModifiers(this.player);
return this.end();
@ -179,11 +179,11 @@ export class FaintPhase extends PokemonPhase {
} else {
globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex));
if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) {
const hasReservePartyMember = !!globalScene
.getEnemyParty()
.filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot)
.length;
if (hasReservePartyMember) {
const reservePartyIndices = globalScene.getBackupPartyMemberIndices(
false,
(pokemon as EnemyPokemon).trainerSlot,
);
if (reservePartyIndices.length) {
globalScene.pushPhase(new SwitchSummonPhase(SwitchType.SWITCH, this.fieldIndex, -1, false, false));
}
}
@ -217,6 +217,7 @@ export class FaintPhase extends PokemonPhase {
globalScene.addFaintedEnemyScore(pokemon as EnemyPokemon);
globalScene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon);
}
// TODO: Do we need to leave the field here & now as opposed to during `switchSummonPhase`?
pokemon.leaveField();
this.end();
},

View File

@ -111,7 +111,6 @@ export class SwitchSummonPhase extends SummonPhase {
scale: 0.5,
onComplete: () => {
globalScene.time.delayedCall(750, () => this.switchAndSummon());
lastPokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
},
});
}
@ -162,9 +161,12 @@ export class SwitchSummonPhase extends SummonPhase {
}
}
// Swap around the 2 pokemon's party positions and play an animation to send in the new pokemon.
party[this.slotIndex] = this.lastPokemon;
party[this.fieldIndex] = switchedInPokemon;
const showTextAndSummon = () => {
// TODO: Should this remove the info container?
this.lastPokemon.leaveField(![SwitchType.BATON_PASS, SwitchType.SHED_TAIL].includes(this.switchType), false);
globalScene.ui.showText(
this.player
? i18next.t("battle:playerGo", {
@ -209,25 +211,30 @@ export class SwitchSummonPhase extends SummonPhase {
const pokemon = this.getPokemon();
// If not switching at start of battle, reset turn counts and temp data on the newly sent in Pokemon
// Needed as we increment turn counters in `TurnEndPhase`.
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
// No need to reset turn/summon data for initial switch
// (since both get initialized to an empty object on object creation)
this.lastPokemon.resetTurnData();
this.lastPokemon.resetSummonData();
pokemon.tempSummonData.turnCount--;
pokemon.tempSummonData.waveTurnCount--;
pokemon.turnData.switchedInThisTurn = true;
}
// Baton Pass over any eligible effects or substitutes before resetting the last pokemon's temporary data.
if (this.switchType === SwitchType.BATON_PASS) {
pokemon.transferSummon(this.lastPokemon);
this.lastPokemon.resetTurnData();
this.lastPokemon.resetSummonData();
} else if (this.switchType === SwitchType.SHED_TAIL) {
const subTag = this.lastPokemon.getTag(SubstituteTag);
if (subTag) {
pokemon.summonData.tags.push(subTag);
}
}
// If not switching at start of battle, reset turn counts and temp data.
// Needed as we increment turn counters in `TurnEndPhase`.
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
pokemon.tempSummonData.turnCount--;
pokemon.tempSummonData.waveTurnCount--;
pokemon.turnData.switchedInThisTurn = true;
// No need to reset turn/summon data for initial switch
//(since both get initialized to an empty object on object creation)
pokemon.resetTurnData();
pokemon.resetSummonData();
this.lastPokemon.resetTurnData();
this.lastPokemon.resetSummonData();
}
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);

View File

@ -1,21 +1,157 @@
import Trainer from "#app/field/trainer";
import { PokemonSummonData, PokemonTurnData } from "#app/field/pokemon";
import { Abilities } from "#enums/abilities";
import { BattleType } from "#enums/battle-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe.each<{ name: string; selfMove?: Moves; selfAbility?: Abilities; oppMove?: Moves }>([
{ name: "Self Switch Attack Moves", selfMove: Moves.U_TURN },
{ name: "Target Switch Attack Moves", oppMove: Moves.DRAGON_TAIL },
{ name: "Self Switch Status Moves", selfMove: Moves.TELEPORT },
{ name: "Target Switch Status Moves", oppMove: Moves.WHIRLWIND },
{ name: "Self Switch Abilities", selfAbility: Abilities.EMERGENCY_EXIT },
describe("Manual Switching -", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleStyle("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.moveset(Moves.SPLASH)
.enemyMoveset(Moves.SPLASH)
.battleType(BattleType.TRAINER)
.enemyAbility(Abilities.BALL_FETCH);
});
describe("Player", () => {
it("should only call leaveField once on the switched out pokemon", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloLeaveSpy = vi.spyOn(piloswine, "leaveField");
const mamoLeaveSpy = vi.spyOn(mamoswine, "leaveField");
game.doSwitchPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
expect(piloLeaveSpy).toHaveBeenCalledTimes(1);
expect(mamoLeaveSpy).toHaveBeenCalledTimes(0);
});
it("should only reset summonData/turnData once per switch", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloSummonSpy = vi.spyOn(piloswine, "resetSummonData");
const piloTurnSpy = vi.spyOn(piloswine, "resetTurnData");
const mamoSummonSpy = vi.spyOn(mamoswine, "resetSummonData");
const mamoTurnSpy = vi.spyOn(mamoswine, "resetTurnData");
game.doSwitchPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
expect(piloSummonSpy).toHaveBeenCalledTimes(1);
expect(piloTurnSpy).toHaveBeenCalledTimes(1);
expect(mamoSummonSpy).toHaveBeenCalledTimes(1);
expect(mamoTurnSpy).toHaveBeenCalledTimes(2); // once from switching, once at turn start
});
it("should not reset battleData/waveData upon switching", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloWaveSpy = vi.spyOn(piloswine, "resetWaveData");
const piloBattleWaveSpy = vi.spyOn(piloswine, "resetBattleAndWaveData");
const mamoWaveSpy = vi.spyOn(mamoswine, "resetWaveData");
const mamoBattleWaveSpy = vi.spyOn(mamoswine, "resetBattleAndWaveData");
game.doSwitchPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
expect(piloWaveSpy).toHaveBeenCalledTimes(0);
expect(piloBattleWaveSpy).toHaveBeenCalledTimes(0);
expect(mamoWaveSpy).toHaveBeenCalledTimes(0);
expect(mamoBattleWaveSpy).toHaveBeenCalledTimes(0);
});
});
describe("Enemy", () => {
it("should only call leaveField once on the switched out pokemon", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [enemy1, enemy2] = game.scene.getEnemyParty();
const enemy1LeaveSpy = vi.spyOn(enemy1, "leaveField");
const enemy2LeaveSpy = vi.spyOn(enemy2, "leaveField");
game.move.select(Moves.SPLASH);
game.forceEnemyToSwitch();
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy1LeaveSpy).toHaveBeenCalledTimes(1);
expect(enemy2LeaveSpy).toHaveBeenCalledTimes(0);
});
it("should only reset summonData/turnData once per switch", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [enemy1, enemy2] = game.scene.getEnemyParty();
const enemy1SummonSpy = vi.spyOn(enemy1, "resetSummonData");
const enemy1TurnSpy = vi.spyOn(enemy1, "resetTurnData");
const enemy2SummonSpy = vi.spyOn(enemy2, "resetSummonData");
const enemy2TurnSpy = vi.spyOn(enemy2, "resetTurnData");
game.move.select(Moves.SPLASH);
game.forceEnemyToSwitch();
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy1SummonSpy).toHaveBeenCalledTimes(1);
expect(enemy1TurnSpy).toHaveBeenCalledTimes(1);
expect(enemy2SummonSpy).toHaveBeenCalledTimes(1);
expect(enemy2TurnSpy).toHaveBeenCalledTimes(2); // once from switching, once at turn start
});
it("should not reset battleData/waveData upon switching", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [enemy1, enemy2] = game.scene.getEnemyParty();
const enemy1WaveSpy = vi.spyOn(enemy1, "resetWaveData");
const enemy1BattleWaveSpy = vi.spyOn(enemy1, "resetBattleAndWaveData");
const enemy2WaveSpy = vi.spyOn(enemy2, "resetWaveData");
const enemy2BattleWaveSpy = vi.spyOn(enemy2, "resetBattleAndWaveData");
game.move.select(Moves.SPLASH);
game.forceEnemyToSwitch();
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy1WaveSpy).toHaveBeenCalledTimes(0);
expect(enemy1BattleWaveSpy).toHaveBeenCalledTimes(0);
expect(enemy2WaveSpy).toHaveBeenCalledTimes(0);
expect(enemy2BattleWaveSpy).toHaveBeenCalledTimes(0);
});
});
});
describe.each<{ name: string; playerMove?: Moves; playerAbility?: Abilities; enemyMove?: Moves }>([
{ name: "Self Switch Attack Moves", playerMove: Moves.U_TURN },
{ name: "Target Switch Attack Moves", enemyMove: Moves.DRAGON_TAIL },
{ name: "Self Switch Status Moves", playerMove: Moves.TELEPORT },
{ name: "Target Switch Status Moves", enemyMove: Moves.WHIRLWIND },
{ name: "Self Switch Abilities", playerAbility: Abilities.EMERGENCY_EXIT, enemyMove: Moves.BRAVE_BIRD },
/* { name: "Fainting", playerMove: Moves.EXPLOSION }, */ // TODO: This calls it twice...
])(
"Switch Outs - $name - ",
({ selfMove = Moves.SPLASH, selfAbility = Abilities.BALL_FETCH, oppMove = Moves.SPLASH }) => {
"Mid-Battle Switch Outs - $name - ",
({ playerMove = Moves.SPLASH, playerAbility = Abilities.BALL_FETCH, enemyMove = Moves.SPLASH }) => {
let phaserGame: Phaser.Game;
let game: GameManager;
@ -32,136 +168,73 @@ describe.each<{ name: string; selfMove?: Moves; selfAbility?: Abilities; oppMove
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset(playerMove)
.ability(playerAbility)
.battleStyle("single")
.disableCrits()
.enemyLevel(100)
.battleType(BattleType.TRAINER)
.passiveAbility(Abilities.STURDY)
.enemySpecies(Species.MAGIKARP)
.enemyMoveset(enemyMove)
.enemyAbility(Abilities.BALL_FETCH)
.enemyPassiveAbility(Abilities.NO_GUARD);
});
describe("Player -", () => {
beforeEach(() => {
game.override.moveset(oppMove).ability(selfAbility).enemyMoveset(selfMove).enemyAbility(Abilities.BALL_FETCH);
});
it("should only call leaveField once on the switched out pokemon", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
it("should only call leaveField once on the switched out pokemon", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloLeaveSpy = vi.spyOn(piloswine, "leaveField");
const mamoLeaveSpy = vi.spyOn(mamoswine, "leaveField");
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloLeaveSpy = vi.spyOn(piloswine, "leaveField");
const mamoLeaveSpy = vi.spyOn(mamoswine, "leaveField");
game.move.select(playerMove);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(selfMove);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("BerryPhase", false);
expect(piloLeaveSpy).toHaveBeenCalledTimes(1);
expect(mamoLeaveSpy).toHaveBeenCalledTimes(0);
});
it("should only reset summonData/turnData once per switch", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloSummonSpy = vi.spyOn(piloswine, "resetSummonData");
const piloTurnSpy = vi.spyOn(piloswine, "resetTurnData");
const mamoSummonSpy = vi.spyOn(mamoswine, "resetSummonData");
const mamoTurnSpy = vi.spyOn(mamoswine, "resetTurnData");
game.move.select(selfMove);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("BerryPhase", false);
expect(piloSummonSpy).toHaveBeenCalledTimes(1);
expect(piloTurnSpy).toHaveBeenCalledTimes(1);
expect(mamoSummonSpy).toHaveBeenCalledTimes(1);
expect(mamoTurnSpy).toHaveBeenCalledTimes(1);
});
it("should not reset battleData/waveData upon switching", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloWaveSpy = vi.spyOn(piloswine, "resetWaveData");
const piloBattleWaveSpy = vi.spyOn(piloswine, "resetBattleAndWaveData");
const mamoWaveSpy = vi.spyOn(mamoswine, "resetWaveData");
const mamoBattleWaveSpy = vi.spyOn(mamoswine, "resetBattleAndWaveData");
game.move.select(selfMove);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
expect(piloWaveSpy).toHaveBeenCalledTimes(0);
expect(piloBattleWaveSpy).toHaveBeenCalledTimes(0);
expect(mamoWaveSpy).toHaveBeenCalledTimes(0);
expect(mamoBattleWaveSpy).toHaveBeenCalledTimes(0);
});
expect(piloLeaveSpy).toHaveBeenCalledTimes(1);
expect(mamoLeaveSpy).toHaveBeenCalledTimes(0);
});
describe("Enemy - ", () => {
beforeEach(() => {
game.override
.enemyMoveset(oppMove)
.enemyAbility(selfAbility)
.moveset(selfMove)
.ability(Abilities.BALL_FETCH)
.battleType(BattleType.TRAINER);
it("should only reset summonData/turnData once per switch", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
// prevent natural trainer switches
vi.spyOn(Trainer.prototype, "getPartyMemberMatchupScores").mockReturnValue([
[100, 1],
[100, 1],
]);
});
const [piloswine, mamoswine] = game.scene.getPlayerParty();
piloswine.addTag(BattlerTagType.AQUA_RING, 999); // give piloswine a tag to ensure we know if summonData got reset
const piloSummonSpy = vi.spyOn(piloswine, "resetSummonData");
const piloTurnSpy = vi.spyOn(piloswine, "resetTurnData");
const mamoSummonSpy = vi.spyOn(mamoswine, "resetSummonData");
const mamoTurnSpy = vi.spyOn(mamoswine, "resetTurnData");
it("should only call leaveField once on the switched out pokemon", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
game.move.select(playerMove);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
const [enemy1, enemy2] = game.scene.getEnemyParty();
const enemy1LeaveSpy = vi.spyOn(enemy1, "leaveField");
const enemy2LeaveSpy = vi.spyOn(enemy2, "leaveField");
expect(piloSummonSpy).toHaveBeenCalledTimes(1);
expect(piloTurnSpy).toHaveBeenCalledTimes(1);
expect(mamoSummonSpy).toHaveBeenCalledTimes(1);
expect(mamoTurnSpy).toHaveBeenCalledTimes(1);
expect(piloswine.summonData).toEqual(new PokemonSummonData());
expect(piloswine.turnData).toEqual(new PokemonTurnData());
});
game.move.select(selfMove);
await game.phaseInterceptor.to("BerryPhase", false);
it("should not reset battleData/waveData upon switching", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
expect(enemy1LeaveSpy).toHaveBeenCalledTimes(1);
expect(enemy2LeaveSpy).toHaveBeenCalledTimes(0);
});
const [piloswine, mamoswine] = game.scene.getPlayerParty();
const piloWaveSpy = vi.spyOn(piloswine, "resetWaveData");
const piloBattleWaveSpy = vi.spyOn(piloswine, "resetBattleAndWaveData");
const mamoWaveSpy = vi.spyOn(mamoswine, "resetWaveData");
const mamoBattleWaveSpy = vi.spyOn(mamoswine, "resetBattleAndWaveData");
it("should only reset summonData/turnData once per switch", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
game.move.select(playerMove);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
const [enemy1, enemy2] = game.scene.getEnemyParty();
const enemy1SummonSpy = vi.spyOn(enemy1, "resetSummonData");
const enemy1TurnSpy = vi.spyOn(enemy1, "resetTurnData");
const enemy2SummonSpy = vi.spyOn(enemy2, "resetSummonData");
const enemy2TurnSpy = vi.spyOn(enemy2, "resetTurnData");
game.move.select(selfMove);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemy1SummonSpy).toHaveBeenCalledTimes(1);
expect(enemy1TurnSpy).toHaveBeenCalledTimes(1);
expect(enemy2SummonSpy).toHaveBeenCalledTimes(1);
expect(enemy2TurnSpy).toHaveBeenCalledTimes(1);
});
it("should not reset battleData/waveData upon switching", async () => {
await game.classicMode.startBattle([Species.PILOSWINE, Species.MAMOSWINE]);
const [enemy1, enemy2] = game.scene.getEnemyParty();
const enemy1WaveSpy = vi.spyOn(enemy1, "resetWaveData");
const enemy1BattleWaveSpy = vi.spyOn(enemy1, "resetBattleAndWaveData");
const enemy2WaveSpy = vi.spyOn(enemy2, "resetWaveData");
const enemy2BattleWaveSpy = vi.spyOn(enemy2, "resetBattleAndWaveData");
game.move.select(selfMove);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy1WaveSpy).toHaveBeenCalledTimes(0);
expect(enemy1BattleWaveSpy).toHaveBeenCalledTimes(0);
expect(enemy2WaveSpy).toHaveBeenCalledTimes(0);
expect(enemy2BattleWaveSpy).toHaveBeenCalledTimes(0);
});
expect(piloWaveSpy).toHaveBeenCalledTimes(0);
expect(piloBattleWaveSpy).toHaveBeenCalledTimes(0);
expect(mamoWaveSpy).toHaveBeenCalledTimes(0);
expect(mamoBattleWaveSpy).toHaveBeenCalledTimes(0);
});
},
);