[Move] Implement Rage

This commit is contained in:
Mason 2024-08-20 11:02:50 -04:00
parent c846f552bb
commit 9e018f4fe4
6 changed files with 137 additions and 2 deletions

View File

@ -205,6 +205,24 @@ export class ShellTrapTag extends BattlerTag {
} }
} }
export class RageTag extends BattlerTag {
constructor() {
super(BattlerTagType.RAGE,[BattlerTagLapseType.MOVE_EFFECT],1,Moves.RAGE);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.MOVE_EFFECT) {
return (pokemon.scene.getCurrentPhase() as MovePhase).move.getMove().id === Moves.RAGE;
} else if (lapseType === BattlerTagLapseType.CUSTOM) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene,pokemon.getBattlerIndex(),true,[BattleStat.ATK],1,false));
pokemon.scene.queueMessage(i18next.t("battlerTags:rageOnHit", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}));
return true;
}
return false;
}
}
export class TrappedTag extends BattlerTag { export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) { constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
super(tagType, lapseType, turnCount, sourceMove, sourceId); super(tagType, lapseType, turnCount, sourceMove, sourceId);
@ -1962,6 +1980,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.GULP_MISSILE_ARROKUDA: case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU: case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove); return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.RAGE:
return new RageTag();
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -6450,7 +6450,7 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.SPD, 2, true), .attr(StatChangeAttr, BattleStat.SPD, 2, true),
new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1), new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1),
new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1) new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1)
.partial(), .attr(AddBattlerTagAttr,BattlerTagType.RAGE,true,false,0,0,false,true),
new SelfStatusMove(Moves.TELEPORT, Type.PSYCHIC, -1, 20, -1, -6, 1) new SelfStatusMove(Moves.TELEPORT, Type.PSYCHIC, -1, 20, -1, -6, 1)
.attr(ForceSwitchOutAttr, true) .attr(ForceSwitchOutAttr, true)
.hidesUser(), .hidesUser(),

View File

@ -69,5 +69,6 @@ export enum BattlerTagType {
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA", GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU", GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING", BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING",
SHELL_TRAP = "SHELL_TRAP" SHELL_TRAP = "SHELL_TRAP",
RAGE = "RAGE"
} }

View File

@ -70,4 +70,5 @@ export const battlerTags: SimpleTranslationEntries = {
"cursedOnAdd": "{{pokemonNameWithAffix}} cut its own HP and put a curse on the {{pokemonName}}!", "cursedOnAdd": "{{pokemonNameWithAffix}} cut its own HP and put a curse on the {{pokemonName}}!",
"cursedLapse": "{{pokemonNameWithAffix}} is afflicted by the Curse!", "cursedLapse": "{{pokemonNameWithAffix}} is afflicted by the Curse!",
"stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!", "stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!",
"rageOnHit": "{{pokemonNameWithAffix}}'s rage is building"
} as const; } as const;

View File

@ -262,6 +262,9 @@ export class MoveEffectPhase extends PokemonPhase {
if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) {
target.lapseTag(BattlerTagType.SHELL_TRAP); target.lapseTag(BattlerTagType.SHELL_TRAP);
} }
if (hitResult < HitResult.NO_EFFECT && move.category !== MoveCategory.STATUS) {
target.lapseTag(BattlerTagType.RAGE);
}
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
} }

110
src/test/moves/rage.test.ts Normal file
View File

@ -0,0 +1,110 @@
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "#test/utils/gameManager";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import {BattleStat} from "#app/data/battle-stat";
const TIMEOUT = 20 * 1000;
describe("Moves - Rage", () => {
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")
.ability(Abilities.UNNERVE)
.moveset([Moves.RAGE,Moves.SPLASH,Moves.SPORE])
.enemyAbility(Abilities.INSOMNIA)
.startingLevel(100)
.enemyLevel(100);
});
it(
"should raise attack if hit after use",
async () => {
game.override
.enemySpecies(Species.SHUCKLE)
.enemyMoveset([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle([Species.NINJASK]);
const leadPokemon = game.scene.getPlayerPokemon()!;
// Ninjask uses rage, then gets hit, gets atk boost
game.doAttack(0);
await game.toNextTurn();
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(1);
}, TIMEOUT
);
it(
"should raise ATK if hit before using non-rage option",
async () => {
game.override
.enemySpecies(Species.NINJASK)
.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
await game.startBattle([Species.SHUCKLE]);
const leadPokemon = game.scene.getPlayerPokemon()!;
// Ninjask moves first, THEN shuckle uses rage, no ATK boost
game.doAttack(0);
await game.toNextTurn();
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
// Shuckle Raged last turn, so when Ninjask hits it, ATK boost despite not using rage this turn
game.doAttack(1);
await game.toNextTurn();
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(1);
}, TIMEOUT
);
it(
"should not raise ATK if hit by status move",
async () => {
game.override
.enemySpecies(Species.NINJASK)
.enemyMoveset([Moves.RAGE, Moves.RAGE, Moves.RAGE, Moves.RAGE]);
await game.startBattle([Species.NINJASK]);
const leadPokemon = game.scene.getPlayerPokemon()!;
// Ninjask Rages, then slept. No boost.
game.doAttack(2);
await game.toNextTurn();
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
}, TIMEOUT
);
it(
"should not raise ATK if rage has no effect",
async () => {
game.override
.enemySpecies(Species.GASTLY)
.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE])
.moveset([Moves.RAGE]);
await game.startBattle([Species.NINJASK]);
const leadPokemon = game.scene.getPlayerPokemon()!;
// Ninjask uses rage, but it has no effect, no ATK boost
game.doAttack(0);
await game.toNextTurn();
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
}, TIMEOUT
);
});