From efe052bc1da4025a548d017141feef590b58d543 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Fri, 30 Aug 2024 17:02:16 -0700 Subject: [PATCH] Fix secondary effects to trapping moves not applying to Ghost types --- src/data/battler-tags.ts | 23 ++++++++++++++++-- src/data/move.ts | 2 +- src/enums/battler-tag-type.ts | 3 ++- src/field/pokemon.ts | 19 +++++++++++++-- src/phases/command-phase.ts | 40 +++++++++++++------------------ src/phases/enemy-command-phase.ts | 8 +------ 6 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 8c05d296e76..f4203459c82 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -212,7 +212,7 @@ export class TrappedTag extends BattlerTag { canAdd(pokemon: Pokemon): boolean { const isGhost = pokemon.isOfType(Type.GHOST); - const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED); + const isTrapped = pokemon.getTag(TrappedTag); return !isTrapped && !isGhost; } @@ -245,6 +245,23 @@ export class TrappedTag extends BattlerTag { } } +/** + * BattlerTag implementing No Retreat's trapping effect. + * This is treated separately from other trapping effects to prevent + * Ghost-type Pokemon from being able to reuse the move. + * @extends TrappedTag + */ +class NoRetreatTag extends TrappedTag { + constructor(sourceId: number) { + super(BattlerTagType.NO_RETREAT, BattlerTagLapseType.CUSTOM, 0, Moves.NO_RETREAT, sourceId); + } + + /** overrides {@linkcode TrappedTag.apply}, removing the Ghost-type condition */ + canAdd(pokemon: Pokemon): boolean { + return !pokemon.getTag(TrappedTag); + } +} + /** * BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Flinch Flinch} status condition */ @@ -864,7 +881,7 @@ export abstract class DamagingTrapTag extends TrappedTag { } canAdd(pokemon: Pokemon): boolean { - return !pokemon.isOfType(Type.GHOST) && !pokemon.findTag(t => t instanceof DamagingTrapTag); + return !pokemon.getTag(TrappedTag); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -1864,6 +1881,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new DrowsyTag(); case BattlerTagType.TRAPPED: return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.NO_RETREAT: + return new NoRetreatTag(sourceId); case BattlerTagType.BIND: return new BindTag(turnCount, sourceId); case BattlerTagType.WRAP: diff --git a/src/data/move.ts b/src/data/move.ts index d50dc7e2074..f14bad4c805 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -8502,7 +8502,7 @@ export function initMoves() { .partial(), new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, false, 1) + .attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false) .condition((user, target, move) => user.getTag(TrappedTag)?.sourceMove !== Moves.NO_RETREAT), // fails if the user is currently trapped by No Retreat new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8) .attr(StatChangeAttr, BattleStat.SPD, -1) diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index b133b442801..5cebacd43fc 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -69,5 +69,6 @@ export enum BattlerTagType { GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA", GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU", BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING", - SHELL_TRAP = "SHELL_TRAP" + SHELL_TRAP = "SHELL_TRAP", + NO_RETREAT = "NO_RETREAT" } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6df486aab39..c3170a15aa7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -18,11 +18,11 @@ import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { BattleStat } from "../data/battle-stat"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag, TrappedTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; import { TempBattleStat } from "../data/temp-battle-stat"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr } from "../data/ability"; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; import { Mode } from "../ui/ui"; @@ -1208,6 +1208,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag)); } + isTrapped(trappedAbMessages?: string[], simulated: boolean = true): boolean { + if (this.isOfType(Type.GHOST)) { + return false; + } + + const trappedMessages = trappedAbMessages ?? []; + const trappedByAbility = new Utils.BooleanHolder(false); + + this.scene.getEnemyField()!.forEach(enemyPokemon => + applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trappedByAbility, this, trappedMessages, simulated) + ); + + return (trappedByAbility.value || !!this.getTag(TrappedTag)); + } + /** * Calculates the type of a move when used by this Pokemon after * type-changing move and ability attributes have applied. diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 68ede826d95..67693d873aa 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -1,10 +1,8 @@ import BattleScene from "#app/battle-scene.js"; import { TurnCommand, BattleType } from "#app/battle.js"; -import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js"; import { TrappedTag, EncoreTag } from "#app/data/battler-tags.js"; import { MoveTargetSet, getMoveTargets } from "#app/data/move.js"; import { speciesStarters } from "#app/data/pokemon-species.js"; -import { Type } from "#app/data/type.js"; import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Biome } from "#app/enums/biome.js"; @@ -15,7 +13,6 @@ import { getPokemonNameWithAffix } from "#app/messages.js"; import { Command } from "#app/ui/command-ui-handler.js"; import { Mode } from "#app/ui/ui.js"; import i18next from "i18next"; -import * as Utils from "#app/utils.js"; import { FieldPhase } from "./field-phase"; import { SelectTargetPhase } from "./select-target-phase"; @@ -77,7 +74,6 @@ export class CommandPhase extends FieldPhase { handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; - const enemyField = this.scene.getEnemyField(); let success: boolean; switch (command) { @@ -184,14 +180,9 @@ export class CommandPhase extends FieldPhase { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { - const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; - const trapped = new Utils.BooleanHolder(false); const batonPass = isSwitch && args[0] as boolean; const trappedAbMessages: string[] = []; - if (!batonPass) { - enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true)); - } - if (batonPass || (!trapTag && !trapped.value)) { + if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } : { command: Command.RUN }; @@ -199,14 +190,27 @@ export class CommandPhase extends FieldPhase { if (!isSwitch && this.fieldIndex) { this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; } - } else if (trapTag) { - if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) { - success = true; + } else if (trappedAbMessages.length > 0) { + if (!isSwitch) { + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText(trappedAbMessages[0], null, () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } else { + const trapTag = playerPokemon.getTag(TrappedTag); + + // trapTag should be defined at this point, but just in case... + if (!trapTag) { this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } : { command: Command.RUN }; break; } + if (!isSwitch) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); @@ -224,16 +228,6 @@ export class CommandPhase extends FieldPhase { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); } }, null, true); - } else if (trapped.value && trappedAbMessages.length > 0) { - if (!isSwitch) { - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText(trappedAbMessages[0], null, () => { - this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - }, null, true); } } break; diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 0b62fcbe813..82450083f59 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -1,9 +1,6 @@ import BattleScene from "#app/battle-scene.js"; import { BattlerIndex } from "#app/battle.js"; -import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js"; -import { TrappedTag } from "#app/data/battler-tags.js"; import { Command } from "#app/ui/command-ui-handler.js"; -import * as Utils from "#app/utils.js"; import { FieldPhase } from "./field-phase"; /** @@ -45,10 +42,7 @@ export class EnemyCommandPhase extends FieldPhase { if (trainer && !enemyPokemon.getMoveQueue().length) { const opponents = enemyPokemon.getOpponents(); - const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; - const trapped = new Utils.BooleanHolder(false); - opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [], true)); - if (!trapTag && !trapped.value) { + if (!enemyPokemon.isTrapped()) { const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); if (partyMemberScores.length) {