[Refactor] Replace forceBypass with bypassFaint flag (#1839)

* replace forceBypass with bypassFaint flag

* add another path alias for src/test

* make form override work for the whole party instead of the first pokemon

* add tests for all abilities that are touched by this change

* remove unnecessary overrides from tests

* move SpeciesFormChangeTimeOfDayTrigger outside arena reset logic

* remove alll resetMock calls, rename it to test
This commit is contained in:
Dmitriy K 2024-06-13 05:36:12 -04:00 committed by GitHub
parent 16e14376c6
commit 0970c2cd4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 593 additions and 124 deletions

View File

@ -54,6 +54,7 @@
},
"imports": {
"#app": "./src/main.js",
"#app/*": "./src/*"
"#app/*": "./src/*",
"#test/*": "./src/test/*"
}
}

View File

@ -1098,18 +1098,18 @@ export default class BattleScene extends SceneBase {
if (pokemon.hasAbility(Abilities.ICE_FACE)) {
pokemon.formIndex = 0;
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
this.unshiftPhase(new ShowTrainerPhase(this));
}
for (const pokemon of this.getParty()) {
if (pokemon) {
if (resetArenaState) {
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon, true);
}
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeTimeOfDayTrigger);
}
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeTimeOfDayTrigger);
}
if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase(this));
} else {

View File

@ -3525,7 +3525,7 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any[]): TAttr },
pokemon: Pokemon, applyFunc: AbAttrApplyFunc<TAttr>, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise<void> {
return new Promise(resolve => {
if (!pokemon.canApplyAbility(passive, args[0])) {
if (!pokemon.canApplyAbility(passive)) {
if (!passive) {
return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve());
} else {
@ -4238,7 +4238,8 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.VICTORY_STAR, 5)
.attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.1)
.partial(),
@ -4347,6 +4348,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.partial(),
new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2),
@ -4379,7 +4381,8 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.DISGUISE, 7)
.attr(PreDefendMovePowerToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PostSummonFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
@ -4392,6 +4395,7 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.ignorable()
.partial(),
new Ability(Abilities.BATTLE_BOND, 7)
@ -4400,7 +4404,8 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.POWER_CONSTRUCT, 7) // TODO: 10% Power Construct Zygarde isn't accounted for yet. If changed, update Zygarde's getSpeciesFormIndex entry accordingly
.attr(PostBattleInitFormChangeAbAttr, () => 2)
.attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
@ -4409,6 +4414,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.partial(),
new Ability(Abilities.CORROSION, 7) // TODO: Test Corrosion against Magic Bounce once it is implemented
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [StatusEffect.POISON, StatusEffect.TOXIC], [Type.STEEL, Type.POISON])
@ -4639,7 +4645,8 @@ export function initAbilities() {
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PreSwitchOutFormChangeAbAttr, () => 1),
.attr(PreSwitchOutFormChangeAbAttr, () => 1)
.bypassFaint(),
new Ability(Abilities.COMMANDER, 9)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)

View File

@ -1004,7 +1004,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param {boolean} passive If true, check if passive can be applied instead of non-passive
* @returns {Ability} The passive ability of the pokemon
*/
canApplyAbility(passive: boolean = false, forceBypass: boolean = false): boolean {
canApplyAbility(passive: boolean = false): boolean {
if (passive && !this.hasPassive()) {
return false;
}
@ -1032,7 +1032,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
}
}
return (this.hp || ability.isBypassFaint || forceBypass) && !ability.conditions.find(condition => !condition(this));
return (this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
}
/**

View File

@ -15,6 +15,7 @@ import {TimeOfDay} from "#app/data/enums/time-of-day";
import { Gender } from "./data/gender";
import { StatusEffect } from "./data/status-effect";
import { modifierTypes } from "./modifier/modifier-type";
import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars
/**
* Overrides for testing different in game situations
@ -53,8 +54,18 @@ export const POKEBALL_OVERRIDE: { active: boolean, pokeballs: PokeballCounts } =
* PLAYER OVERRIDES
*/
// forms can be found in pokemon-species.ts
export const STARTER_FORM_OVERRIDE: integer = 0;
/**
* Set the form index of any starter in the party whose `speciesId` is inside this override
* @see {@link allSpecies} in `src/data/pokemon-species.ts` for form indexes
* @example
* ```
* const STARTER_FORM_OVERRIDES = {
* [Species.DARMANITAN]: 1
* }
* ```
*/
export const STARTER_FORM_OVERRIDES: Partial<Record<Species, number>> = {};
// default 5 or 20 for Daily
export const STARTING_LEVEL_OVERRIDE: integer = 0;
/**

View File

@ -555,6 +555,10 @@ export class SelectStarterPhase extends Phase {
});
}
/**
* Initialize starters before starting the first battle
* @param starters {@linkcode Pokemon} with which to start the first battle
*/
initBattle(starters: Starter[]) {
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
@ -564,9 +568,13 @@ export class SelectStarterPhase extends Phase {
}
const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDE;
if (
starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES &&
starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]]
) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId];
}
let starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#app/data/enums/moves.js";
import { Abilities } from "#app/data/enums/abilities.js";
import { Species } from "#app/data/enums/species.js";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - BATTLE BOND", () => {
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BATTLE_BOND);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 1,
ashForm = 2;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.GRENINJA]: ashForm,
});
await game.startBattle([Species.MAGIKARP, Species.GRENINJA]);
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA);
expect(greninja).not.toBe(undefined);
expect(greninja.formIndex).toBe(ashForm);
greninja.hp = 0;
greninja.status = new Status(StatusEffect.FAINT);
expect(greninja.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(greninja.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#app/data/enums/moves.js";
import { Abilities } from "#app/data/enums/abilities.js";
import { Species } from "#app/data/enums/species.js";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - DISGUISE", () => {
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DISGUISE);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switched to base form on arena reset",
async () => {
const baseForm = 0,
bustedForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.MIMIKYU]: bustedForm,
});
await game.startBattle([Species.MAGIKARP, Species.MIMIKYU]);
const mimikyu = game.scene.getParty().find((p) => p.species.speciesId === Species.MIMIKYU);
expect(mimikyu).not.toBe(undefined);
expect(mimikyu.formIndex).toBe(bustedForm);
mimikyu.hp = 0;
mimikyu.status = new Status(StatusEffect.FAINT);
expect(mimikyu.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(mimikyu.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#app/data/enums/moves.js";
import { Abilities } from "#app/data/enums/abilities.js";
import { Species } from "#app/data/enums/species.js";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - POWER CONSTRUCT", () => {
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.POWER_CONSTRUCT);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 2,
completeForm = 4;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.ZYGARDE]: completeForm,
});
await game.startBattle([Species.MAGIKARP, Species.ZYGARDE]);
const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE);
expect(zygarde).not.toBe(undefined);
expect(zygarde.formIndex).toBe(completeForm);
zygarde.hp = 0;
zygarde.status = new Status(StatusEffect.FAINT);
expect(zygarde.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(zygarde.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#app/data/enums/moves.js";
import { Abilities } from "#app/data/enums/abilities.js";
import { Species } from "#app/data/enums/species.js";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - SCHOOLING", () => {
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SCHOOLING);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const soloForm = 0,
schoolForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.WISHIWASHI]: schoolForm,
});
await game.startBattle([Species.MAGIKARP, Species.WISHIWASHI]);
const wishiwashi = game.scene.getParty().find((p) => p.species.speciesId === Species.WISHIWASHI);
expect(wishiwashi).not.toBe(undefined);
expect(wishiwashi.formIndex).toBe(schoolForm);
wishiwashi.hp = 0;
wishiwashi.status = new Status(StatusEffect.FAINT);
expect(wishiwashi.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(wishiwashi.formIndex).toBe(soloForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#app/data/enums/moves.js";
import { Abilities } from "#app/data/enums/abilities.js";
import { Species } from "#app/data/enums/species.js";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - SHIELDS DOWN", () => {
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHIELDS_DOWN);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switched to base form on arena reset",
async () => {
const meteorForm = 0,
coreForm = 7;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.MINIOR]: coreForm,
});
await game.startBattle([Species.MAGIKARP, Species.MINIOR]);
const minior = game.scene.getParty().find((p) => p.species.speciesId === Species.MINIOR);
expect(minior).not.toBe(undefined);
expect(minior.formIndex).toBe(coreForm);
minior.hp = 0;
minior.status = new Status(StatusEffect.FAINT);
expect(minior.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(minior.formIndex).toBe(meteorForm);
},
TIMEOUT
);
});

View File

@ -1,9 +1,9 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, test, 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 * as Overrides from "#app/overrides";
import { Abilities } from "#app/data/enums/abilities";
import { Species } from "#app/data/enums/species";
import {
CommandPhase,
DamagePhase,
@ -12,18 +12,21 @@ import {
PostSummonPhase,
SwitchPhase,
SwitchSummonPhase,
TurnEndPhase, TurnInitPhase,
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";
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";
import { Status, StatusEffect } from "#app/data/status-effect.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - Zen mode", () => {
describe("Abilities - ZEN MODE", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@ -40,103 +43,139 @@ describe("Abilities - Zen mode", () => {
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]);
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);
test(
"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);
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);
},
TIMEOUT
);
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);
test(
"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);
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);
},
TIMEOUT
);
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);
test(
"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);
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);
},
TIMEOUT
);
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 0,
zenForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.DARMANITAN]: zenForm,
});
await game.startBattle([Species.MAGIKARP, Species.DARMANITAN]);
const darmanitan = game.scene.getParty().find((p) => p.species.speciesId === Species.DARMANITAN);
expect(darmanitan).not.toBe(undefined);
expect(darmanitan.formIndex).toBe(zenForm);
darmanitan.hp = 0;
darmanitan.status = new Status(StatusEffect.FAINT);
expect(darmanitan.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(darmanitan.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#app/data/enums/moves.js";
import { Abilities } from "#app/data/enums/abilities.js";
import { Species } from "#app/data/enums/species.js";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - ZERO TO HERO", () => {
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZERO_TO_HERO);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 0,
heroForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.PALAFIN]: heroForm,
});
await game.startBattle([Species.MAGIKARP, Species.PALAFIN]);
const palafin = game.scene.getParty().find((p) => p.species.speciesId === Species.PALAFIN);
expect(palafin).not.toBe(undefined);
expect(palafin.formIndex).toBe(heroForm);
palafin.hp = 0;
palafin.status = new Status(StatusEffect.FAINT);
expect(palafin.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(palafin.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -12,7 +12,8 @@
"baseUrl": "./src",
"paths": {
"#app/*": ["*.ts"],
"#app": ["."]
"#app": ["."],
"#test/*": ["./test/*.ts"]
},
"outDir": "./build",
"noEmit": true