diff --git a/src/data/move.ts b/src/data/move.ts index f795d265336..d315565907a 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1935,12 +1935,19 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @see {@linkcode apply} */ export class MultiHitAttr extends MoveAttr { - private multiHitType: MultiHitType; + private _intrinsicMultiHitType: MultiHitType; + private _multiHitType: MultiHitType; + + // Must be publicly readable for battle_bond.test.ts + get multiHitType(): MultiHitType { + return this._multiHitType; + } constructor(multiHitType?: MultiHitType) { super(); - this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this._intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this._multiHitType = this._intrinsicMultiHitType; } /** @@ -1954,9 +1961,9 @@ export class MultiHitAttr extends MoveAttr { * @returns True */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const hitType = new Utils.NumberHolder(this.multiHitType); + const hitType = new Utils.NumberHolder(this._intrinsicMultiHitType); applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); - this.multiHitType = hitType.value; + this._multiHitType = hitType.value; (args[0] as Utils.NumberHolder).value = this.getHitCount(user, target); return true; diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index c7dffeb150a..b88b688b16c 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,11 +1,10 @@ +import Move, { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move"; import { Status, StatusEffect } from "#app/data/status-effect"; -import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - BATTLE BOND", () => { @@ -24,40 +23,74 @@ describe("Abilities - BATTLE BOND", () => { beforeEach(() => { game = new GameManager(phaserGame); - const moveToUse = Moves.SPLASH; - game.override.battleType("single"); - game.override.ability(Abilities.BATTLE_BOND); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); + game.override.battleType("single") + .ability(Abilities.BATTLE_BOND) + .moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ]) + .enemySpecies(Species.BULBASAUR) + .startingLevel(100) // Avoid levelling up + .enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()` }); - test( - "check if fainted pokemon switches to base form on arena reset", - async () => { - const baseForm = 1; - const ashForm = 2; - game.override.startingWave(4); - game.override.starterForms({ - [Species.GRENINJA]: ashForm, - }); + it("check if fainted pokemon switches to base form on arena reset", async () => { + const baseForm = 1; + const ashForm = 2; + game.override.startingWave(4) + .starterForms({ [Species.GRENINJA]: ashForm, }) + .enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); - await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); - const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); - expect(greninja).toBeDefined(); - expect(greninja!.formIndex).toBe(ashForm); + const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); + expect(greninja).toBeDefined(); + expect(greninja!.formIndex).toBe(ashForm); - greninja!.hp = 0; - greninja!.status = new Status(StatusEffect.FAINT); - expect(greninja!.isFainted()).toBe(true); + greninja!.hp = 0; + greninja!.status = new Status(StatusEffect.FAINT); + expect(greninja!.isFainted()).toBe(true); - game.move.select(Moves.SPLASH); - await game.doKillOpponents(); - await game.phaseInterceptor.to(TurnEndPhase); - game.doSelectModifier(); - await game.phaseInterceptor.to(QuietFormChangePhase); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("TurnEndPhase"); + game.doSelectModifier(); + await game.phaseInterceptor.to("QuietFormChangePhase"); - expect(greninja!.formIndex).toBe(baseForm); - }, - ); + expect(greninja!.formIndex).toBe(baseForm); + }); + + it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => { + const ashForm = 2; + game.override.startingWave(4) + .starterForms({ [Species.GRENINJA]: ashForm, }) + .enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.GRENINJA ]); + // Wave 4: Use Water Shuriken in Ash form + let expectedBattlePower = 20; + let expectedMultiHitType = MultiHitType._3; + + const waterShuriken = allMoves.find(move => move.id === Moves.WATER_SHURIKEN) as Move; + vi.spyOn(waterShuriken, "calculateBattlePower"); + + let actualMultiHitType : MultiHitType | null = null; + const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0] as MultiHitAttr; + vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => { + actualMultiHitType = multiHitAttr.multiHitType; + return 3; + }); + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + + await game.doKillOpponents(); + await game.toNextWave(); + // Wave 5: Use Water Shuriken in base form + expectedBattlePower = 15; + expectedMultiHitType = MultiHitType._2_TO_5; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + }); });