From bb88fd6d11231e11ac71c7ae2cb2a67b6fdd6cce Mon Sep 17 00:00:00 2001 From: geeil-han Date: Wed, 15 Jan 2025 10:08:49 +0100 Subject: [PATCH] implementation of rage fist --- src/data/custom-pokemon-data.ts | 10 +- src/data/move.ts | 28 ++++- src/field/pokemon.ts | 5 + src/phases/encounter-phase.ts | 6 + src/phases/new-biome-encounter-phase.ts | 1 + src/test/moves/rage_fist.test.ts | 156 ++++++++++++++++++++++++ 6 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 src/test/moves/rage_fist.test.ts diff --git a/src/data/custom-pokemon-data.ts b/src/data/custom-pokemon-data.ts index 1c3bbbc3180..55b06db5179 100644 --- a/src/data/custom-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -5,7 +5,8 @@ import type { Nature } from "#enums/nature"; /** * Data that can customize a Pokemon in non-standard ways from its Species - * Currently only used by Mystery Encounters and Mints. + * Used by Mystery Encounters and Mints + * Also used as a counter how often a Pokemon got hit until new arena encounter */ export class CustomPokemonData { public spriteScale: number; @@ -13,6 +14,8 @@ export class CustomPokemonData { public passive: Abilities | -1; public nature: Nature | -1; public types: Type[]; + //hitsRecivedCount aka hitsRecCount saves how often the pokemon got hit until a new arena encounter (used for Rage Fist) + public hitsRecCount: number; constructor(data?: CustomPokemonData | Partial) { if (!isNullOrUndefined(data)) { @@ -24,5 +27,10 @@ export class CustomPokemonData { this.passive = this.passive ?? -1; this.nature = this.nature ?? -1; this.types = this.types ?? []; + this.hitsRecCount = this.hitsRecCount ?? 0; + } + + resetHitRecivedCount(): void { + this.hitsRecCount = 0; } } diff --git a/src/data/move.ts b/src/data/move.ts index f3a1f3aa119..956dbe702ba 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3993,12 +3993,34 @@ export class FriendshipPowerAttr extends VariablePowerAttr { } } -export class HitCountPowerAttr extends VariablePowerAttr { +/** + * This Attribute calculates the current power of {@linkcode Moves.RAGE_FIST} + * The counter for power calculation does not reset on every wave but on every new arena encounter + */ +export class RageFistPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value += Math.min(user.battleData.hitCount, 6) * 50; + const BDHitCount = user.battleData.hitCount; + const previousHitCount = user.battleData.prevHitCount; + + this.updateHitRecivedCount(user, BDHitCount, previousHitCount); + + console.log(`GHNote hitsRecCount: ${user.customPokemonData.hitsRecCount}`); + + (args[0] as Utils.NumberHolder).value = 50 + (Math.min(user.customPokemonData.hitsRecCount, 6) * 50); return true; } + + /** + * Updates the hitCount of recived hits during this arena encounter + * @param user Pokemon calling Rage Fist + * @param BDHitCount The hitCount of reviced hits this battle up until the function is called + * @param previousHitCount The hitCount of reviced hits this battle the last time Rage Fist was called + */ + updateHitRecivedCount(user: Pokemon, BDHitCount: number, previousHitCount: number): void { + user.customPokemonData.hitsRecCount += (BDHitCount - previousHitCount); + user.battleData.prevHitCount = BDHitCount; + } } /** @@ -11004,7 +11026,7 @@ export function initMoves() { .attr(MultiHitAttr, MultiHitType._2), new AttackMove(Moves.RAGE_FIST, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9) .partial() // Counter resets every wave instead of on arena reset - .attr(HitCountPowerAttr) + .attr(RageFistPowerAttr) .punchingMove(), new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8fc00e2ebeb..1e377e70291 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5281,7 +5281,12 @@ export class PokemonSummonData { } export class PokemonBattleData { + //counts the hits the pokemon recived public hitCount: number = 0; + /** + * used for {@linkcode Moves.RAGE_FIST} in order to save hit Counts recived before Rage Fist is applied + */ + public prevHitCount: number = 0; public endured: boolean = false; public berriesEaten: BerryType[] = []; public abilitiesApplied: Abilities[] = []; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 6dae7dff8f9..2083f09896a 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -104,6 +104,12 @@ export class EncounterPhase extends BattlePhase { } if (!this.loaded) { if (battle.battleType === BattleType.TRAINER) { + //resets hitRecCount during Trainer ecnounter + for (const pokemon of globalScene.getPlayerParty()) { + if (pokemon) { + pokemon.customPokemonData.resetHitRecivedCount(); + } + } battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? } else { let enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true); diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index be6815333e5..b0a7bf4d1ae 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -14,6 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { for (const pokemon of globalScene.getPlayerParty()) { if (pokemon) { pokemon.resetBattleData(); + pokemon.customPokemonData.resetHitRecivedCount(); } } diff --git a/src/test/moves/rage_fist.test.ts b/src/test/moves/rage_fist.test.ts new file mode 100644 index 00000000000..61f612eddd4 --- /dev/null +++ b/src/test/moves/rage_fist.test.ts @@ -0,0 +1,156 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { allMoves } from "#app/data/move"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Rage Fist", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .moveset([ Moves.RAGE_FIST, Moves.SPLASH, Moves.SUBSTITUTE ]) + .startingLevel(100) + .enemyLevel(1) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.DOUBLE_KICK); + }); + + it("should have 100 more power if hit twice before calling Rage Fist", async () => { + game.override + .enemySpecies(Species.MAGIKARP); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const move = allMoves[Moves.RAGE_FIST]; + vi.spyOn(move, "calculateBattlePower"); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); + + it("should maintain its power during next battle if it is within the same arena encounter", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(1); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const move = allMoves[Moves.RAGE_FIST]; + vi.spyOn(move, "calculateBattlePower"); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextWave(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(250); + }); + + it("should reset the hitRecCounter if we enter new trainer battle", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(4); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const move = allMoves[Moves.RAGE_FIST]; + vi.spyOn(move, "calculateBattlePower"); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextWave(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); + + it("should not increase the hitCounter if Substitute is hit", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(4); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const move = allMoves[Moves.RAGE_FIST]; + vi.spyOn(move, "calculateBattlePower"); + + game.move.select(Moves.SUBSTITUTE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(game.scene.getPlayerPokemon()?.customPokemonData.hitsRecCount).toBe(0); + }); + + //For some unknown reason the second Rage fist is not called. This might be due to entering a new biome + it.todo("should reset the hitRecCounter if we enter new biome", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(10); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const move = allMoves[Moves.RAGE_FIST]; + vi.spyOn(move, "calculateBattlePower"); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextWave(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); + + //Test does not work correctly. Feel free to add changes if you can make it work + it.todo("should not reset the hitRecCounter if switched out", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(1); + + await game.classicMode.startBattle([ Species.CHARIZARD, Species.BLASTOISE ]); + + const move = allMoves[Moves.RAGE_FIST]; + vi.spyOn(move, "calculateBattlePower"); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + game.doSelectPartyPokemon(1); + await game.toNextWave(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + game.doSelectPartyPokemon(0); + await game.phaseInterceptor.to("CommandPhase"); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(Species.CHARIZARD); + }); +});