Skip to content

Commit

Permalink
[Move] Improve implementation of Rage Fist damage increase (#5129)
Browse files Browse the repository at this point in the history
* implementation of rage fist

* Apply suggestions from code review

Co-authored-by: NightKev <[email protected]>

* Update src/test/moves/rage_fist.test.ts

Co-authored-by: NightKev <[email protected]>

* added removed TODO from some test cases

* Apply suggestions from code review

Added changes to documentation and cleaning up code

Co-authored-by: NightKev <[email protected]>

* added protected to updateHitReceivedCount()

---------

Co-authored-by: NightKev <[email protected]>
Co-authored-by: damocleas <[email protected]>
  • Loading branch information
3 people authored Jan 19, 2025
1 parent 75e66b4 commit f3256ec
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 5 deletions.
10 changes: 9 additions & 1 deletion src/data/custom-pokemon-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ 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;
public ability: Abilities | -1;
public passive: Abilities | -1;
public nature: Nature | -1;
public types: Type[];
/** `hitsReceivedCount` 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<CustomPokemonData>) {
if (!isNullOrUndefined(data)) {
Expand All @@ -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;
}

resetHitReceivedCount(): void {
this.hitsRecCount = 0;
}
}
28 changes: 24 additions & 4 deletions src/data/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3993,12 +3993,32 @@ 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 { hitCount, prevHitCount } = user.battleData;
const basePower: Utils.NumberHolder = args[0];

this.updateHitReceivedCount(user, hitCount, prevHitCount);

basePower.value = 50 + (Math.min(user.customPokemonData.hitsRecCount, 6) * 50);

return true;
}

/**
* Updates the number of hits the Pokemon has taken in battle
* @param user Pokemon calling Rage Fist
* @param hitCount The number of received hits this battle
* @param previousHitCount The number of received hits this battle since last time Rage Fist was used
*/
protected updateHitReceivedCount(user: Pokemon, hitCount: number, previousHitCount: number): void {
user.customPokemonData.hitsRecCount += (hitCount - previousHitCount);
user.battleData.prevHitCount = hitCount;
}
}

/**
Expand Down Expand Up @@ -10991,8 +11011,8 @@ export function initMoves() {
new AttackMove(Moves.TWIN_BEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9)
.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)
.edgeCase() // Counter incorrectly increases on confusion self-hits
.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),
Expand Down
3 changes: 3 additions & 0 deletions src/field/pokemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5282,7 +5282,10 @@ export class PokemonSummonData {
}

export class PokemonBattleData {
/** counts the hits the pokemon received */
public hitCount: number = 0;
/** used for {@linkcode Moves.RAGE_FIST} in order to save hit Counts received before Rage Fist is applied */
public prevHitCount: number = 0;
public endured: boolean = false;
public berriesEaten: BerryType[] = [];
public abilitiesApplied: Abilities[] = [];
Expand Down
6 changes: 6 additions & 0 deletions src/phases/encounter-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.resetHitReceivedCount();
}
}
battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here?
} else {
let enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true);
Expand Down
1 change: 1 addition & 0 deletions src/phases/new-biome-encounter-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
for (const pokemon of globalScene.getPlayerParty()) {
if (pokemon) {
pokemon.resetBattleData();
pokemon.customPokemonData.resetHitReceivedCount();
}
}

Expand Down
143 changes: 143 additions & 0 deletions src/test/moves/rage_fist.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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;
const move = allMoves[Moves.RAGE_FIST];

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);

vi.spyOn(move, "calculateBattlePower");
});

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 ]);

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 ]);

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 ]);

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 ]);

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);
});

it("should reset the hitRecCounter if we enter new biome", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(10);

await game.classicMode.startBattle([ Species.MAGIKARP ]);

game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();

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 reset the hitRecCounter if switched out", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(1)
.enemyMoveset(Moves.TACKLE);

await game.classicMode.startBattle([ Species.CHARIZARD, Species.BLASTOISE ]);

game.move.select(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();

game.doSwitchPokemon(1);
await game.toNextTurn();

game.doSwitchPokemon(1);
await game.toNextTurn();

game.move.select(Moves.RAGE_FIST);
await game.phaseInterceptor.to("MoveEndPhase");

expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(Species.CHARIZARD);
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
});
});

0 comments on commit f3256ec

Please sign in to comment.