diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f0aeb68e277..0b1ad049bbe 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1330,13 +1330,14 @@ export default class BattleScene extends SceneBase { } this.executeWithSeedOffset(() => { - this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); + this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble, this.currentBattle?.playerFaints, this.currentBattle?.enemyFaints); }, newWaveIndex << 3, this.waveSeed); this.currentBattle.incrementTurn(); if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { // Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome this.currentBattle.mysteryEncounterType = mysteryEncounterType; + this.resetFaintsCount(); } //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); @@ -1354,6 +1355,8 @@ export default class BattleScene extends SceneBase { this.arena.updatePoolsForTimeOfDay(); } if (resetArenaState) { + this.resetFaintsCount(); + this.arena.resetArenaEffects(); playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); @@ -1403,6 +1406,14 @@ export default class BattleScene extends SceneBase { return this.arena; } + /** + * resets plaer/enemyFaints count on {@linkcode currentBattle} + */ + resetFaintsCount(): void { + this.currentBattle.playerFaints = 0; + this.currentBattle.enemyFaints = 0; + } + updateFieldScale(): Promise { return new Promise(resolve => { const fieldScale = Math.floor(Math.pow(1 / this.getField(true) diff --git a/src/battle.ts b/src/battle.ts index 287a981f83d..836076ef26b 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -102,9 +102,9 @@ export default class Battle { private battleSeedState: string | null = null; public moneyScattered: number = 0; public lastUsedPokeball: PokeballType | null = null; - /** The number of times a Pokemon on the player's side has fainted this battle */ + /** The number of times a Pokemon on the player's side has fainted this arena encounter */ public playerFaints: number = 0; - /** The number of times a Pokemon on the enemy's side has fainted this battle */ + /** The number of times a Pokemon on the enemy's side has fainted this arena encounter */ public enemyFaints: number = 0; public playerFaintsHistory: FaintLogEntry[] = []; public enemyFaintsHistory: FaintLogEntry[] = []; @@ -115,7 +115,7 @@ export default class Battle { private rngCounter: number = 0; - constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double?: boolean) { + constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double?: boolean, playerFaints?: number, enemyFaints?: number) { this.gameMode = gameMode; this.waveIndex = waveIndex; this.battleType = battleType; @@ -125,6 +125,8 @@ export default class Battle { ? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave()) : trainer?.getPartyLevels(this.waveIndex); this.double = double ?? false; + this.playerFaints = playerFaints ?? 0; + this.enemyFaints = enemyFaints ?? 0; } private initBattleSpec(): void { diff --git a/src/data/move.ts b/src/data/move.ts index 06f3c85e9c4..15e236b4488 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -10898,7 +10898,6 @@ export function initMoves() { .attr(ConfuseAttr) .recklessMove(), new AttackMove(Moves.LAST_RESPECTS, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9) - .partial() // Counter resets every wave instead of on arena reset .attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? globalScene.currentBattle.playerFaints : globalScene.currentBattle.enemyFaints, 100)) .makesContact(false), new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 7bf3bc81930..23827d18159 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -97,7 +97,7 @@ export class FaintPhase extends PokemonPhase { const pokemon = this.getPokemon(); - // Track total times pokemon have been KO'd for supreme overlord/last respects + // Track total times pokemon have been KO'd for last respects if (pokemon.isPlayer()) { globalScene.currentBattle.playerFaints += 1; globalScene.currentBattle.playerFaintsHistory.push({ pokemon: pokemon, turn: globalScene.currentBattle.turn }); diff --git a/src/test/moves/last_respects.test.ts b/src/test/moves/last_respects.test.ts new file mode 100644 index 00000000000..4f6ad4d31c8 --- /dev/null +++ b/src/test/moves/last_respects.test.ts @@ -0,0 +1,174 @@ +import { Moves } from "#enums/moves"; +import { BattlerIndex } from "#app/battle"; +import { Species } from "#enums/species"; +import { Abilities } from "#enums/abilities"; +import GameManager from "#test/utils/gameManager"; +import { allMoves } from "#app/data/move"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Last Respects", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const move = allMoves[Moves.LAST_RESPECTS]; + const basePower = move.power; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .disableCrits() + .moveset([ Moves.LAST_RESPECTS, Moves.EXPLOSION ]) + .ability(Abilities.BALL_FETCH) + .enemyAbility(Abilities.BALL_FETCH) + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.SPLASH) + .startingLevel(1) + .enemyLevel(100); + }); + + it("should have 150 power if 2 allies faint before using move", async () => { + await game.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + vi.spyOn(move, "calculateBattlePower"); + + /** + * Bulbasur faints once + */ + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + /** + * Charmander faints once + */ + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(2); + await game.toNextTurn(); + + game.move.select(Moves.LAST_RESPECTS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(move.calculateBattlePower).toHaveReturnedWith(basePower + (2 * 50)); + }); + + it("should have 200 power if an ally fainted twice and antoher one once", async () => { + await game.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + vi.spyOn(move, "calculateBattlePower"); + + /** + * Bulbasur faints once + */ + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + /** + * Charmander faints once + */ + game.doRevivePokemon(1); + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + /** + * Bulbasur faints twice + */ + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(2); + await game.toNextTurn(); + + game.move.select(Moves.LAST_RESPECTS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(move.calculateBattlePower).toHaveReturnedWith(basePower + (3 * 50)); + }); + + /** + * The following 3 tests do not switch out Pokemon 0 after it uses Explosion. + * It should die after using Explosion and switch out to Pokemon 1. + * After switching in Pokemon 1 it uses Last Respects and the battle power is calculated. + */ + it.todo("should maintain its power during next battle if it is within the same arena encounter", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(1) + .enemyLevel(1) + .startingLevel(100); + + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(1); + await game.toNextWave(); + + game.move.select(Moves.LAST_RESPECTS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(basePower + (1 * 50)); + }); + + it.todo("should reset playerFaints count if we enter new trainer battle", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(4) + .enemyLevel(1) + .startingLevel(100); + + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + game.move.select(Moves.LAST_RESPECTS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(basePower); + }); + + it.todo("should reset playerFaints count if we enter new biome", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(10) + .enemyLevel(1) + .startingLevel(100); + + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + game.move.select(Moves.EXPLOSION); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + game.move.select(Moves.LAST_RESPECTS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(basePower); + }); +});