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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import { PokemonType } from "#enums/pokemon-type";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species"; import { Species } from "#app/enums/species";
import { HitResult } from "#app/field/pokemon";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; 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 () => { 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()!; const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType"); vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply"); const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
const move = allMoves[Moves.TACKLE]; const move = allMoves[Moves.TACKLE];
vi.spyOn(move, "calculateBattlePower"); vi.spyOn(move, "calculateBattlePower");
@ -54,21 +53,23 @@ describe("Abilities - Galvanize", () => {
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC); expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.EFFECTIVE); expect(spy).toHaveReturnedWith(1);
expect(move.calculateBattlePower).toHaveReturnedWith(48); expect(move.calculateBattlePower).toHaveReturnedWith(48);
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
spy.mockRestore();
}); });
it("should cause Normal-type attacks to activate Volt Absorb", async () => { it("should cause Normal-type attacks to activate Volt Absorb", async () => {
game.override.enemyAbility(Abilities.VOLT_ABSORB); game.override.enemyAbility(Abilities.VOLT_ABSORB);
await game.startBattle(); await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType"); vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply"); const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8); enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
@ -77,37 +78,37 @@ describe("Abilities - Galvanize", () => {
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC); expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT); expect(spy).toHaveReturnedWith(0);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
}); });
it("should not change the type of variable-type moves", async () => { it("should not change the type of variable-type moves", async () => {
game.override.enemySpecies(Species.MIGHTYENA); game.override.enemySpecies(Species.MIGHTYENA);
await game.startBattle([Species.ESPEON]); await game.classicMode.startBattle([Species.ESPEON]);
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType"); vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply"); const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
game.move.select(Moves.REVELATION_DANCE); game.move.select(Moves.REVELATION_DANCE);
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("BerryPhase", false);
expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(PokemonType.ELECTRIC); expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(PokemonType.ELECTRIC);
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT); expect(spy).toHaveReturnedWith(0);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
}); });
it("should affect all hits of a Normal-type multi-hit move", async () => { 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()!; const playerPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(playerPokemon, "getMoveType"); vi.spyOn(playerPokemon, "getMoveType");
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply"); const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
game.move.select(Moves.FURY_SWIPES); game.move.select(Moves.FURY_SWIPES);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
@ -125,6 +126,6 @@ describe("Abilities - Galvanize", () => {
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); 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 { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { HitCheckResult } from "#enums/hit-check-result";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
@ -28,6 +29,7 @@ describe("Abilities - No Guard", () => {
.moveset(Moves.ZAP_CANNON) .moveset(Moves.ZAP_CANNON)
.ability(Abilities.NO_GUARD) .ability(Abilities.NO_GUARD)
.enemyLevel(200) .enemyLevel(200)
.enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH); .enemyMoveset(Moves.SPLASH);
}); });
@ -48,7 +50,7 @@ describe("Abilities - No Guard", () => {
await game.phaseInterceptor.to(MoveEndPhase); 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 () => { 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 // Shield Dust negates secondary effect
const phase = game.scene.getCurrentPhase() as MoveEffectPhase; const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
const move = phase.move.getMove(); const move = phase.move;
expect(move.id).toBe(Moves.AIR_SLASH); expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new NumberHolder(move.chance); const chance = new NumberHolder(move.chance);

View File

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

View File

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

View File

@ -27,8 +27,7 @@ describe("Items - Scope Lens", () => {
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.moveset([Moves.POUND]) .moveset([Moves.POUND])
.startingHeldItems([{ name: "SCOPE_LENS" }]) .startingHeldItems([{ name: "SCOPE_LENS" }])
.battleStyle("single") .battleStyle("single");
.disableCrits();
}, 20000); }, 20000);
it("should raise CRIT stage by 1", async () => { 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000); }, 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000); }, 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); 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.runFrom(MovePhase).to(MoveEndPhase);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000); }, 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); 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.runFrom(MovePhase).to(MoveEndPhase);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
}, 20000); }, 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000); }, 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000); }, 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.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false); 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); await game.phaseInterceptor.to(DamageAnimPhase, false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
}, 20000); }, 20000);

View File

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

View File

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