Update tests to reflect move effect phase changes

Co-authored-by: innerthunder <brandonerickson98@gmail.com>
This commit is contained in:
Sirz Benjie 2025-04-12 15:35:52 -05:00
parent f85e008202
commit f29486cd4e
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
14 changed files with 102 additions and 99 deletions

View File

@ -669,10 +669,10 @@ export default class Move implements Localizable {
case MoveFlags.REFLECTABLE:
// If the target is not semi-invulnerable and either has magic coat active or an unignored magic bounce ability
if (
target?.getTag(SemiInvulnerableTag) &&
(target.getTag(BattlerTagType.MAGIC_COAT) ||
target?.getTag(SemiInvulnerableTag) ||
!(target?.getTag(BattlerTagType.MAGIC_COAT) ||
(!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target.hasAbilityWithAttr(ReflectStatusMoveAbAttr)))
target?.hasAbilityWithAttr(ReflectStatusMoveAbAttr)))
) {
return false;
}
@ -2733,11 +2733,11 @@ export class StealEatBerryAttr extends EatBerryAttr {
}
/**
* User steals a random berry from the target and then eats it.
* @param {Pokemon} user Pokemon that used the move and will eat the stolen berry
* @param {Pokemon} target Pokemon that will have its berry stolen
* @param {Move} move Move being used
* @param {any[]} args Unused
* @returns {boolean} true if the function succeeds
* @param user - Pokemon that used the move and will eat the stolen berry
* @param target - Pokemon that will have its berry stolen
* @param move - Move being used
* @param args Unused
* @returns true if the function succeeds
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const cancelled = new BooleanHolder(false);

View File

@ -1,7 +1,6 @@
export enum MoveEffectTrigger {
PRE_APPLY,
POST_APPLY,
HIT,
/** Triggers one time after all target effects have applied */
POST_TARGET
}

View File

@ -151,8 +151,6 @@ export class MoveEffectPhase extends PokemonPhase {
let targets = this.getTargets();
let fieldBounce = false;
// For field targeted moves, we only look for the first target that may magic bounce
for (const [i, target] of targets.entries()) {
@ -162,7 +160,6 @@ export class MoveEffectPhase extends PokemonPhase {
if (fieldMove && hitCheck[0] === HitCheckResult.REFLECTED) {
targets = [target];
this.hitChecks = [hitCheck];
fieldBounce = true;
break;
}
if (hitCheck[0] === HitCheckResult.HIT) {
@ -173,13 +170,8 @@ export class MoveEffectPhase extends PokemonPhase {
this.hitChecks[i] = hitCheck;
}
if (anySuccess || (fieldMove && !fieldBounce)) {
if (anySuccess) {
this.moveHistoryEntry.result = MoveResult.SUCCESS;
// Unreflected field moves have no targets; they target the field
if (fieldMove) {
this.hitChecks = [];
return [];
}
} else {
user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
@ -350,6 +342,9 @@ export class MoveEffectPhase extends PokemonPhase {
const targets = this.conductHitChecks(user, fieldMove);
this.firstHit = user.turnData.hitCount === user.turnData.hitsLeft;
this.lastHit = user.turnData.hitsLeft === 1 || !targets.some(t => t.isActive(true));
// only play the animation if the move had at least one successful target
// Play the animation if the move was successful against any of its targets or it has a POST_TARGET effect (like self destruct)
@ -375,6 +370,7 @@ export class MoveEffectPhase extends PokemonPhase {
* Callback to be called after the move animation is played
*/
private postAnimCallback(user: Pokemon, targets: Pokemon[]) {
console.log("============Inside post anim callback======");
// Add to the move history entry
if (this.firstHit) {
user.pushMoveHistory(this.moveHistoryEntry);
@ -387,7 +383,10 @@ export class MoveEffectPhase extends PokemonPhase {
console.warn(e.message || "Unexpected error in move effect phase");
this.end();
}
//
// Apply queued phases
if (this.queuedPhases.length) {
globalScene.appendToPhase(this.queuedPhases, MoveEndPhase);
}
const moveType = user.getMoveType(this.move, true);
if (this.move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) {
user.stellarTypesBoosted.push(moveType);
@ -398,11 +397,6 @@ export class MoveEffectPhase extends PokemonPhase {
}
this.updateSubstitutes();
// Apply queued phases
if (this.queuedPhases.length) {
globalScene.appendToPhase(this.queuedPhases, MoveEndPhase);
}
this.end();
}
@ -592,7 +586,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @param target - {@linkcode Pokemon} the target to check for protection
* @param move - The {@linkcode Move} being used
*/
private protectedCheck(user: Pokemon, target: Pokemon, move: Move) {
private protectedCheck(user: Pokemon, target: Pokemon) {
/** The {@linkcode ArenaTagSide} to which the target belongs */
const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
/** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */
@ -608,7 +602,7 @@ export class MoveEffectPhase extends PokemonPhase {
hasConditionalProtectApplied,
user,
target,
move.id,
this.move.id,
bypassIgnoreProtect,
);
}
@ -648,18 +642,21 @@ export class MoveEffectPhase extends PokemonPhase {
return [HitCheckResult.ERROR, 0];
}
// Moves targeting the user or field bypass accuracy and effectiveness checks
// Moves targeting the user bypass all checks
if (move.moveTarget === MoveTarget.USER) {
return [HitCheckResult.HIT, 1];
}
const fieldTargeted = isFieldTargeted(move);
// If the target is not on the field, cancel the hit check
if (!target.isActive(true)) {
if (!target.isActive(true) && !fieldTargeted) {
return [HitCheckResult.TARGET_NOT_ON_FIELD, 0];
}
// If the target of the move is hidden by the effects of its commander ability, then this misses
if (
!fieldTargeted &&
globalScene.currentBattle.double &&
target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target
) {
@ -667,14 +664,15 @@ export class MoveEffectPhase extends PokemonPhase {
}
/** Whether both accuracy and invulnerability checks can be skipped */
const bypassAccAndInvuln = this.checkBypassAccAndInvuln(target);
const bypassAccAndInvuln = fieldTargeted || this.checkBypassAccAndInvuln(target);
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
if (semiInvulnerableTag && !bypassAccAndInvuln && !this.checkBypassSemiInvuln(semiInvulnerableTag)) {
return [HitCheckResult.MISS, 0];
}
if (this.protectedCheck(user, target, move)) {
if (!fieldTargeted && this.protectedCheck(user, target)) {
console.log("====== Protected ========");
return [HitCheckResult.PROTECTED, 0];
}
@ -682,6 +680,12 @@ export class MoveEffectPhase extends PokemonPhase {
return [HitCheckResult.REFLECTED, 0];
}
// After the magic bounce check, field targeted moves are always successful
if (fieldTargeted) {
console.log("====== Field targeted moves overriding hit check ========");
return [HitCheckResult.HIT, 1];
}
const cancelNoEffectMessage = new BooleanHolder(false);
/**

View File

@ -4,7 +4,6 @@ import { PokemonType } from "#enums/pokemon-type";
import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
import { HitResult } from "#app/field/pokemon";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -38,13 +37,13 @@ describe("Abilities - Galvanize", () => {
});
it("should change Normal-type attacks to Electric type and boost their power", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
const move = allMoves[Moves.TACKLE];
vi.spyOn(move, "calculateBattlePower");
@ -54,21 +53,23 @@ describe("Abilities - Galvanize", () => {
await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.EFFECTIVE);
expect(spy).toHaveReturnedWith(1);
expect(move.calculateBattlePower).toHaveReturnedWith(48);
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
spy.mockRestore();
});
it("should cause Normal-type attacks to activate Volt Absorb", async () => {
game.override.enemyAbility(Abilities.VOLT_ABSORB);
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
@ -77,37 +78,37 @@ describe("Abilities - Galvanize", () => {
await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT);
expect(spy).toHaveReturnedWith(0);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
});
it("should not change the type of variable-type moves", async () => {
game.override.enemySpecies(Species.MIGHTYENA);
await game.startBattle([Species.ESPEON]);
await game.classicMode.startBattle([Species.ESPEON]);
const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
game.move.select(Moves.REVELATION_DANCE);
await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(PokemonType.ELECTRIC);
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT);
expect(spy).toHaveReturnedWith(0);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
});
it("should affect all hits of a Normal-type multi-hit move", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
game.move.select(Moves.FURY_SWIPES);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
@ -125,6 +126,6 @@ describe("Abilities - Galvanize", () => {
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
}
expect(enemyPokemon.apply).not.toHaveReturnedWith(HitResult.NO_EFFECT);
expect(spy).not.toHaveReturnedWith(0);
});
});

View File

@ -4,6 +4,7 @@ import { MoveEndPhase } from "#app/phases/move-end-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { HitCheckResult } from "#enums/hit-check-result";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
@ -28,6 +29,7 @@ describe("Abilities - No Guard", () => {
.moveset(Moves.ZAP_CANNON)
.ability(Abilities.NO_GUARD)
.enemyLevel(200)
.enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
@ -48,7 +50,7 @@ describe("Abilities - No Guard", () => {
await game.phaseInterceptor.to(MoveEndPhase);
expect(moveEffectPhase.hitCheck).toHaveReturnedWith(true);
expect(moveEffectPhase.hitCheck).toHaveReturnedWith([HitCheckResult.HIT, 1]);
});
it("should guarantee double battle with any one LURE", async () => {

View File

@ -52,7 +52,7 @@ describe("Abilities - Shield Dust", () => {
// Shield Dust negates secondary effect
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
const move = phase.move.getMove();
const move = phase.move;
expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new NumberHolder(move.chance);

View File

@ -25,7 +25,6 @@ describe("Abilities - Super Luck", () => {
.moveset([Moves.TACKLE])
.ability(Abilities.SUPER_LUCK)
.battleStyle("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);

View File

@ -2,7 +2,6 @@ import { BattlerIndex } from "#app/battle";
import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
import { HitResult } from "#app/field/pokemon";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -87,13 +86,15 @@ describe("Abilities - Tera Shell", () => {
await game.classicMode.startBattle([Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "apply");
const spy = vi.spyOn(playerPokemon, "getMoveEffectiveness");
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.EFFECTIVE);
expect(spy).toHaveLastReturnedWith(1);
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40);
spy.mockRestore();
});
it("should change the effectiveness of all strikes of a multi-strike move", async () => {
@ -102,7 +103,7 @@ describe("Abilities - Tera Shell", () => {
await game.classicMode.startBattle([Species.SNORLAX]);
const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "apply");
const spy = vi.spyOn(playerPokemon, "getMoveEffectiveness");
game.move.select(Moves.SPLASH);
@ -110,8 +111,9 @@ describe("Abilities - Tera Shell", () => {
await game.move.forceHit();
for (let i = 0; i < 2; i++) {
await game.phaseInterceptor.to("MoveEffectPhase");
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.NOT_VERY_EFFECTIVE);
expect(spy).toHaveLastReturnedWith(0.5);
}
expect(playerPokemon.apply).toHaveReturnedTimes(2);
expect(spy).toHaveReturnedTimes(2);
spy.mockRestore();
});
});

View File

@ -36,8 +36,7 @@ describe("Items - Dire Hit", () => {
.enemyMoveset(Moves.SPLASH)
.moveset([Moves.POUND])
.startingHeldItems([{ name: "DIRE_HIT" }])
.battleStyle("single")
.disableCrits();
.battleStyle("single");
}, 20000);
it("should raise CRIT stage by 1", async () => {

View File

@ -28,7 +28,6 @@ describe("Items - Leek", () => {
.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH])
.startingHeldItems([{ name: "LEEK" }])
.moveset([Moves.TACKLE])
.disableCrits()
.battleStyle("single");
});

View File

@ -27,8 +27,7 @@ describe("Items - Scope Lens", () => {
.enemyMoveset(Moves.SPLASH)
.moveset([Moves.POUND])
.startingHeldItems([{ name: "SCOPE_LENS" }])
.battleStyle("single")
.disableCrits();
.battleStyle("single");
}, 20000);
it("should raise CRIT stage by 1", async () => {

View File

@ -57,12 +57,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000);
@ -77,12 +77,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000);
@ -97,7 +97,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
@ -107,7 +107,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.phaseInterceptor.runFrom(MovePhase).to(MoveEndPhase);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000);
@ -123,7 +123,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
@ -132,7 +132,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.phaseInterceptor.runFrom(MovePhase).to(MoveEndPhase);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
}, 20000);
@ -147,12 +147,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000);
@ -191,22 +191,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000);
@ -245,22 +245,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000);

View File

@ -4,7 +4,6 @@ import { allMoves, TeraMoveCategoryAttr } from "#app/data/moves/move";
import type Move from "#app/data/moves/move";
import { PokemonType } from "#enums/pokemon-type";
import { Abilities } from "#app/enums/abilities";
import { HitResult } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
@ -49,9 +48,9 @@ describe("Moves - Tera Blast", () => {
it("changes type to match user's tera type", async () => {
game.override.enemySpecies(Species.FURRET);
await game.startBattle();
await game.classicMode.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = PokemonType.FIGHTING;
@ -61,11 +60,11 @@ describe("Moves - Tera Blast", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
expect(spy).toHaveReturnedWith(2);
}, 20000);
it("increases power if user is Stellar tera type", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = PokemonType.STELLAR;
@ -79,25 +78,25 @@ describe("Moves - Tera Blast", () => {
}, 20000);
it("is super effective against terastallized targets if user is Stellar tera type", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = PokemonType.STELLAR;
playerPokemon.isTerastallized = true;
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
enemyPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
expect(spy).toHaveReturnedWith(2);
});
it("uses the higher ATK for damage calculation", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 100;
@ -112,7 +111,7 @@ describe("Moves - Tera Blast", () => {
});
it("uses the higher SPATK for damage calculation", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 1;
@ -127,7 +126,7 @@ describe("Moves - Tera Blast", () => {
it("should stay as a special move if ATK turns lower than SPATK mid-turn", async () => {
game.override.enemyMoveset([Moves.CHARM]);
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 51;
@ -145,7 +144,7 @@ describe("Moves - Tera Blast", () => {
game.override
.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }])
.starterSpecies(Species.CUBONE);
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
@ -163,7 +162,7 @@ describe("Moves - Tera Blast", () => {
it("does not change its move category from stat changes due to abilities", async () => {
game.override.ability(Abilities.HUGE_POWER);
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 50;
@ -178,7 +177,7 @@ describe("Moves - Tera Blast", () => {
});
it("causes stat drops if user is Stellar tera type", async () => {
await game.startBattle();
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = PokemonType.STELLAR;

View File

@ -18,29 +18,29 @@ import { vi } from "vitest";
*/
export class MoveHelper extends GameManagerHelper {
/**
* Intercepts {@linkcode MoveEffectPhase} and mocks the
* {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `true`.
* Used to force a move to hit.
* Intercepts {@linkcode MoveEffectPhase} and mocks the phase's move's
* accuracy to -1, guaranteeing a hit.
*/
public async forceHit(): Promise<void> {
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(this.game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase;
vi.spyOn(moveEffectPhase.move, "calculateBattleAccuracy").mockReturnValue(-1);
}
/**
* Intercepts {@linkcode MoveEffectPhase} and mocks the
* {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `false`.
* Used to force a move to miss.
* Intercepts {@linkcode MoveEffectPhase} and mocks the phase's move's accuracy
* to 0, guaranteeing a miss.
* @param firstTargetOnly - Whether the move should force miss on the first target only, in the case of multi-target moves.
*/
public async forceMiss(firstTargetOnly = false): Promise<void> {
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
const hitCheck = vi.spyOn(this.game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck");
const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase;
const accuracy = vi.spyOn(moveEffectPhase.move, "calculateBattleAccuracy");
if (firstTargetOnly) {
hitCheck.mockReturnValueOnce(false);
accuracy.mockReturnValueOnce(0);
} else {
hitCheck.mockReturnValue(false);
accuracy.mockReturnValue(0);
}
}