[Move] Fully implement Throat Chop (#4115)
* fully implement throat chop * add linkcode in docs * address comments * update test
This commit is contained in:
parent
a88b989939
commit
3c05237b2e
|
@ -107,8 +107,8 @@ export interface TerrainBattlerTag {
|
|||
* to select restricted moves.
|
||||
*/
|
||||
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
||||
constructor(tagType: BattlerTagType, turnCount: integer, sourceMove?: Moves, sourceId?: integer) {
|
||||
super(tagType, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], turnCount, sourceMove, sourceId);
|
||||
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: integer, sourceMove?: Moves, sourceId?: integer) {
|
||||
super(tagType, lapseType, turnCount, sourceMove, sourceId);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
@ -158,6 +158,49 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
|||
abstract interruptedText(pokemon: Pokemon, move: Moves): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag representing the "Throat Chop" effect. Pokemon with this tag cannot use sound-based moves.
|
||||
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Throat_Chop_(move) | Throat Chop}
|
||||
* @extends MoveRestrictionBattlerTag
|
||||
*/
|
||||
export class ThroatChoppedTag extends MoveRestrictionBattlerTag {
|
||||
constructor() {
|
||||
super(BattlerTagType.THROAT_CHOPPED, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.PRE_MOVE ], 2, Moves.THROAT_CHOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a {@linkcode Moves | move} is restricted by Throat Chop.
|
||||
* @override
|
||||
* @param {Moves} move the {@linkcode Moves | move} to check for sound-based restriction
|
||||
* @returns true if the move is sound-based
|
||||
*/
|
||||
override isMoveRestricted(move: Moves): boolean {
|
||||
return allMoves[move].hasFlag(MoveFlags.SOUND_BASED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a message when the player attempts to select a move that is restricted by Throat Chop.
|
||||
* @override
|
||||
* @param {Pokemon} pokemon the {@linkcode Pokemon} that is attempting to select the restricted move
|
||||
* @param {Moves} move the {@linkcode Moves | move} that is being restricted
|
||||
* @returns the message to display when the player attempts to select the restricted move
|
||||
*/
|
||||
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a message when a move is interrupted by Throat Chop.
|
||||
* @override
|
||||
* @param {Pokemon} pokemon the interrupted {@linkcode Pokemon}
|
||||
* @param {Moves} move the {@linkcode Moves | move} that was interrupted
|
||||
* @returns the message to display when the move is interrupted
|
||||
*/
|
||||
override interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:throatChopInterruptedMove", { pokemonName: getPokemonNameWithAffix(pokemon) });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag representing the "disabling" effect performed by {@linkcode Moves.DISABLE} and {@linkcode Abilities.CURSED_BODY}.
|
||||
* When the tag is added, the last-used move of the tag holder is set as the disabled move.
|
||||
|
@ -167,7 +210,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||
private moveId: Moves = Moves.NONE;
|
||||
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.DISABLED, 4, Moves.DISABLE, sourceId);
|
||||
super(BattlerTagType.DISABLED, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 4, Moves.DISABLE, sourceId);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
@ -2158,6 +2201,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||
return new GulpMissileTag(tagType, sourceMove);
|
||||
case BattlerTagType.TAR_SHOT:
|
||||
return new TarShotTag();
|
||||
case BattlerTagType.THROAT_CHOPPED:
|
||||
return new ThroatChoppedTag();
|
||||
case BattlerTagType.NONE:
|
||||
default:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
|
|
|
@ -8404,7 +8404,7 @@ export function initMoves() {
|
|||
.target(MoveTarget.USER_AND_ALLIES)
|
||||
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))),
|
||||
new AttackMove(Moves.THROAT_CHOP, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
|
||||
.partial(),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.THROAT_CHOPPED),
|
||||
new AttackMove(Moves.POLLEN_PUFF, Type.BUG, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7)
|
||||
.attr(StatusCategoryOnAllyAttr)
|
||||
.attr(HealOnAllyAttr, 0.5, true, false)
|
||||
|
|
|
@ -73,5 +73,6 @@ export enum BattlerTagType {
|
|||
SHELL_TRAP = "SHELL_TRAP",
|
||||
DRAGON_CHEER = "DRAGON_CHEER",
|
||||
NO_RETREAT = "NO_RETREAT",
|
||||
THROAT_CHOPPED = "THROAT_CHOPPED",
|
||||
TAR_SHOT = "TAR_SHOT",
|
||||
}
|
||||
|
|
|
@ -44,7 +44,9 @@
|
|||
"moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.",
|
||||
"moveNoPP": "There's no PP left for\nthis move!",
|
||||
"moveDisabled": "{{moveName}} is disabled!",
|
||||
"moveCannotBeSelected": "{{moveName}} cannot be selected!",
|
||||
"disableInterruptedMove": "{{pokemonNameWithAffix}}'s {{moveName}}\nis disabled!",
|
||||
"throatChopInterruptedMove": "The effects of Throat Chop prevent\n{{pokemonName}} from using certain moves!",
|
||||
"noPokeballForce": "An unseen force\nprevents using Poké Balls.",
|
||||
"noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!",
|
||||
"noPokeballMulti": "You can only throw a Poké Ball\nwhen there is one Pokémon remaining!",
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
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 Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||
|
||||
describe("Moves - Throat Chop", () => {
|
||||
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
|
||||
.moveset(Array(4).fill(Moves.GROWL))
|
||||
.battleType("single")
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Array(4).fill(Moves.THROAT_CHOP))
|
||||
.enemySpecies(Species.MAGIKARP);
|
||||
});
|
||||
|
||||
it("prevents the target from using sound-based moves for two turns", async () => {
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
|
||||
// First turn, move is interrupted
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0);
|
||||
|
||||
// Second turn, struggle if no valid moves
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
expect(game.scene.getEnemyPokemon()!.isFullHp()).toBe(false);
|
||||
}, TIMEOUT);
|
||||
});
|
Loading…
Reference in New Issue