From 8a355d500a27ca8c74482665a8680528007ab727 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:30:04 -0700 Subject: [PATCH] [Bug] Move Restriction Battler Tag bugs (#4536) * Added fixes * Revert "Added fixes" This reverts commit 3feccd792ddef0b32ddc40782b60f23f952cad23. * Added loadTag functions * Fixes * typeodcs * Torment * yawn * hsldklahdlhalhdlahldhlah * Imprison Fixes * Fixed imprison not interrupting PRE_MOVE * just kidding * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fixing what broke * added scp[es * missed a scope * Update src/data/battler-tags.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * good tp go * merge * battler tags * Apply suggestions from code review * Changed function names * publics --------- Co-authored-by: frutescens Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/arena-tag.ts | 68 ++++++++++++++++++++------------- src/data/battler-tags.ts | 75 ++++++++++++++++++++++++------------- src/data/move.ts | 3 +- src/field/pokemon.ts | 8 ++-- src/phases/command-phase.ts | 4 +- 5 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 11d28ab7e25..71cf11fa06f 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -4,7 +4,7 @@ import { Type } from "#app/data/type"; import * as Utils from "#app/utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move"; import { getPokemonNameWithAffix } from "#app/messages"; -import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "#app/field/pokemon"; +import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#app/data/status-effect"; import { BattlerIndex } from "#app/battle"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; @@ -71,6 +71,32 @@ export abstract class ArenaTag { this.sourceId = source.sourceId; this.side = source.side; } + + /** + * Helper function that retrieves the source Pokemon + * @param scene medium to retrieve the source Pokemon + * @returns The source {@linkcode Pokemon} or `null` if none is found + */ + public getSourcePokemon(scene: BattleScene): Pokemon | null { + return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + } + + /** + * Helper function that retrieves the Pokemon affected + * @param scene - medium to retrieve the involved Pokemon + * @returns list of PlayerPokemon or EnemyPokemon on the field + */ + public getAffectedPokemon(scene: BattleScene): Pokemon[] { + switch (this.side) { + case ArenaTagSide.PLAYER: + return scene.getPlayerField() ?? []; + case ArenaTagSide.ENEMY: + return scene.getEnemyField() ?? []; + case ArenaTagSide.BOTH: + default: + return scene.getField(true) ?? []; + } + } } /** @@ -978,36 +1004,24 @@ class NoneTag extends ArenaTag { * Imprison will apply to any opposing Pokemon that switch onto the field as well. */ class ImprisonTag extends ArenaTrapTag { - private source: Pokemon; - constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } - /** - * Helper function that retrieves the Pokemon affected - * @param {BattleScene} scene medium to retrieve the involved Pokemon - * @returns list of PlayerPokemon or EnemyPokemon on the field - */ - private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { - if (!this.source.isPlayer()) { - return scene.getPlayerField() ?? []; - } - return scene.getEnemyField() ?? []; - } - /** * This function applies the effects of Imprison to the opposing Pokemon already present on the field. * @param arena */ override onAdd({ scene }: Arena) { - this.source = scene.getPokemonById(this.sourceId!)!; - if (this.source) { - const party = this.retrieveField(scene); - party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { - p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + const source = this.getSourcePokemon(scene); + if (source) { + const party = this.getAffectedPokemon(scene); + party?.forEach((p: Pokemon ) => { + if (p.isAllowedInBattle()) { + p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + } }); - scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(this.source) })); + scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } } @@ -1016,8 +1030,9 @@ class ImprisonTag extends ArenaTrapTag { * @param _arena * @returns `true` if the source of the tag is still active on the field | `false` if not */ - override lapse(_arena: Arena): boolean { - return this.source.isActive(true); + override lapse({ scene }: Arena): boolean { + const source = this.getSourcePokemon(scene); + return source ? source.isActive(true) : false; } /** @@ -1026,7 +1041,8 @@ class ImprisonTag extends ArenaTrapTag { * @returns `true` */ override activateTrap(pokemon: Pokemon): boolean { - if (this.source.isActive(true)) { + const source = this.getSourcePokemon(pokemon.scene); + if (source && source.isActive(true) && pokemon.isAllowedInBattle()) { pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); } return true; @@ -1037,8 +1053,8 @@ class ImprisonTag extends ArenaTrapTag { * @param arena */ override onRemove({ scene }: Arena): void { - const party = this.retrieveField(scene); - party?.forEach((p: PlayerPokemon | EnemyPokemon) => { + const party = this.getAffectedPokemon(scene); + party?.forEach((p: Pokemon) => { p.removeTag(BattlerTagType.IMPRISON); }); } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 18f03ada941..a5016746013 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -23,6 +23,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; +import BattleScene from "#app/battle-scene"; export enum BattlerTagLapseType { FAINT, @@ -90,6 +91,15 @@ export class BattlerTag { this.sourceMove = source.sourceMove; this.sourceId = source.sourceId; } + + /** + * Helper function that retrieves the source Pokemon object + * @param scene medium to retrieve the source Pokemon + * @returns The source {@linkcode Pokemon} or `null` if none is found + */ + public getSourcePokemon(scene: BattleScene): Pokemon | null { + return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + } } export interface WeatherBattlerTag { @@ -120,7 +130,7 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { const phase = pokemon.scene.getCurrentPhase() as MovePhase; const move = phase.move; - if (this.isMoveRestricted(move.moveId)) { + if (this.isMoveRestricted(move.moveId, pokemon)) { if (this.interruptedText(pokemon, move.moveId)) { pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId)); } @@ -136,10 +146,11 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { /** * Gets whether this tag is restricting a move. * - * @param {Moves} move {@linkcode Moves} ID to check restriction for. - * @returns {boolean} `true` if the move is restricted by this tag, otherwise `false`. + * @param move - {@linkcode Moves} ID to check restriction for. + * @param user - The {@linkcode Pokemon} involved + * @returns `true` if the move is restricted by this tag, otherwise `false`. */ - abstract isMoveRestricted(move: Moves): boolean; + public abstract isMoveRestricted(move: Moves, user?: Pokemon): boolean; /** * Checks if this tag is restricting a move based on a user's decisions during the target selection phase @@ -327,6 +338,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); } + /** + * Loads the Gorilla Tactics Battler Tag along with its unique class variable moveId + * @override + * @param source Gorilla Tactics' {@linkcode BattlerTag} information + */ + public override loadTag(source: BattlerTag | any): void { + super.loadTag(source); + this.moveId = source.moveId; + } + /** * * @override @@ -2510,8 +2531,6 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { * Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied */ export class TormentTag extends MoveRestrictionBattlerTag { - private target: Pokemon; - constructor(sourceId: number) { super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId); } @@ -2523,7 +2542,6 @@ export class TormentTag extends MoveRestrictionBattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - this.target = pokemon; pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } @@ -2542,15 +2560,18 @@ export class TormentTag extends MoveRestrictionBattlerTag { * @param {Moves} move the move under investigation * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other */ - override isMoveRestricted(move: Moves): boolean { - const lastMove = this.target.getLastXMoves(1)[0]; + public override isMoveRestricted(move: Moves, user: Pokemon): boolean { + if (!user) { + return false; + } + const lastMove = user.getLastXMoves(1)[0]; if ( !lastMove ) { return false; } // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts const moveObj = allMoves[lastMove.move]; - const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); + const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) { return true; @@ -2602,37 +2623,39 @@ export class TauntTag extends MoveRestrictionBattlerTag { * The tag is only removed when the source-user is removed from the field. */ export class ImprisonTag extends MoveRestrictionBattlerTag { - private source: Pokemon | null; - constructor(sourceId: number) { super(BattlerTagType.IMPRISON, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 1, Moves.IMPRISON, sourceId); } - override onAdd(pokemon: Pokemon) { - if (this.sourceId) { - this.source = pokemon.scene.getPokemonById(this.sourceId); - } - } - /** * Checks if the source of Imprison is still active - * @param _pokemon - * @param _lapseType + * @override + * @param pokemon The pokemon this tag is attached to * @returns `true` if the source is still active */ - override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - return this.source?.isActive(true) ?? false; + public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + const source = this.getSourcePokemon(pokemon.scene); + if (source) { + if (lapseType === BattlerTagLapseType.PRE_MOVE) { + return super.lapse(pokemon, lapseType) && source.isActive(true); + } else { + return source.isActive(true); + } + } + return false; } /** * Checks if the source of the tag has the parameter move in its moveset and that the source is still active + * @override * @param {Moves} move the move under investigation * @returns `false` if either condition is not met */ - override isMoveRestricted(move: Moves): boolean { - if (this.source) { - const sourceMoveset = this.source.getMoveset().map(m => m!.moveId); - return sourceMoveset?.includes(move) && this.source.isActive(true); + public override isMoveRestricted(move: Moves, user: Pokemon): boolean { + const source = this.getSourcePokemon(user.scene); + if (source) { + const sourceMoveset = source.getMoveset().map(m => m!.moveId); + return sourceMoveset?.includes(move) && source.isActive(true); } return false; } diff --git a/src/data/move.ts b/src/data/move.ts index 8e9977337cc..d4f3b2ce3ee 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7883,7 +7883,8 @@ export function initMoves() { .attr(SwitchAbilitiesAttr), new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3) .ignoresSubstitute() - .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false), + .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false) + .target(MoveTarget.ENEMY_SIDE), new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN) .condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 136c1eb1685..9ae83753e62 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3062,8 +3062,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @see {@linkcode MoveRestrictionBattlerTag} */ - isMoveRestricted(moveId: Moves): boolean { - return this.getRestrictingTag(moveId) !== null; + public isMoveRestricted(moveId: Moves, pokemon?: Pokemon): boolean { + return this.getRestrictingTag(moveId, pokemon) !== null; } /** @@ -3096,7 +3096,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null { for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { - if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) { + if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId, user)) { return tag as MoveRestrictionBattlerTag; } else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return tag as MoveRestrictionBattlerTag; @@ -5139,7 +5139,7 @@ export class PokemonMove { * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. */ isUsable(pokemon: Pokemon, ignorePp: boolean = false, ignoreRestrictionTags: boolean = false): boolean { - if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId)) { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { return false; } diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index e85c66543ac..cf66631bd96 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -114,8 +114,8 @@ export class CommandPhase extends FieldPhase { // Decides between a Disabled, Not Implemented, or No PP translation message const errorMessage = - playerPokemon.isMoveRestricted(move.moveId) - ? playerPokemon.getRestrictingTag(move.moveId)!.selectionDeniedText(playerPokemon, move.moveId) + playerPokemon.isMoveRestricted(move.moveId, playerPokemon) + ? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId) : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator