[Test] Add Tests for Zen Mode Ability (#1978)

* added tests for zen mode - change form in battle

* added tests to run some battle against trainer/rival & boss

* added a test with a method to kill a pokemon

* added an override in the clock mocked to reduce the time of fainting pokemon and thus reducing test time from 5s to less than 1s

* added some more tests + doAttack, doKillOpponents, toNextWave, toNextTurn helper

* added some more tests + doAttack, doKillOpponents, toNextWave, toNextTurn helper + fix some tests
This commit is contained in:
Greenlamp2 2024-06-10 16:10:23 +02:00 committed by GitHub
parent 32d222c9cd
commit e29c08ba1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 516 additions and 41 deletions

View File

@ -1079,6 +1079,10 @@ export class NextEncounterPhase extends EncounterPhase {
super(scene); super(scene);
} }
start() {
super.start();
}
doEncounter(): void { doEncounter(): void {
this.scene.playBgm(undefined, true); this.scene.playBgm(undefined, true);
@ -1497,6 +1501,10 @@ export class SwitchSummonPhase extends SummonPhase {
this.batonPass = batonPass; this.batonPass = batonPass;
} }
start(): void {
super.start();
}
preSummon(): void { preSummon(): void {
if (!this.player) { if (!this.player) {
if (this.slotIndex === -1) { if (this.slotIndex === -1) {

View File

@ -0,0 +1,142 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
DamagePhase,
EnemyCommandPhase,
MessagePhase,
PostSummonPhase,
SwitchPhase,
SwitchSummonPhase,
TurnEndPhase, TurnInitPhase,
TurnStartPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Stat} from "#app/data/pokemon-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {QuietFormChangePhase} from "#app/form-change-phase";
describe("Abilities - Zen mode", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
});
it("ZEN MODE - not enough damage to change form", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 100;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(40);
await game.phaseInterceptor.runFrom(DamagePhase).to(TurnEndPhase, false);
expect(game.scene.getParty()[0].hp).toBeLessThan(100);
expect(game.scene.getParty()[0].formIndex).toBe(0);
}, 20000);
it("ZEN MODE - enough damage to change form", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(QuietFormChangePhase);
await game.phaseInterceptor.to(TurnInitPhase, false);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
}, 20000);
it("ZEN MODE - kill pokemon while on zen mode", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
Species.CHARIZARD,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(80);
await game.phaseInterceptor.runFrom(DamagePhase).to(QuietFormChangePhase);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
await game.killPokemon(game.scene.getParty()[0]);
expect(game.scene.getParty()[0].isFainted()).toBe(true);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, 0, 1, false, false));
game.scene.ui.setMode(Mode.MESSAGE);
});
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
game.endPhase();
});
await game.phaseInterceptor.run(SwitchPhase);
await game.phaseInterceptor.to(PostSummonPhase);
expect(game.scene.getParty()[1].formIndex).toBe(1);
}, 20000);
});

View File

@ -6,7 +6,7 @@ import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides"; import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler"; import {Command} from "#app/ui/command-ui-handler";
import { import {
CommandPhase, CommandPhase, DamagePhase,
EncounterPhase, EncounterPhase,
EnemyCommandPhase, EnemyCommandPhase,
LoginPhase, LoginPhase,
@ -15,7 +15,7 @@ import {
SelectStarterPhase, SelectStarterPhase,
SummonPhase, SummonPhase,
TitlePhase, TitlePhase,
TurnInitPhase, TurnInitPhase, VictoryPhase,
} from "#app/phases"; } from "#app/phases";
import {Moves} from "#app/data/enums/moves"; import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
@ -106,9 +106,7 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); });
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase, false);
expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT);
expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
}, 20000); }, 20000);
it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => { it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => {
@ -128,7 +126,7 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); });
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase, false);
}, 20000); }, 20000);
it("load 100% data file", async() => { it("load 100% data file", async() => {
@ -259,5 +257,71 @@ describe("Test Battle Phase", () => {
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("kill opponent pokemon", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle([
Species.DARMANITAN,
Species.CHARIZARD,
]);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.to(DamagePhase, false);
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
await game.phaseInterceptor.to(VictoryPhase, false);
}, 200000);
it("to next turn", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle();
const turn = game.scene.currentBattle.turn;
await game.doAttack(0);
await game.toNextTurn();
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
}, 20000);
it("to next wave with pokemon killed, single", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle();
const waveIndex = game.scene.currentBattle.waveIndex;
await game.doAttack(0);
await game.doKillOpponents();
await game.toNextWave();
expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex);
}, 20000);
}); });

View File

@ -1,4 +1,4 @@
import {afterEach, beforeAll, beforeEach, describe, it, vi} from "vitest"; import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import * as overrides from "#app/overrides"; import * as overrides from "#app/overrides";
@ -22,18 +22,24 @@ describe("Test Battle Phase", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
}); const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
it("should start phase", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); });
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
it.skip("to next turn", async() => {
await game.startBattle(); await game.startBattle();
}, 100000); const turn = game.scene.currentBattle.turn;
await game.doAttack(0);
await game.toNextTurn();
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
}, 20000);
}); });

View File

@ -0,0 +1,137 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {Mode} from "#app/ui/ui";
import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides";
import {
CommandPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
it("startBattle 2vs1 boss", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(10);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 boss", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(10);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 trainer", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs1 trainer", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs1 rival", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(8);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 rival", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(8);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 1vs1 trainer", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 trainer", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 4vs2 trainer", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
Species.DARKRAI,
Species.GABITE,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
});

View File

@ -11,6 +11,6 @@ export default class TextInterceptor {
} }
getLatestMessage(): string { getLatestMessage(): string {
return this.logs[this.logs.length - 1]; return this.logs.pop();
} }
} }

View File

@ -3,19 +3,20 @@ import {Mode} from "#app/ui/ui";
import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils"; import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils";
import { import {
CommandPhase, CommandPhase,
DamagePhase,
EncounterPhase, EncounterPhase,
LoginPhase, FaintPhase,
PostSummonPhase, LoginPhase, NewBattlePhase,
SelectGenderPhase, SelectGenderPhase,
SelectStarterPhase, SelectStarterPhase,
TitlePhase, TitlePhase, TurnInitPhase,
} from "#app/phases"; } from "#app/phases";
import BattleScene from "#app/battle-scene.js"; import BattleScene from "#app/battle-scene.js";
import PhaseInterceptor from "#app/test/utils/phaseInterceptor"; import PhaseInterceptor from "#app/test/utils/phaseInterceptor";
import TextInterceptor from "#app/test/utils/TextInterceptor"; import TextInterceptor from "#app/test/utils/TextInterceptor";
import {GameModes, getGameMode} from "#app/game-mode"; import {GameModes, getGameMode} from "#app/game-mode";
import fs from "fs"; import fs from "fs";
import { AES, enc } from "crypto-js"; import {AES, enc} from "crypto-js";
import {updateUserInfo} from "#app/account"; import {updateUserInfo} from "#app/account";
import {Species} from "#app/data/enums/species"; import {Species} from "#app/data/enums/species";
import {PlayerGender} from "#app/data/enums/player-gender"; import {PlayerGender} from "#app/data/enums/player-gender";
@ -23,6 +24,11 @@ import {GameDataType} from "#app/data/enums/game-data-type";
import InputsHandler from "#app/test/utils/inputsHandler"; import InputsHandler from "#app/test/utils/inputsHandler";
import {ExpNotification} from "#app/enums/exp-notification"; import {ExpNotification} from "#app/enums/exp-notification";
import ErrorInterceptor from "#app/test/utils/errorInterceptor"; import ErrorInterceptor from "#app/test/utils/errorInterceptor";
import {EnemyPokemon, PlayerPokemon} from "#app/field/pokemon";
import {MockClock} from "#app/test/utils/mocks/mockClock";
import {Command} from "#app/ui/command-ui-handler";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import {Button} from "#app/enums/buttons";
/** /**
* Class to manage the game state and transitions between phases. * Class to manage the game state and transitions between phases.
@ -84,8 +90,8 @@ export default class GameManager {
* @param callback - The callback to execute. * @param callback - The callback to execute.
* @param expireFn - Optional function to determine if the prompt has expired. * @param expireFn - Optional function to determine if the prompt has expired.
*/ */
onNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn?: () => void) { onNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn?: () => void, awaitingActionInput: boolean = false) {
this.phaseInterceptor.addToNextPrompt(phaseTarget, mode, callback, expireFn); this.phaseInterceptor.addToNextPrompt(phaseTarget, mode, callback, expireFn, awaitingActionInput);
} }
/** /**
@ -142,17 +148,68 @@ export default class GameManager {
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE); this.setMode(Mode.MESSAGE);
this.endPhase(); this.endPhase();
}, () => this.isCurrentPhase(CommandPhase)); }, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(TurnInitPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE); this.setMode(Mode.MESSAGE);
this.endPhase(); this.endPhase();
}, () => this.isCurrentPhase(CommandPhase)); }, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(TurnInitPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase).catch((e) => reject(e)); await this.phaseInterceptor.to(CommandPhase).catch((e) => reject(e));
console.log("==================[New Turn]=================="); console.log("==================[New Turn]==================");
return resolve(); return resolve();
}); });
} }
doAttack(moveIndex: integer): Promise<void> {
this.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
this.scene.ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
this.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
(this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, moveIndex, false);
});
return this.phaseInterceptor.to(DamagePhase);
}
doKillOpponents() {
return new Promise<void>(async(resolve, reject) => {
await this.killPokemon(this.scene.currentBattle.enemyParty[0]).catch((e) => reject(e));
if (this.scene.currentBattle.double) {
await this.killPokemon(this.scene.currentBattle.enemyParty[1]).catch((e) => reject(e));
}
return resolve();
});
}
toNextTurn(): Promise<void> {
return new Promise<void>(async(resolve, reject) => {
await this.phaseInterceptor.to(CommandPhase).catch((e) => reject(e));
return resolve();
});
}
toNextWave(): Promise<void> {
return new Promise<void>(async(resolve, reject) => {
this.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
handler.processInput(Button.CANCEL);
}, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase), true);
this.onNextPrompt("SelectModifierPhase", Mode.CONFIRM, () => {
const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
handler.processInput(Button.ACTION);
}, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(TurnInitPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(TurnInitPhase));
await this.phaseInterceptor.to(CommandPhase).catch((e) => reject(e));
return resolve();
});
}
/** /**
* Checks if the player has won the battle. * Checks if the player has won the battle.
* @returns True if the player has won, otherwise false. * @returns True if the player has won, otherwise false.
@ -213,4 +270,15 @@ export default class GameManager {
} }
return updateUserInfo(); return updateUserInfo();
} }
async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) {
(this.scene.time as MockClock).overrideDelay = 0.01;
return new Promise<void>(async(resolve, reject) => {
pokemon.hp = 0;
this.scene.pushPhase(new FaintPhase(this.scene, pokemon.getBattlerIndex(), true));
await this.phaseInterceptor.to(FaintPhase).catch((e) => reject(e));
(this.scene.time as MockClock).overrideDelay = undefined;
resolve();
});
}
} }

View File

@ -120,7 +120,7 @@ export default class GameWrapper {
pause: () => null, pause: () => null,
setRate: () => null, setRate: () => null,
add: () => this.scene.sound, add: () => this.scene.sound,
get: () => this.scene.sound, get: () => ({...this.scene.sound, totalDuration: 0}),
getAllPlaying: () => [], getAllPlaying: () => [],
manager: { manager: {
game: this.game, game: this.game,
@ -131,6 +131,13 @@ export default class GameWrapper {
key: "", key: "",
}; };
this.scene.cameras = {
main: {
setPostPipeline: () => null,
removePostPipeline: () => null,
},
}
this.scene.tweens = { this.scene.tweens = {
add: (data) => { add: (data) => {
if (data.onComplete) { if (data.onComplete) {

View File

@ -2,8 +2,10 @@ import Clock = Phaser.Time.Clock;
export class MockClock extends Clock { export class MockClock extends Clock {
public overrideDelay: number;
constructor(scene) { constructor(scene) {
super(scene); super(scene);
this.overrideDelay = undefined;
setInterval(() => { setInterval(() => {
/* /*
To simulate frame update To simulate frame update
@ -14,4 +16,9 @@ export class MockClock extends Clock {
this.update(this.systems.game.loop.time, 100); this.update(this.systems.game.loop.time, 100);
}, 100); }, 100);
} }
addEvent(config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig): Phaser.Time.TimerEvent {
const cfg = { ...config, delay: this.overrideDelay || config.delay};
return super.addEvent(cfg);
}
} }

View File

@ -143,6 +143,7 @@ export default class MockSprite {
play() { play() {
// return this.phaserSprite.play(); // return this.phaserSprite.play();
return this;
} }
setPipelineData(key, value) { setPipelineData(key, value) {

View File

@ -1,17 +1,43 @@
import { import {
BattleEndPhase, BattleEndPhase,
BerryPhase, BerryPhase,
CheckSwitchPhase, CommandPhase, DamagePhase, EggLapsePhase, CheckSwitchPhase,
EncounterPhase, EnemyCommandPhase, FaintPhase, CommandPhase,
LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase, DamagePhase,
EggLapsePhase,
EncounterPhase,
EnemyCommandPhase,
FaintPhase,
LoginPhase,
MessagePhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
NewBattlePhase,
NextEncounterPhase,
PostSummonPhase, PostSummonPhase,
SelectGenderPhase, SelectModifierPhase, SelectGenderPhase,
SelectStarterPhase, SelectTargetPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase, SelectModifierPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, UnavailablePhase, VictoryPhase SelectStarterPhase,
SelectTargetPhase,
ShinySparklePhase,
ShowAbilityPhase,
StatChangePhase,
SummonPhase,
SwitchPhase,
SwitchSummonPhase,
TitlePhase,
ToggleDoublePositionPhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
UnavailablePhase,
VictoryPhase
} from "#app/phases"; } from "#app/phases";
import UI, {Mode} from "#app/ui/ui"; import UI, {Mode} from "#app/ui/ui";
import {Phase} from "#app/phase"; import {Phase} from "#app/phase";
import ErrorInterceptor from "#app/test/utils/errorInterceptor"; import ErrorInterceptor from "#app/test/utils/errorInterceptor";
import {QuietFormChangePhase} from "#app/form-change-phase";
export default class PhaseInterceptor { export default class PhaseInterceptor {
public scene; public scene;
@ -63,10 +89,13 @@ export default class PhaseInterceptor {
[ShinySparklePhase, this.startPhase], [ShinySparklePhase, this.startPhase],
[SelectTargetPhase, this.startPhase], [SelectTargetPhase, this.startPhase],
[UnavailablePhase, this.startPhase], [UnavailablePhase, this.startPhase],
[QuietFormChangePhase, this.startPhase],
[SwitchPhase, this.startPhase],
[SwitchSummonPhase, this.startPhase],
]; ];
private endBySetMode = [ private endBySetMode = [
TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase TitlePhase, SelectGenderPhase, CommandPhase
]; ];
/** /**
@ -78,8 +107,8 @@ export default class PhaseInterceptor {
this.log = []; this.log = [];
this.onHold = []; this.onHold = [];
this.prompts = []; this.prompts = [];
this.startPromptHandler();
this.initPhases(); this.initPhases();
this.startPromptHander();
} }
rejectAll(error) { rejectAll(error) {
@ -109,8 +138,10 @@ export default class PhaseInterceptor {
async to(phaseTo, runTarget: boolean = true): Promise<void> { async to(phaseTo, runTarget: boolean = true): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this); ErrorInterceptor.getInstance().add(this);
await this.run(this.phaseFrom).catch((e) => reject(e)); if (this.phaseFrom) {
this.phaseFrom = null; await this.run(this.phaseFrom).catch((e) => reject(e));
this.phaseFrom = null;
}
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name; const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async() => { this.intervalRun = setInterval(async() => {
const currentPhase = this.onHold?.length && this.onHold[0]; const currentPhase = this.onHold?.length && this.onHold[0];
@ -238,7 +269,6 @@ export default class PhaseInterceptor {
*/ */
superEndPhase() { superEndPhase() {
const instance = this.scene.getCurrentPhase(); const instance = this.scene.getCurrentPhase();
console.log(`%c INTERCEPTED Super End Phase ${instance.constructor.name}`, "color:red;");
this.originalSuperEnd.apply(instance); this.originalSuperEnd.apply(instance);
this.inProgress?.callback(); this.inProgress?.callback();
this.inProgress = undefined; this.inProgress = undefined;
@ -253,6 +283,9 @@ export default class PhaseInterceptor {
const instance = this.scene.ui; const instance = this.scene.ui;
console.log("setMode", mode, args); console.log("setMode", mode, args);
const ret = this.originalSetMode.apply(instance, [mode, ...args]); const ret = this.originalSetMode.apply(instance, [mode, ...args]);
if (!this.phases[currentPhase.constructor.name]) {
throw new Error(`missing ${currentPhase.constructor.name} in phaseInterceptior PHASES list`);
}
if (this.phases[currentPhase.constructor.name].endBySetMode) { if (this.phases[currentPhase.constructor.name].endBySetMode) {
this.inProgress?.callback(); this.inProgress?.callback();
this.inProgress = undefined; this.inProgress = undefined;
@ -263,16 +296,17 @@ export default class PhaseInterceptor {
/** /**
* Method to start the prompt handler. * Method to start the prompt handler.
*/ */
startPromptHander() { startPromptHandler() {
this.promptInterval = setInterval(() => { this.promptInterval = setInterval(() => {
if (this.prompts.length) { if (this.prompts.length) {
const actionForNextPrompt = this.prompts[0]; const actionForNextPrompt = this.prompts[0];
const expireFn = actionForNextPrompt.expireFn && actionForNextPrompt.expireFn(); const expireFn = actionForNextPrompt.expireFn && actionForNextPrompt.expireFn();
const currentMode = this.scene.ui.getMode(); const currentMode = this.scene.ui.getMode();
const currentPhase = this.scene.getCurrentPhase().constructor.name; const currentPhase = this.scene.getCurrentPhase().constructor.name;
const currentHandler = this.scene.ui.getHandler();
if (expireFn) { if (expireFn) {
this.prompts.shift(); this.prompts.shift();
} else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget) { } else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget && currentHandler.active && (!actionForNextPrompt.awaitingActionInput || (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput))) {
this.prompts.shift().callback(); this.prompts.shift().callback();
} }
} }
@ -286,12 +320,13 @@ export default class PhaseInterceptor {
* @param callback - The callback function to execute. * @param callback - The callback function to execute.
* @param expireFn - The function to determine if the prompt has expired. * @param expireFn - The function to determine if the prompt has expired.
*/ */
addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void) { addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void, awaitingActionInput: boolean = false) {
this.prompts.push({ this.prompts.push({
phaseTarget, phaseTarget,
mode, mode,
callback, callback,
expireFn expireFn,
awaitingActionInput
}); });
} }