[Bug] Fix PostSummonPhase Logic to Support Single Pokémon Teams (#2153)

* added test for spikes + forceOpponentToSwitch

* fix conditional for intimidate in a double if there is only 1 pokemon available on our side

* fix variable naming and ternary condition

* added a fallback to clear the conditionalQueue if it's a new turn

* speed up tests by skipping LoginPhase
This commit is contained in:
Greenlamp2 2024-06-13 14:42:25 +02:00 committed by GitHub
parent 0970c2cd4e
commit c908153761
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 90 additions and 29 deletions

View File

@ -9,6 +9,10 @@ export interface UserInfo {
export let loggedInUser: UserInfo = null;
export const clientSessionId = Utils.randomString(32);
export function initLoggedInUser(): void {
loggedInUser = { username: "Guest", lastSessionSlot: -1 };
}
export function updateUserInfo(): Promise<[boolean, integer]> {
return new Promise<[boolean, integer]>(resolve => {
if (bypassLogin) {

View File

@ -1922,6 +1922,8 @@ export default class BattleScene extends SceneBase {
}
if (!this.phaseQueue.length) {
this.populatePhaseQueue();
// clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift();

View File

@ -1033,13 +1033,19 @@ export class EncounterPhase extends BattlePhase {
if (this.scene.currentBattle.battleType !== BattleType.TRAINER) {
enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => {
// is the player party initialized ?
const a = !!this.scene.getParty()?.length;
// if there is not a player party, we can't continue
if (!this.scene.getParty()?.length) {
return false;
}
// how many player pokemon are on the field ?
const amountOnTheField = this.scene.getParty().filter(p => p.isOnField()).length;
const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length;
// if it's a 2vs1, there will never be a 2nd pokemon on our field even
const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2);
// if it's a double, there should be 2, otherwise 1
const b = this.scene.currentBattle.double ? amountOnTheField === 2 : amountOnTheField === 1;
return a && b;
if (this.scene.currentBattle.double) {
return pokemonsOnFieldCount === requiredPokemonsOnField;
}
return pokemonsOnFieldCount === 1;
}));
const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {

View File

@ -5,15 +5,17 @@ import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase, DamagePhase,
EnemyCommandPhase,
CommandPhase, DamagePhase, EncounterPhase,
EnemyCommandPhase, SelectStarterPhase,
TurnInitPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {BattleStat} from "#app/data/battle-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {generateStarter, getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {Status, StatusEffect} from "#app/data/status-effect";
import {GameModes, getGameMode} from "#app/game-mode";
describe("Abilities - Intimidate", () => {
@ -337,5 +339,53 @@ describe("Abilities - Intimidate", () => {
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
}, 200000);
}, 20000);
it("double - wild vs only 1 on player side", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
await game.runToSummon([
Species.MIGHTYENA,
]);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
}, 20000);
it("double - wild vs only 1 alive on player side", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene, [
Species.MIGHTYENA,
Species.POOCHYENA,
]);
const selectStarterPhase = new SelectStarterPhase(game.scene);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
game.scene.getParty()[1].hp = 0;
game.scene.getParty()[1].status = new Status(StatusEffect.FAINT);
});
await game.phaseInterceptor.run(EncounterPhase);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
}, 20000);
});

View File

@ -28,7 +28,7 @@ describe("Phases", () => {
describe("LoginPhase", () => {
it("should start the login phase", async () => {
const loginPhase = new LoginPhase(scene);
scene.pushPhase(loginPhase);
scene.unshiftPhase(loginPhase);
await game.phaseInterceptor.run(LoginPhase);
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
});
@ -37,7 +37,7 @@ describe("Phases", () => {
describe("TitlePhase", () => {
it("should start the title phase", async () => {
const titlePhase = new TitlePhase(scene);
scene.pushPhase(titlePhase);
scene.unshiftPhase(titlePhase);
await game.phaseInterceptor.run(TitlePhase);
expect(scene.ui.getMode()).to.equal(Mode.TITLE);
});
@ -46,7 +46,7 @@ describe("Phases", () => {
describe("UnavailablePhase", () => {
it("should start the unavailable phase", async () => {
const unavailablePhase = new UnavailablePhase(scene);
scene.pushPhase(unavailablePhase);
scene.unshiftPhase(unavailablePhase);
await game.phaseInterceptor.run(UnavailablePhase);
expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE);
}, 20000);

View File

@ -6,7 +6,6 @@ import {
EncounterPhase,
FaintPhase,
LoginPhase, NewBattlePhase,
SelectGenderPhase,
SelectStarterPhase,
TitlePhase, TurnInitPhase,
} from "#app/phases";
@ -100,14 +99,8 @@ export default class GameManager {
* @returns A promise that resolves when the title phase is reached.
*/
async runToTitle(): Promise<void> {
await this.phaseInterceptor.run(LoginPhase);
this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
this.scene.gameData.gender = PlayerGender.MALE;
this.endPhase();
}, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.whenAboutToRun(LoginPhase);
this.phaseInterceptor.pop();
await this.phaseInterceptor.run(TitlePhase);
this.scene.gameSpeed = 5;
@ -116,6 +109,9 @@ export default class GameManager {
this.scene.expGainsSpeed = 3;
this.scene.expParty = ExpNotification.SKIP;
this.scene.hpBarSpeed = 3;
this.scene.enableTutorials = false;
this.scene.gameData.gender = PlayerGender.MALE;
}
/**

View File

@ -212,19 +212,20 @@ export default class PhaseInterceptor {
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
const interval = setInterval(async () => {
const currentPhase = this.onHold.shift();
if (currentPhase) {
if (currentPhase.name !== targetName) {
this.onHold.unshift(currentPhase);
} else {
clearInterval(interval);
resolve();
}
const currentPhase = this.onHold[0];
if (currentPhase?.name === targetName) {
clearInterval(interval);
resolve();
}
});
});
}
pop() {
this.onHold.pop();
this.scene.shiftPhase();
}
/**
* Method to initialize phases and their corresponding methods.
*/

View File

@ -10,6 +10,7 @@ import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability";
import {initAchievements} from "#app/system/achv.js";
import { initVouchers } from "#app/system/voucher.js";
import {initLoggedInUser} from "#app/account";
initVouchers();
initAchievements();
@ -21,5 +22,6 @@ initPokemonForms();
initSpecies();
initMoves();
initAbilities();
initLoggedInUser();
global.testFailed = false;