mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-16 21:32:18 +02:00
fully implement gorilla tactics
This commit is contained in:
parent
01eb05469a
commit
6b82907b2e
@ -1624,6 +1624,40 @@ export class PostAttackAbAttr extends AbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ability attribute for Gorilla Tactics
|
||||||
|
* @extends PostAttackAbAttr
|
||||||
|
*/
|
||||||
|
export class GorillaTacticsAbAttr extends PostAttackAbAttr {
|
||||||
|
constructor() {
|
||||||
|
super((user, target, move) => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability
|
||||||
|
* @param passive n/a
|
||||||
|
* @param simulated whether the ability is being simulated
|
||||||
|
* @param defender n/a
|
||||||
|
* @param move n/a
|
||||||
|
* @param hitResult n/a
|
||||||
|
* @param args n/a
|
||||||
|
* @returns `true` if the ability is applied
|
||||||
|
*/
|
||||||
|
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
|
||||||
|
if (simulated) {
|
||||||
|
return simulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pokemon.getTag(BattlerTagType.GORILLA_TACTICS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pokemon.addTag(BattlerTagType.GORILLA_TACTICS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||||
private stealCondition: PokemonAttackCondition | null;
|
private stealCondition: PokemonAttackCondition | null;
|
||||||
|
|
||||||
@ -5595,7 +5629,7 @@ export function initAbilities() {
|
|||||||
.bypassFaint()
|
.bypassFaint()
|
||||||
.partial(),
|
.partial(),
|
||||||
new Ability(Abilities.GORILLA_TACTICS, 8)
|
new Ability(Abilities.GORILLA_TACTICS, 8)
|
||||||
.unimplemented(),
|
.attr(GorillaTacticsAbAttr),
|
||||||
new Ability(Abilities.NEUTRALIZING_GAS, 8)
|
new Ability(Abilities.NEUTRALIZING_GAS, 8)
|
||||||
.attr(SuppressFieldAbilitiesAbAttr)
|
.attr(SuppressFieldAbilitiesAbAttr)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
|
@ -107,8 +107,8 @@ export interface TerrainBattlerTag {
|
|||||||
* to select restricted moves.
|
* to select restricted moves.
|
||||||
*/
|
*/
|
||||||
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
||||||
constructor(tagType: BattlerTagType, turnCount: integer, sourceMove?: Moves, sourceId?: integer) {
|
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: integer, sourceMove?: Moves, sourceId?: integer) {
|
||||||
super(tagType, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], turnCount, sourceMove, sourceId);
|
super(tagType, lapseType, turnCount, sourceMove, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@ -119,7 +119,9 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
|||||||
const move = phase.move;
|
const move = phase.move;
|
||||||
|
|
||||||
if (this.isMoveRestricted(move.moveId)) {
|
if (this.isMoveRestricted(move.moveId)) {
|
||||||
pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId));
|
if (this.interruptedText(pokemon, move.moveId)) {
|
||||||
|
pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId));
|
||||||
|
}
|
||||||
phase.cancel();
|
phase.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +157,9 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
|||||||
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
|
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
|
||||||
* @returns {string} text to display when the move is interrupted
|
* @returns {string} text to display when the move is interrupted
|
||||||
*/
|
*/
|
||||||
abstract interruptedText(pokemon: Pokemon, move: Moves): string;
|
interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -167,7 +171,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||||||
private moveId: Moves = Moves.NONE;
|
private moveId: Moves = Moves.NONE;
|
||||||
|
|
||||||
constructor(sourceId: number) {
|
constructor(sourceId: number) {
|
||||||
super(BattlerTagType.DISABLED, 4, Moves.DISABLE, sourceId);
|
super(BattlerTagType.DISABLED, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 4, Moves.DISABLE, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@ -178,7 +182,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*
|
*
|
||||||
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@link moveId} and shows a message.
|
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@linkcode moveId} and shows a message.
|
||||||
* Otherwise the move ID will not get assigned and this tag will get removed next turn.
|
* Otherwise the move ID will not get assigned and this tag will get removed next turn.
|
||||||
*/
|
*/
|
||||||
override onAdd(pokemon: Pokemon): void {
|
override onAdd(pokemon: Pokemon): void {
|
||||||
@ -207,8 +211,12 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||||||
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name });
|
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/**
|
||||||
override interruptedText(pokemon: Pokemon, move: Moves): string {
|
* @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
|
||||||
|
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
|
||||||
|
* @returns {string} text to display when the move is interrupted
|
||||||
|
*/
|
||||||
|
interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||||
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
|
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +227,73 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used by Gorilla Tactics to restrict the user to using only one move.
|
||||||
|
* @extends MoveRestrictionBattlerTag
|
||||||
|
*/
|
||||||
|
export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
||||||
|
private moveId = Moves.NONE;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override isMoveRestricted(move: Moves): boolean {
|
||||||
|
return move !== this.moveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @param {Pokemon} pokemon the {@linkcode Pokemon} to check if the tag can be added
|
||||||
|
* @returns `true` if the pokemon has a valid move and no existing {@linkcode GorillaTacticsTag}; `false` otherwise
|
||||||
|
*/
|
||||||
|
override canAdd(pokemon: Pokemon): boolean {
|
||||||
|
return (this.getLastValidMove(pokemon) !== undefined) && !pokemon.getTag(GorillaTacticsTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that move history exists on {@linkcode Pokemon} and has a valid move.
|
||||||
|
* If so, sets the {@linkcode moveId} and increases the user's Attack by one stage.
|
||||||
|
* @override
|
||||||
|
* @param {Pokemon} pokemon the {@linkcode Pokemon} to add the tag to
|
||||||
|
*/
|
||||||
|
override onAdd(pokemon: Pokemon): void {
|
||||||
|
const lastValidMove = this.getLastValidMove(pokemon);
|
||||||
|
|
||||||
|
if (!lastValidMove) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveId = lastValidMove;
|
||||||
|
pokemon.setStatStage(Stat.ATK, 1);
|
||||||
|
pokemon.updateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @param {Pokemon} pokemon n/a
|
||||||
|
* @param {Moves} move {@linkcode Moves} ID of the move being denied
|
||||||
|
* @returns {string} text to display when the move is denied
|
||||||
|
*/
|
||||||
|
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
|
||||||
|
return i18next.t("battle:canOnlyUseMove", { moveName: allMoves[this.moveId].name, pokemonName: getPokemonNameWithAffix(pokemon) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last valid move from the pokemon's move history.
|
||||||
|
* @param {Pokemon} pokemon {@linkcode Pokemon} to get the last valid move from
|
||||||
|
* @returns {Moves | undefined} the last valid move from the pokemon's move history
|
||||||
|
*/
|
||||||
|
getLastValidMove(pokemon: Pokemon): Moves | undefined {
|
||||||
|
const move = pokemon.getLastXMoves()
|
||||||
|
.find(m => m.move !== Moves.NONE && m.move !== Moves.STRUGGLE && !m.virtual);
|
||||||
|
|
||||||
|
return move?.move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BattlerTag that represents the "recharge" effects of moves like Hyper Beam.
|
* BattlerTag that represents the "recharge" effects of moves like Hyper Beam.
|
||||||
*/
|
*/
|
||||||
@ -2125,6 +2200,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.GORILLA_TACTICS:
|
||||||
|
return new GorillaTacticsTag();
|
||||||
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);
|
||||||
|
@ -73,4 +73,5 @@ export enum BattlerTagType {
|
|||||||
SHELL_TRAP = "SHELL_TRAP",
|
SHELL_TRAP = "SHELL_TRAP",
|
||||||
DRAGON_CHEER = "DRAGON_CHEER",
|
DRAGON_CHEER = "DRAGON_CHEER",
|
||||||
NO_RETREAT = "NO_RETREAT",
|
NO_RETREAT = "NO_RETREAT",
|
||||||
|
GORILLA_TACTICS = "GORILLA_TACTICS",
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
"moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.",
|
"moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.",
|
||||||
"moveNoPP": "There's no PP left for\nthis move!",
|
"moveNoPP": "There's no PP left for\nthis move!",
|
||||||
"moveDisabled": "{{moveName}} is disabled!",
|
"moveDisabled": "{{moveName}} is disabled!",
|
||||||
|
"canOnlyUseMove": "{{pokemonName}} can only use {{moveName}}!",
|
||||||
"disableInterruptedMove": "{{pokemonNameWithAffix}}'s {{moveName}}\nis disabled!",
|
"disableInterruptedMove": "{{pokemonNameWithAffix}}'s {{moveName}}\nis disabled!",
|
||||||
"noPokeballForce": "An unseen force\nprevents using Poké Balls.",
|
"noPokeballForce": "An unseen force\nprevents using Poké Balls.",
|
||||||
"noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!",
|
"noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!",
|
||||||
|
87
src/test/abilities/gorilla_tactics.test.ts
Normal file
87
src/test/abilities/gorilla_tactics.test.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Moves } from "#app/enums/moves";
|
||||||
|
import { Species } from "#app/enums/species";
|
||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Gorilla Tactics", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyLevel(30)
|
||||||
|
.moveset([Moves.SPLASH, Moves.TACKLE])
|
||||||
|
.ability(Abilities.GORILLA_TACTICS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("boosts the Pokémon's Attack by 50%, but limits the Pokémon to using only one move", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.GALAR_DARMANITAN]);
|
||||||
|
|
||||||
|
const darmanitan = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(darmanitan.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
// Other moves should be restricted
|
||||||
|
expect(darmanitan.isMoveRestricted(Moves.TACKLE)).toBe(true);
|
||||||
|
expect(darmanitan.isMoveRestricted(Moves.SPLASH)).toBe(false);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should struggle if the only usable move is disabled", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.GALAR_DARMANITAN]);
|
||||||
|
|
||||||
|
const darmanitan = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(darmanitan.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
// Other moves should be restricted
|
||||||
|
expect(darmanitan.isMoveRestricted(Moves.TACKLE)).toBe(true);
|
||||||
|
expect(darmanitan.isMoveRestricted(Moves.SPLASH)).toBe(false);
|
||||||
|
|
||||||
|
// Turn where Tackle is interrupted by Disable
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.DISABLE));
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
|
||||||
|
// Turn where Struggle is used
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp());
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user