From 8f5f10b45b579234dc955403b14828308c6cdeea Mon Sep 17 00:00:00 2001 From: frutescens Date: Sun, 22 Sep 2024 15:35:59 -0700 Subject: [PATCH 01/41] Torment --- src/data/battler-tags.ts | 39 ++++++++++++++++++++++++++++++++ src/data/move.ts | 2 +- src/enums/battler-tag-type.ts | 3 +++ src/locales/en/battler-tags.json | 3 ++- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 3be6562307ba..98c87fad14bf 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2407,6 +2407,43 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { } } +export class TormentTag extends MoveRestrictionBattlerTag { + private target: Pokemon; + + constructor(sourceId: number) { + super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId); + } + + onAdd(pokemon: Pokemon) { + super.onAdd(pokemon); + this.target = pokemon; + //pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.TORMENT_ADD); + pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); + } + + override lapse(pokemon: Pokemon, tagType: BattlerTagLapseType): boolean { + if (!pokemon.isActive(true)) { + return super.lapse(pokemon, tagType); + } + return true; + } + + isMoveRestricted(move: Moves): boolean { + const lastMove = this.target.getLastXMoves(1)[0]; + if ( !lastMove ) { + return false; + } + if (lastMove.move === move && lastMove.result === MoveResult.SUCCESS && lastMove.move !== Moves.STRUGGLE) { + return true; + } + return false; + } + + override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); + } +} + /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * @@ -2572,6 +2609,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new MysteryEncounterPostSummonTag(); case BattlerTagType.HEAL_BLOCK: return new HealBlockTag(turnCount, sourceMove); + case BattlerTagType.TORMENT: + return new TormentTag(sourceId); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index 86139a22adf8..c44e544097da 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7489,7 +7489,7 @@ export function initMoves() { .target(MoveTarget.BOTH_SIDES), new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3) .ignoresSubstitute() - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1), new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1) .attr(ConfuseAttr), diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 6cf2d260dcba..e5d7e36a79c1 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -81,4 +81,7 @@ export enum BattlerTagType { DOUBLE_SHOCKED = "DOUBLE_SHOCKED", MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", HEAL_BLOCK = "HEAL_BLOCK", + TORMENT = "TORMENT", + TAUNT = "TAUNT", + IMPRISON = "IMPRISON", } diff --git a/src/locales/en/battler-tags.json b/src/locales/en/battler-tags.json index b31826b02440..47e2e37b73b8 100644 --- a/src/locales/en/battler-tags.json +++ b/src/locales/en/battler-tags.json @@ -73,5 +73,6 @@ "tarShotOnAdd": "{{pokemonNameWithAffix}} became weaker to fire!", "substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!", "substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!", - "substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!" + "substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!", + "tormentOnAdd": "{{pokemonNameWithAffix}} was subjected to torment!" } From a1bdbddc23d4c8f31f4b96c3cc780b5a8e327433 Mon Sep 17 00:00:00 2001 From: frutescens Date: Sun, 22 Sep 2024 17:03:50 -0700 Subject: [PATCH 02/41] Taunt and Imprison --- src/data/battler-tags.ts | 63 +++++++++++++++++++++++++++++++- src/data/move.ts | 6 +-- src/locales/en/battler-tags.json | 4 +- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 98c87fad14bf..dd8679d1a301 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2423,7 +2423,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { override lapse(pokemon: Pokemon, tagType: BattlerTagLapseType): boolean { if (!pokemon.isActive(true)) { - return super.lapse(pokemon, tagType); + return false; } return true; } @@ -2444,6 +2444,63 @@ export class TormentTag extends MoveRestrictionBattlerTag { } } +export class TauntTag extends MoveRestrictionBattlerTag { + constructor() { + super(BattlerTagType.TAUNT, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 4, Moves.TAUNT); + } + + override onAdd(pokemon: Pokemon) { + super.onAdd(pokemon); + // Needs onAdd animation + pokemon.scene.queueMessage(i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); + } + + override isMoveRestricted(move: Moves): boolean { + return allMoves[move].category === MoveCategory.STATUS; + } + + override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); + } + + override interruptedText(pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); + } +} + +export class ImprisonTag extends MoveRestrictionBattlerTag { + private source: Pokemon; + + constructor(sourceId: number) { + super(BattlerTagType.IMPRISON, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 1, Moves.IMPRISON, sourceId); + } + + override onAdd(pokemon: Pokemon) { + this.source = pokemon.scene.getPokemonById(this.sourceId!)!; + pokemon.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)}), 1500); + } + + override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { + return this.source.isActive(true); + } + + override isMoveRestricted(move: Moves): boolean { + const sourceMoveset = this.source.getMoveset().map(m => { + return m!.moveId; + }); + return sourceMoveset.includes(move); + } + + override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); + } + + override interruptedText(pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); + } +} + + /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * @@ -2611,6 +2668,10 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new HealBlockTag(turnCount, sourceMove); case BattlerTagType.TORMENT: return new TormentTag(sourceId); + case BattlerTagType.TAUNT: + return new TauntTag(); + case BattlerTagType.IMPRISON: + return new ImprisonTag(sourceId); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index c44e544097da..7fd0c8e6fac9 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7520,7 +7520,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false), new StatusMove(Moves.TAUNT, Type.DARK, 100, 20, -1, 0, 3) .ignoresSubstitute() - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.TAUNT, false, true, 4), new StatusMove(Moves.HELPING_HAND, Type.NORMAL, -1, 20, -1, 5, 3) .attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND) .ignoresSubstitute() @@ -7563,9 +7563,9 @@ export function initMoves() { new StatusMove(Moves.SKILL_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 3) .ignoresSubstitute() .attr(SwitchAbilitiesAttr), - new SelfStatusMove(Moves.IMPRISON, Type.PSYCHIC, -1, 10, -1, 0, 3) + new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3) .ignoresSubstitute() - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.IMPRISON, false, true, 1), 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/locales/en/battler-tags.json b/src/locales/en/battler-tags.json index 47e2e37b73b8..3f800c19349f 100644 --- a/src/locales/en/battler-tags.json +++ b/src/locales/en/battler-tags.json @@ -74,5 +74,7 @@ "substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!", "substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!", "substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!", - "tormentOnAdd": "{{pokemonNameWithAffix}} was subjected to torment!" + "tormentOnAdd": "{{pokemonNameWithAffix}} was subjected to torment!", + "tauntOnAdd":"{{pokemonNameWithAffix}} fell for the taunt!", + "imprisonOnAdd":"{{pokemonNameWithAffix}} sealed the opponents move(s)!" } From 969d32b9d65f0d6900108f109a900e7c4ff24093 Mon Sep 17 00:00:00 2001 From: frutescens Date: Sun, 22 Sep 2024 20:27:32 -0700 Subject: [PATCH 03/41] ability immunities --- src/data/ability.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 7a8d77cc0220..15047b7918ad 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -4850,6 +4850,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.OBLIVIOUS, 3) .attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.TAUNT) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.CLOUD_NINE, 3) @@ -5334,8 +5335,13 @@ export function initAbilities() { .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(MoveAbilityBypassAbAttr), new Ability(Abilities.AROMA_VEIL, 6) - .ignorable() - .unimplemented(), + .attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.TAUNT) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.TORMENT) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.DISABLE) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.HEAL_BLOCK) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.ENCORE) + .partial(), new Ability(Abilities.FLOWER_VEIL, 6) .ignorable() .unimplemented(), From 9f18f0cdb2f6a47725ce6f77c18fc35adbb56ec1 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 12:34:54 -0700 Subject: [PATCH 04/41] Aroma Veil --- src/data/ability.ts | 29 +++++++++++++----- src/data/arena-tag.ts | 46 ++++++++++++++++++++++++++++- src/data/battler-tags.ts | 20 ++++++++++++- src/enums/arena-tag-type.ts | 4 ++- src/locales/en/ability-trigger.json | 3 +- 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 104b60a37758..ad2c8988f94e 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -764,6 +764,8 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { } } + + export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { private condition: PokemonDefendCondition; private tagType: ArenaTagType; @@ -2022,6 +2024,25 @@ export class PostSummonAbAttr extends AbAttr { return false; } } + +export class ApplyArenaTagAbAttr extends PostSummonAbAttr { + private tagType: ArenaTagType; + + constructor(tagtype: ArenaTagType) { + super(true); + this.tagType = tagtype; + } + + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const tag = pokemon.scene.arena.getTag(this.tagType); + if (!tag && !simulated) { + pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + return true; + } + return false; + } +} + /** * Removes specified arena tags when a Pokemon is summoned. */ @@ -5395,13 +5416,7 @@ export function initAbilities() { .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(MoveAbilityBypassAbAttr), new Ability(Abilities.AROMA_VEIL, 6) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.TAUNT) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.TORMENT) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.DISABLE) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.HEAL_BLOCK) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.ENCORE) - .partial(), + .attr(ApplyArenaTagAbAttr, ArenaTagType.AROMA_VEIL), new Ability(Abilities.FLOWER_VEIL, 6) .ignorable() .unimplemented(), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index fdc32b75c196..a1d799a29714 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -3,7 +3,7 @@ import { Type } from "./type"; import * as Utils from "../utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move"; import { getPokemonNameWithAffix } from "../messages"; -import Pokemon, { HitResult, PokemonMove } from "../field/pokemon"; +import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "../field/pokemon"; import { StatusEffect } from "./status-effect"; import { BattlerIndex } from "../battle"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; @@ -919,6 +919,48 @@ class SafeguardTag extends ArenaTag { } } +/** +class ImprisonTag extends ArenaTag { + constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + super(); + } +} +*/ + +class AromaVeilTag extends ArenaTag { + private protectedTags: BattlerTagType[]; + private source: Pokemon; + + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { + super(ArenaTagType.AROMA_VEIL, turnCount, undefined, sourceId, side); + this.protectedTags = [BattlerTagType.TAUNT, BattlerTagType.TORMENT, BattlerTagType.DISABLED, BattlerTagType.HEAL_BLOCK, BattlerTagType.ENCORE, BattlerTagType.INFATUATED]; + } + + + onAdd(arena: Arena): void { + this.source = arena.scene.getPokemonById(this.sourceId!)!; + const party = this.source.isPlayer() ? this.source.scene.getPlayerField() : this.source.scene.getEnemyField(); + party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { + p.findAndRemoveTags(t => this.protectedTags.includes(t.tagType)); + }); + } + + lapse(_arena: Arena): boolean { + return this.source.isActive(true); + } + + apply(arena: Arena, args: any[]): boolean { + const targetPokemon = args[2]; + if (this.protectedTags.includes(args[1] as BattlerTagType)) { + (args[0] as Utils.BooleanHolder).value = false; + arena.scene.queueMessage(i18next.t("abilityTriggers:aromaVeilImmunity", { + pokemonNameWithAffix: targetPokemon, + })); + } + return true; + } +} + export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { @@ -967,6 +1009,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov return new HappyHourTag(turnCount, sourceId, side); case ArenaTagType.SAFEGUARD: return new SafeguardTag(turnCount, sourceId, side); + case ArenaTagType.AROMA_VEIL: + return new AromaVeilTag(turnCount, sourceId, side); default: return null; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index dd8679d1a301..1ed3be36c438 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -23,6 +23,8 @@ 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 { ArenaTagType } from "#app/enums/arena-tag-type"; +import { ArenaTagSide } from "./arena-tag"; export enum BattlerTagLapseType { FAINT, @@ -113,6 +115,15 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { super(tagType, lapseType, turnCount, sourceMove, sourceId); } + override canAdd(pokemon: Pokemon): boolean { + const validArena = new Utils.BooleanHolder(true); + const arenaTag = pokemon.scene.arena.findTagsOnSide(t => t.tagType === ArenaTagType.AROMA_VEIL, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)[0]; + if (arenaTag) { + arenaTag.apply(pokemon.scene.arena, [validArena, this.tagType, getPokemonNameWithAffix(pokemon)]); + } + return validArena.value; + } + /** @override */ override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { @@ -692,9 +703,16 @@ export class InfatuatedTag extends BattlerTag { canAdd(pokemon: Pokemon): boolean { if (this.sourceId) { const pkm = pokemon.scene.getPokemonById(this.sourceId); + console.log(pkm); + + const validArena = new Utils.BooleanHolder(true); + const arenaTag = pokemon.scene.arena.findTagsOnSide(t => t.tagType === ArenaTagType.AROMA_VEIL, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)[0]; + if (arenaTag) { + arenaTag.apply(pokemon.scene.arena, [validArena, this.tagType, getPokemonNameWithAffix(pokemon)]); + } if (pkm) { - return pokemon.isOppositeGender(pkm); + return pokemon.isOppositeGender(pkm) && validArena.value; } else { console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId); return false; diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts index 1c79750c91ab..38047f0a6aeb 100644 --- a/src/enums/arena-tag-type.ts +++ b/src/enums/arena-tag-type.ts @@ -23,5 +23,7 @@ export enum ArenaTagType { TAILWIND = "TAILWIND", HAPPY_HOUR = "HAPPY_HOUR", SAFEGUARD = "SAFEGUARD", - NO_CRIT = "NO_CRIT" + NO_CRIT = "NO_CRIT", + AROMA_VEIL = "AROMA_VEIL", + IMPRISON = "IMPRISON" } diff --git a/src/locales/en/ability-trigger.json b/src/locales/en/ability-trigger.json index da21d80e3c72..0542d2f23a6e 100644 --- a/src/locales/en/ability-trigger.json +++ b/src/locales/en/ability-trigger.json @@ -60,5 +60,6 @@ "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", - "preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!" + "preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!", + "aromaVeilImmunity": "{{pokemonNameWithAffix}} is protected by an aromatic veil!" } From 66465a507611e2ac006b670571c153924be34f12 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 13:28:45 -0700 Subject: [PATCH 05/41] Imprison --- src/data/arena-tag.ts | 41 ++++++++++++++++++++++++++++++++++------ src/data/battler-tags.ts | 1 - src/data/move.ts | 2 +- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index a1d799a29714..03d5d25d28d3 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -919,13 +919,41 @@ class SafeguardTag extends ArenaTag { } } -/** -class ImprisonTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { - super(); +class ImprisonTag extends ArenaTrapTag { + private source: Pokemon; + + constructor(sourceId: integer, side: ArenaTagSide) { + super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); + } + + onAdd(arena: Arena) { + this.source = arena.scene.getPokemonById(this.sourceId!)!; + const party = (this.side === ArenaTagSide.PLAYER) ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { + p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + }); + arena.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)}), 1500); + console.log(arena.scene); + } + + lapse(_arena: Arena): boolean { + return this.source.isActive(true); + } + + activateTrap(pokemon: Pokemon): boolean { + if (this.source.isActive(true)) { + pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + } + return true; + } + + onRemove(arena: Arena): void { + const party = (this.side === ArenaTagSide.PLAYER) ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + party?.forEach((p: PlayerPokemon | EnemyPokemon) => { + p.removeTag(BattlerTagType.IMPRISON); + }); } } -*/ class AromaVeilTag extends ArenaTag { private protectedTags: BattlerTagType[]; @@ -936,7 +964,6 @@ class AromaVeilTag extends ArenaTag { this.protectedTags = [BattlerTagType.TAUNT, BattlerTagType.TORMENT, BattlerTagType.DISABLED, BattlerTagType.HEAL_BLOCK, BattlerTagType.ENCORE, BattlerTagType.INFATUATED]; } - onAdd(arena: Arena): void { this.source = arena.scene.getPokemonById(this.sourceId!)!; const party = this.source.isPlayer() ? this.source.scene.getPlayerField() : this.source.scene.getEnemyField(); @@ -1009,6 +1036,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov return new HappyHourTag(turnCount, sourceId, side); case ArenaTagType.SAFEGUARD: return new SafeguardTag(turnCount, sourceId, side); + case ArenaTagType.IMPRISON: + return new ImprisonTag(sourceId, side); case ArenaTagType.AROMA_VEIL: return new AromaVeilTag(turnCount, sourceId, side); default: diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 01ed3ea8d874..b0280d8d3bd7 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2525,7 +2525,6 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { override onAdd(pokemon: Pokemon) { this.source = pokemon.scene.getPokemonById(this.sourceId!)!; - pokemon.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)}), 1500); } override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { diff --git a/src/data/move.ts b/src/data/move.ts index 1e7f67bb7821..48a134c45b6f 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7583,7 +7583,7 @@ export function initMoves() { .attr(SwitchAbilitiesAttr), new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3) .ignoresSubstitute() - .attr(AddBattlerTagAttr, BattlerTagType.IMPRISON, false, true, 1), + .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false), 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)), From f8b143d3d07ee2306bda954dfd9b01cd3bc38de9 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 13:32:44 -0700 Subject: [PATCH 06/41] Test Files --- src/test/abilities/aroma_veil.test.ts | 0 src/test/moves/imprison.test.ts | 0 src/test/moves/taunt.test.ts | 0 src/test/moves/torment.test.ts | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/test/abilities/aroma_veil.test.ts create mode 100644 src/test/moves/imprison.test.ts create mode 100644 src/test/moves/taunt.test.ts create mode 100644 src/test/moves/torment.test.ts diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/moves/imprison.test.ts b/src/test/moves/imprison.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/moves/taunt.test.ts b/src/test/moves/taunt.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/moves/torment.test.ts b/src/test/moves/torment.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 From 880736277ada4a91fbfb7a38557c6e5fa3b2dcd3 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 19:28:31 -0700 Subject: [PATCH 07/41] Added exceptions for Rollout and check for active ability --- src/data/arena-tag.ts | 2 +- src/data/battler-tags.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 03d5d25d28d3..6da8ca76b576 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -973,7 +973,7 @@ class AromaVeilTag extends ArenaTag { } lapse(_arena: Arena): boolean { - return this.source.isActive(true); + return this.source.isActive(true) && this.source.hasAbility(Abilities.AROMA_VEIL); } apply(arena: Arena, args: any[]): boolean { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index b0280d8d3bd7..c9fc72676bac 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import { StatusEffect } from "./status-effect"; import * as Utils from "../utils"; -import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr } from "./move"; +import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move"; import { Type } from "./type"; import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability"; import { TerrainType } from "./terrain"; @@ -2481,7 +2481,8 @@ export class TormentTag extends MoveRestrictionBattlerTag { if ( !lastMove ) { return false; } - if (lastMove.move === move && lastMove.result === MoveResult.SUCCESS && lastMove.move !== Moves.STRUGGLE) { + const isLockedIntoMove = allMoves[lastMove.move].hasAttr(ConsecutiveUseDoublePowerAttr); + if (lastMove.move === move && lastMove.result === MoveResult.SUCCESS && lastMove.move !== Moves.STRUGGLE && !isLockedIntoMove) { return true; } return false; From 87b4af04be8f71dbe937be2fe53e23cb2a3671b1 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 20:04:56 -0700 Subject: [PATCH 08/41] adding tests so that git doesn't auto-fail --- src/data/battler-tags.ts | 3 +- src/test/abilities/aroma_veil.test.ts | 54 +++++++++++++++++++++++ src/test/moves/imprison.test.ts | 54 +++++++++++++++++++++++ src/test/moves/taunt.test.ts | 54 +++++++++++++++++++++++ src/test/moves/torment.test.ts | 63 +++++++++++++++++++++++++++ 5 files changed, 227 insertions(+), 1 deletion(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c9fc72676bac..0a8ea4c658e5 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2482,7 +2482,8 @@ export class TormentTag extends MoveRestrictionBattlerTag { return false; } const isLockedIntoMove = allMoves[lastMove.move].hasAttr(ConsecutiveUseDoublePowerAttr); - if (lastMove.move === move && lastMove.result === MoveResult.SUCCESS && lastMove.move !== Moves.STRUGGLE && !isLockedIntoMove) { + const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); + if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isLockedIntoMove) { return true; } return false; diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index e69de29bb2d1..e01b63dbf887 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -0,0 +1,54 @@ +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { Abilities } from "#enums/abilities"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveResult } from "#app/field/pokemon"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; + +describe("Moves - Aroma Veil", () => { + 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") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([Moves.TAUNT, Moves.SPLASH]) + .enemySpecies(Species.SHUCKLE) + .moveset([Moves.GROWL]); + }); + + it("Pokemon should not be able to use the same move consecutively", async () => { + await game.classicMode.startBattle([Species.REGIELEKI]); + + const playerPokemon = game.scene.getPlayerPokemon(); + + // First turn, Player Pokemon fails usin + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.TAUNT); + await game.toNextTurn(); + const move1 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move1.move).toBe(Moves.GROWL); + expect(move1.result).toBe(MoveResult.SUCCESS); + expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); + + // Second turn, Taunt forces Struggle to occur + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const move2 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move2.move).toBe(Moves.STRUGGLE); + }); +}); diff --git a/src/test/moves/imprison.test.ts b/src/test/moves/imprison.test.ts index e69de29bb2d1..92669591a9f0 100644 --- a/src/test/moves/imprison.test.ts +++ b/src/test/moves/imprison.test.ts @@ -0,0 +1,54 @@ +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { Abilities } from "#enums/abilities"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveResult } from "#app/field/pokemon"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; + +describe("Moves - Imprison", () => { + 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") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([Moves.IMPRISON, Moves.SPLASH]) + .enemySpecies(Species.SHUCKLE) + .moveset([Moves.GROWL]); + }); + + it("Pokemon should not be able to use the same move consecutively", async () => { + await game.classicMode.startBattle([Species.REGIELEKI]); + + const playerPokemon = game.scene.getPlayerPokemon(); + + // First turn, Player Pokemon fails usin + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.IMPRISON); + await game.toNextTurn(); + const move1 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move1.move).toBe(Moves.GROWL); + expect(move1.result).toBe(MoveResult.SUCCESS); + expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); + + // Second turn, Taunt forces Struggle to occur + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const move2 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move2.move).toBe(Moves.STRUGGLE); + }); +}); diff --git a/src/test/moves/taunt.test.ts b/src/test/moves/taunt.test.ts index e69de29bb2d1..368d052ff8da 100644 --- a/src/test/moves/taunt.test.ts +++ b/src/test/moves/taunt.test.ts @@ -0,0 +1,54 @@ +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { Abilities } from "#enums/abilities"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveResult } from "#app/field/pokemon"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; + +describe("Moves - Taunt", () => { + 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") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([Moves.TAUNT, Moves.SPLASH]) + .enemySpecies(Species.SHUCKLE) + .moveset([Moves.GROWL]); + }); + + it("Pokemon should not be able to use Status Moves", async () => { + await game.classicMode.startBattle([Species.REGIELEKI]); + + const playerPokemon = game.scene.getPlayerPokemon(); + + // First turn, Player Pokemon succeeds using Growl + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.TAUNT); + await game.toNextTurn(); + const move1 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move1.move).toBe(Moves.GROWL); + expect(move1.result).toBe(MoveResult.SUCCESS); + expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); + + // Second turn, Taunt forces Struggle to occur + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const move2 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move2.move).toBe(Moves.STRUGGLE); + }); +}); diff --git a/src/test/moves/torment.test.ts b/src/test/moves/torment.test.ts index e69de29bb2d1..479736f06216 100644 --- a/src/test/moves/torment.test.ts +++ b/src/test/moves/torment.test.ts @@ -0,0 +1,63 @@ +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { Abilities } from "#enums/abilities"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveResult } from "#app/field/pokemon"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; + +describe("Moves - Torment", () => { + 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") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([Moves.TORMENT, Moves.SPLASH]) + .enemySpecies(Species.SHUCKLE) + .enemyLevel(30) + .moveset([Moves.TACKLE]); + }); + + it("Pokemon should not be able to use the same move consecutively", async () => { + await game.classicMode.startBattle([Species.CHANSEY]); + + const playerPokemon = game.scene.getPlayerPokemon(); + + // First turn, Player Pokemon uses Tackle successfully + game.move.select(Moves.TACKLE); + await game.forceEnemyMove(Moves.TORMENT); + await game.toNextTurn(); + const move1 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move1.move).toBe(Moves.TACKLE); + expect(move1.result).toBe(MoveResult.SUCCESS); + expect(playerPokemon?.getTag(BattlerTagType.TORMENT)).toBeDefined(); + + // Second turn, Torment forces Struggle to occur + game.move.select(Moves.TACKLE); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const move2 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move2.move).toBe(Moves.STRUGGLE); + + // Third turn, Tackle can be used. + game.move.select(Moves.TACKLE); + await game.forceEnemyMove(Moves.SPLASH); + await game.phaseInterceptor.to(TurnEndPhase); + const move3 = playerPokemon?.getLastXMoves(1)[0]!; + expect(move3.move).toBe(Moves.TACKLE); + }); +}); From fa389912c72e136919ab770250203db50d6e4842 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 20:06:02 -0700 Subject: [PATCH 09/41] Blah --- src/data/battler-tags.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 0a8ea4c658e5..794670782384 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -703,7 +703,6 @@ export class InfatuatedTag extends BattlerTag { canAdd(pokemon: Pokemon): boolean { if (this.sourceId) { const pkm = pokemon.scene.getPokemonById(this.sourceId); - console.log(pkm); const validArena = new Utils.BooleanHolder(true); const arenaTag = pokemon.scene.arena.findTagsOnSide(t => t.tagType === ArenaTagType.AROMA_VEIL, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)[0]; From 15b7b525e85fe4c93f8c7b16a6276ed73d15e14b Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 20:41:26 -0700 Subject: [PATCH 10/41] please --- src/test/moves/imprison.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/moves/imprison.test.ts b/src/test/moves/imprison.test.ts index 92669591a9f0..a9cf5c3f7215 100644 --- a/src/test/moves/imprison.test.ts +++ b/src/test/moves/imprison.test.ts @@ -42,13 +42,11 @@ describe("Moves - Imprison", () => { const move1 = playerPokemon?.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.GROWL); expect(move1.result).toBe(MoveResult.SUCCESS); - expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); + expect(playerPokemon?.getTag(BattlerTagType.IMPRISON)).toBeDefined(); // Second turn, Taunt forces Struggle to occur game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - const move2 = playerPokemon?.getLastXMoves(1)[0]!; - expect(move2.move).toBe(Moves.STRUGGLE); }); }); From 1d17d7d215b44ccef73402de4c80bc7314541b86 Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 20:56:43 -0700 Subject: [PATCH 11/41] some documentation --- src/data/battler-tags.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 794670782384..489390da0d0a 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2454,6 +2454,11 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { } } +/** + * Battle Tag that applies the move Torment to the target Pokemon + * Torment restricts the consecutive use of moves. + * The tag is only removed if the target leaves the battle. + */ export class TormentTag extends MoveRestrictionBattlerTag { private target: Pokemon; @@ -2464,7 +2469,6 @@ export class TormentTag extends MoveRestrictionBattlerTag { onAdd(pokemon: Pokemon) { super.onAdd(pokemon); this.target = pokemon; - //pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.TORMENT_ADD); pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } @@ -2493,6 +2497,11 @@ export class TormentTag extends MoveRestrictionBattlerTag { } } +/** + * Battle Tag that applies the move Taunt to the target Pokemon + * Taunt restricts the use of status moves. + * The tag is removed after 4 turns. + */ export class TauntTag extends MoveRestrictionBattlerTag { constructor() { super(BattlerTagType.TAUNT, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 4, Moves.TAUNT); @@ -2500,7 +2509,6 @@ export class TauntTag extends MoveRestrictionBattlerTag { override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - // Needs onAdd animation pokemon.scene.queueMessage(i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } @@ -2517,6 +2525,11 @@ export class TauntTag extends MoveRestrictionBattlerTag { } } +/** + * Battle Tag that applies the move Imprison to the target Pokemon + * Imprison restricts the opposing side's usage of moves shared by the source-user of Imprison. + * The tag is only removed when the source-user is removed from the field. + */ export class ImprisonTag extends MoveRestrictionBattlerTag { private source: Pokemon; From 2e7bfb031b4e0b89cec50021d38789de99f1be1a Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 20:57:52 -0700 Subject: [PATCH 12/41] Removed random newlines --- src/data/ability.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index c107cea5ff8a..2ae25912145e 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -764,8 +764,6 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { } } - - export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { private condition: PokemonDefendCondition; private tagType: ArenaTagType; From bf3a2db47c3dd175ed496652aae333baae91b6da Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 21:55:26 -0700 Subject: [PATCH 13/41] Added check for ability's presence mid battle --- src/data/arena-tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 6da8ca76b576..3f0cb6340ec0 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -978,7 +978,7 @@ class AromaVeilTag extends ArenaTag { apply(arena: Arena, args: any[]): boolean { const targetPokemon = args[2]; - if (this.protectedTags.includes(args[1] as BattlerTagType)) { + if (this.protectedTags.includes(args[1] as BattlerTagType) && this.lapse(arena)) { (args[0] as Utils.BooleanHolder).value = false; arena.scene.queueMessage(i18next.t("abilityTriggers:aromaVeilImmunity", { pokemonNameWithAffix: targetPokemon, From 7b08366a70028f8711f150b295c56d968699bb7b Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 23:13:07 -0700 Subject: [PATCH 14/41] Changed BattlerTagImmunityAbAttr to look at lists instead --- src/data/ability.ts | 43 ++++++++--------------------- src/data/arena-tag.ts | 36 ------------------------ src/data/battler-tags.ts | 19 +------------ src/enums/arena-tag-type.ts | 1 - src/locales/en/ability-trigger.json | 3 +- 5 files changed, 14 insertions(+), 88 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 2ae25912145e..6df99ff4ab2f 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2023,24 +2023,6 @@ export class PostSummonAbAttr extends AbAttr { } } -export class ApplyArenaTagAbAttr extends PostSummonAbAttr { - private tagType: ArenaTagType; - - constructor(tagtype: ArenaTagType) { - super(true); - this.tagType = tagtype; - } - - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const tag = pokemon.scene.arena.getTag(this.tagType); - if (!tag && !simulated) { - pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); - return true; - } - return false; - } -} - /** * Removes specified arena tags when a Pokemon is summoned. */ @@ -2867,17 +2849,17 @@ export class PreApplyBattlerTagAbAttr extends AbAttr { * Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets. */ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { - private immuneTagType: BattlerTagType; + private immuneTagTypes: BattlerTagType[]; private battlerTag: BattlerTag; - constructor(immuneTagType: BattlerTagType) { + constructor(immuneTagTypes: BattlerTagType[]) { super(); - this.immuneTagType = immuneTagType; + this.immuneTagTypes = immuneTagTypes; } applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (tag.tagType === this.immuneTagType) { + if (this.immuneTagTypes.includes(tag.tagType)) { cancelled.value = true; if (!simulated) { this.battlerTag = tag; @@ -4931,8 +4913,7 @@ export function initAbilities() { .attr(TypeImmunityHealAbAttr, Type.WATER) .ignorable(), new Ability(Abilities.OBLIVIOUS, 3) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.TAUNT) + .attr(BattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT]) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.CLOUD_NINE, 3) @@ -4945,7 +4926,7 @@ export function initAbilities() { .attr(StatMultiplierAbAttr, Stat.ACC, 1.3), new Ability(Abilities.INSOMNIA, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) + .attr(BattlerTagImmunityAbAttr, [BattlerTagType.DROWSY]) .ignorable(), new Ability(Abilities.COLOR_CHANGE, 3) .attr(PostDefendTypeChangeAbAttr) @@ -4960,7 +4941,7 @@ export function initAbilities() { .attr(IgnoreMoveEffectsAbAttr) .partial(), new Ability(Abilities.OWN_TEMPO, 3) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) + .attr(BattlerTagImmunityAbAttr, [BattlerTagType.CONFUSED]) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.SUCTION_CUPS, 3) @@ -5023,7 +5004,7 @@ export function initAbilities() { .attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON) .bypassFaint(), new Ability(Abilities.INNER_FOCUS, 3) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.FLINCHED) + .attr(BattlerTagImmunityAbAttr, [BattlerTagType.FLINCHED]) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.MAGMA_ARMOR, 3) @@ -5124,7 +5105,7 @@ export function initAbilities() { .attr(DoubleBattleChanceAbAttr), new Ability(Abilities.VITAL_SPIRIT, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) + .attr(BattlerTagImmunityAbAttr, [BattlerTagType.DROWSY]) .ignorable(), new Ability(Abilities.WHITE_SMOKE, 3) .attr(ProtectStatAbAttr) @@ -5418,7 +5399,7 @@ export function initAbilities() { .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(MoveAbilityBypassAbAttr), new Ability(Abilities.AROMA_VEIL, 6) - .attr(ApplyArenaTagAbAttr, ArenaTagType.AROMA_VEIL), + .attr(UserFieldBattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT, BattlerTagType.DISABLED, BattlerTagType.TORMENT, BattlerTagType.HEAL_BLOCK]), new Ability(Abilities.FLOWER_VEIL, 6) .ignorable() .unimplemented(), @@ -5443,7 +5424,7 @@ export function initAbilities() { .attr(MoveTypeChangeAbAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), new Ability(Abilities.SWEET_VEIL, 6) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY) + .attr(UserFieldBattlerTagImmunityAbAttr, [ BattlerTagType.DROWSY]) .ignorable() .partial(), // Mold Breaker ally should not be affected by Sweet Veil new Ability(Abilities.STANCE_CHANGE, 6) @@ -5597,7 +5578,7 @@ export function initAbilities() { .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr) .attr(StatusEffectImmunityAbAttr, ...getNonVolatileStatusEffects()) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY), + .attr(BattlerTagImmunityAbAttr, [BattlerTagType.DROWSY]), new Ability(Abilities.QUEENLY_MAJESTY, 7) .attr(FieldPriorityMoveImmunityAbAttr) .ignorable(), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 3f0cb6340ec0..f7d4fe4b2785 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -955,40 +955,6 @@ class ImprisonTag extends ArenaTrapTag { } } -class AromaVeilTag extends ArenaTag { - private protectedTags: BattlerTagType[]; - private source: Pokemon; - - constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { - super(ArenaTagType.AROMA_VEIL, turnCount, undefined, sourceId, side); - this.protectedTags = [BattlerTagType.TAUNT, BattlerTagType.TORMENT, BattlerTagType.DISABLED, BattlerTagType.HEAL_BLOCK, BattlerTagType.ENCORE, BattlerTagType.INFATUATED]; - } - - onAdd(arena: Arena): void { - this.source = arena.scene.getPokemonById(this.sourceId!)!; - const party = this.source.isPlayer() ? this.source.scene.getPlayerField() : this.source.scene.getEnemyField(); - party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { - p.findAndRemoveTags(t => this.protectedTags.includes(t.tagType)); - }); - } - - lapse(_arena: Arena): boolean { - return this.source.isActive(true) && this.source.hasAbility(Abilities.AROMA_VEIL); - } - - apply(arena: Arena, args: any[]): boolean { - const targetPokemon = args[2]; - if (this.protectedTags.includes(args[1] as BattlerTagType) && this.lapse(arena)) { - (args[0] as Utils.BooleanHolder).value = false; - arena.scene.queueMessage(i18next.t("abilityTriggers:aromaVeilImmunity", { - pokemonNameWithAffix: targetPokemon, - })); - } - return true; - } -} - - export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { case ArenaTagType.MIST: @@ -1038,8 +1004,6 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov return new SafeguardTag(turnCount, sourceId, side); case ArenaTagType.IMPRISON: return new ImprisonTag(sourceId, side); - case ArenaTagType.AROMA_VEIL: - return new AromaVeilTag(turnCount, sourceId, side); default: return null; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 489390da0d0a..1e98a5874bb4 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -23,8 +23,6 @@ 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 { ArenaTagType } from "#app/enums/arena-tag-type"; -import { ArenaTagSide } from "./arena-tag"; export enum BattlerTagLapseType { FAINT, @@ -115,15 +113,6 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { super(tagType, lapseType, turnCount, sourceMove, sourceId); } - override canAdd(pokemon: Pokemon): boolean { - const validArena = new Utils.BooleanHolder(true); - const arenaTag = pokemon.scene.arena.findTagsOnSide(t => t.tagType === ArenaTagType.AROMA_VEIL, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)[0]; - if (arenaTag) { - arenaTag.apply(pokemon.scene.arena, [validArena, this.tagType, getPokemonNameWithAffix(pokemon)]); - } - return validArena.value; - } - /** @override */ override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { @@ -704,14 +693,8 @@ export class InfatuatedTag extends BattlerTag { if (this.sourceId) { const pkm = pokemon.scene.getPokemonById(this.sourceId); - const validArena = new Utils.BooleanHolder(true); - const arenaTag = pokemon.scene.arena.findTagsOnSide(t => t.tagType === ArenaTagType.AROMA_VEIL, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)[0]; - if (arenaTag) { - arenaTag.apply(pokemon.scene.arena, [validArena, this.tagType, getPokemonNameWithAffix(pokemon)]); - } - if (pkm) { - return pokemon.isOppositeGender(pkm) && validArena.value; + return pokemon.isOppositeGender(pkm); } else { console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId); return false; diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts index 38047f0a6aeb..f761b137fffc 100644 --- a/src/enums/arena-tag-type.ts +++ b/src/enums/arena-tag-type.ts @@ -24,6 +24,5 @@ export enum ArenaTagType { HAPPY_HOUR = "HAPPY_HOUR", SAFEGUARD = "SAFEGUARD", NO_CRIT = "NO_CRIT", - AROMA_VEIL = "AROMA_VEIL", IMPRISON = "IMPRISON" } diff --git a/src/locales/en/ability-trigger.json b/src/locales/en/ability-trigger.json index 0542d2f23a6e..da21d80e3c72 100644 --- a/src/locales/en/ability-trigger.json +++ b/src/locales/en/ability-trigger.json @@ -60,6 +60,5 @@ "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", - "preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!", - "aromaVeilImmunity": "{{pokemonNameWithAffix}} is protected by an aromatic veil!" + "preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!" } From 8a2ffdea49627ec6a1b73c53f3b6485c805baf2f Mon Sep 17 00:00:00 2001 From: frutescens Date: Mon, 23 Sep 2024 23:26:29 -0700 Subject: [PATCH 15/41] Work? --- src/data/arena-tag.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index f7d4fe4b2785..dfa598b9a3d8 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -932,8 +932,7 @@ class ImprisonTag extends ArenaTrapTag { party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); }); - arena.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)}), 1500); - console.log(arena.scene); + arena.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)})); } lapse(_arena: Arena): boolean { From 11de99b43c3d5afff59440e4226d83d501c4f9ff Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 13:16:11 -0700 Subject: [PATCH 16/41] Imprison and Taunt Tests --- src/test/moves/imprison.test.ts | 69 +++++++++++++++++++++++++++------ src/test/moves/taunt.test.ts | 2 +- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/test/moves/imprison.test.ts b/src/test/moves/imprison.test.ts index a9cf5c3f7215..5821232efa3f 100644 --- a/src/test/moves/imprison.test.ts +++ b/src/test/moves/imprison.test.ts @@ -4,8 +4,8 @@ import { Abilities } from "#enums/abilities"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { MoveResult } from "#app/field/pokemon"; import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; describe("Moves - Imprison", () => { let phaserGame: Phaser.Game; @@ -25,28 +25,75 @@ describe("Moves - Imprison", () => { game.override .battleType("single") .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset([Moves.IMPRISON, Moves.SPLASH]) + .enemyMoveset([Moves.IMPRISON, Moves.SPLASH, Moves.GROWL]) .enemySpecies(Species.SHUCKLE) - .moveset([Moves.GROWL]); + .moveset([Moves.TRANSFORM, Moves.SPLASH]); }); - it("Pokemon should not be able to use the same move consecutively", async () => { + it("Pokemon under Imprison cannot use shared moves", async () => { await game.classicMode.startBattle([Species.REGIELEKI]); const playerPokemon = game.scene.getPlayerPokemon(); - // First turn, Player Pokemon fails usin - game.move.select(Moves.GROWL); + game.move.select(Moves.TRANSFORM); await game.forceEnemyMove(Moves.IMPRISON); await game.toNextTurn(); + const playerMoveset = playerPokemon!.getMoveset().map(x => x?.moveId); + const enemyMoveset = game.scene.getEnemyPokemon()!.getMoveset().map(x => x?.moveId); + expect(enemyMoveset.includes(playerMoveset[0])).toBeTruthy(); + const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON); + const imprisonBattlerTag = playerPokemon!.getTag(BattlerTagType.IMPRISON); + expect(imprisonArenaTag).toBeDefined(); + expect(imprisonBattlerTag).toBeDefined(); + + // Second turn, Imprison forces Struggle to occur + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); const move1 = playerPokemon?.getLastXMoves(1)[0]!; - expect(move1.move).toBe(Moves.GROWL); - expect(move1.result).toBe(MoveResult.SUCCESS); - expect(playerPokemon?.getTag(BattlerTagType.IMPRISON)).toBeDefined(); + expect(move1.move).toBe(Moves.STRUGGLE); + }); + + it("Imprison applies to Pokemon switched into Battle", async () => { + game.override.battleType("single"); + await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); + + const playerPokemon1 = game.scene.getPlayerPokemon(); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.IMPRISON); + await game.toNextTurn(); + const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON); + const imprisonBattlerTag1 = playerPokemon1!.getTag(BattlerTagType.IMPRISON); + expect(imprisonArenaTag).toBeDefined(); + expect(imprisonBattlerTag1).toBeDefined(); - // Second turn, Taunt forces Struggle to occur - game.move.select(Moves.GROWL); + // Second turn, Imprison forces Struggle to occur + game.doSwitchPokemon(1); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const playerPokemon2 = game.scene.getPlayerPokemon(); + const imprisonBattlerTag2 = playerPokemon2!.getTag(BattlerTagType.IMPRISON); + expect(playerPokemon1).not.toEqual(playerPokemon2); + expect(imprisonBattlerTag2).toBeDefined(); + }); + + it("The effects of Imprison only end when the source is no longer active", async () => { + game.override.battleType("single"); + game.override.moveset([Moves.SPLASH, Moves.IMPRISON]); + await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); + + const playerPokemon = game.scene.getPlayerPokemon(); + const enemyPokemon = game.scene.getEnemyPokemon(); + game.move.select(Moves.IMPRISON); + await game.forceEnemyMove(Moves.IMPRISON); + await game.toNextTurn(); + expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); + expect(enemyPokemon!.getTag(BattlerTagType.IMPRISON)).toBeDefined(); + game.doSwitchPokemon(1); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); + expect(playerPokemon?.isActive(true)).toBeFalsy(); + expect(enemyPokemon!.getTag(BattlerTagType.IMPRISON)).toBeUndefined(); }); }); diff --git a/src/test/moves/taunt.test.ts b/src/test/moves/taunt.test.ts index 368d052ff8da..e2b87873cc59 100644 --- a/src/test/moves/taunt.test.ts +++ b/src/test/moves/taunt.test.ts @@ -35,7 +35,7 @@ describe("Moves - Taunt", () => { const playerPokemon = game.scene.getPlayerPokemon(); - // First turn, Player Pokemon succeeds using Growl + // First turn, Player Pokemon succeeds using Growl without Torment game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.TAUNT); await game.toNextTurn(); From c1a2b76800bb992a8d1bb5de8cc16dd8ef1157ea Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 13:47:52 -0700 Subject: [PATCH 17/41] Tests --- src/data/arena-tag.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index dfa598b9a3d8..1f935005ad5a 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -923,12 +923,13 @@ class ImprisonTag extends ArenaTrapTag { private source: Pokemon; constructor(sourceId: integer, side: ArenaTagSide) { + console.log(side); super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } onAdd(arena: Arena) { this.source = arena.scene.getPokemonById(this.sourceId!)!; - const party = (this.side === ArenaTagSide.PLAYER) ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); }); @@ -947,7 +948,7 @@ class ImprisonTag extends ArenaTrapTag { } onRemove(arena: Arena): void { - const party = (this.side === ArenaTagSide.PLAYER) ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); party?.forEach((p: PlayerPokemon | EnemyPokemon) => { p.removeTag(BattlerTagType.IMPRISON); }); From 542ac83dfd992b29afc322bb18c884e227f6f867 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 13:48:32 -0700 Subject: [PATCH 18/41] Final tests before documentation --- src/test/abilities/aroma_veil.test.ts | 38 ++++++++++++++++----------- src/test/moves/imprison.test.ts | 5 ++-- src/test/moves/taunt.test.ts | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index e01b63dbf887..7ee073b43745 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -4,8 +4,9 @@ import { Abilities } from "#enums/abilities"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { MoveResult } from "#app/field/pokemon"; import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { BattlerIndex } from "#app/battle"; describe("Moves - Aroma Veil", () => { let phaserGame: Phaser.Game; @@ -23,32 +24,39 @@ describe("Moves - Aroma Veil", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .battleType("single") + .battleType("double") .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset([Moves.TAUNT, Moves.SPLASH]) + .enemyMoveset([Moves.HEAL_BLOCK, Moves.IMPRISON, Moves.SPLASH]) .enemySpecies(Species.SHUCKLE) + .ability(Abilities.AROMA_VEIL) .moveset([Moves.GROWL]); }); - it("Pokemon should not be able to use the same move consecutively", async () => { - await game.classicMode.startBattle([Species.REGIELEKI]); + it("Aroma Veil protects the Pokemon's side against most Move Restriction Battler Tags", async () => { + await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const playerPokemon = game.scene.getPlayerPokemon(); + const playerPokemon = game.scene.getParty()!; - // First turn, Player Pokemon fails usin game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.TAUNT); + game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.HEAL_BLOCK); await game.toNextTurn(); - const move1 = playerPokemon?.getLastXMoves(1)[0]!; - expect(move1.move).toBe(Moves.GROWL); - expect(move1.result).toBe(MoveResult.SUCCESS); - expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); + expect(playerPokemon[0].getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); + expect(playerPokemon[1].getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); + }); + + it("Aroma Veil does not protect against Imprison", async () => { + await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - // Second turn, Taunt forces Struggle to occur + const playerPokemon = game.scene.getParty()!; + + game.move.select(Moves.GROWL); game.move.select(Moves.GROWL); + await game.forceEnemyMove(Moves.IMPRISON, BattlerIndex.PLAYER); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - const move2 = playerPokemon?.getLastXMoves(1)[0]!; - expect(move2.move).toBe(Moves.STRUGGLE); + expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); + expect(playerPokemon[0].getTag(BattlerTagType.IMPRISON)).toBeDefined(); + expect(playerPokemon[1].getTag(BattlerTagType.IMPRISON)).toBeDefined(); }); }); diff --git a/src/test/moves/imprison.test.ts b/src/test/moves/imprison.test.ts index 5821232efa3f..d3dc8d33f985 100644 --- a/src/test/moves/imprison.test.ts +++ b/src/test/moves/imprison.test.ts @@ -55,7 +55,6 @@ describe("Moves - Imprison", () => { }); it("Imprison applies to Pokemon switched into Battle", async () => { - game.override.battleType("single"); await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); const playerPokemon1 = game.scene.getPlayerPokemon(); @@ -79,14 +78,13 @@ describe("Moves - Imprison", () => { }); it("The effects of Imprison only end when the source is no longer active", async () => { - game.override.battleType("single"); game.override.moveset([Moves.SPLASH, Moves.IMPRISON]); await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); const playerPokemon = game.scene.getPlayerPokemon(); const enemyPokemon = game.scene.getEnemyPokemon(); game.move.select(Moves.IMPRISON); - await game.forceEnemyMove(Moves.IMPRISON); + await game.forceEnemyMove(Moves.GROWL); await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); expect(enemyPokemon!.getTag(BattlerTagType.IMPRISON)).toBeDefined(); @@ -94,6 +92,7 @@ describe("Moves - Imprison", () => { await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(playerPokemon?.isActive(true)).toBeFalsy(); + expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeUndefined(); expect(enemyPokemon!.getTag(BattlerTagType.IMPRISON)).toBeUndefined(); }); }); diff --git a/src/test/moves/taunt.test.ts b/src/test/moves/taunt.test.ts index e2b87873cc59..7b63a48bc5e3 100644 --- a/src/test/moves/taunt.test.ts +++ b/src/test/moves/taunt.test.ts @@ -35,7 +35,7 @@ describe("Moves - Taunt", () => { const playerPokemon = game.scene.getPlayerPokemon(); - // First turn, Player Pokemon succeeds using Growl without Torment + // First turn, Player Pokemon succeeds using Growl without Taunt game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.TAUNT); await game.toNextTurn(); From 7c3b88820808f28b10ea3437a7028a76e7f87260 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 14:10:55 -0700 Subject: [PATCH 19/41] documentation blah --- src/data/arena-tag.ts | 38 +++++++++++++++++++++++++++++++------- src/data/battler-tags.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 1f935005ad5a..432cfdc2b17f 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -919,27 +919,47 @@ class SafeguardTag extends ArenaTag { } } +/** + * This arena tag facilitates the application of the move Imprison + * Imprison remains in effect as long as the source Pokemon is active and present on the field. + * Imprison will apply to any opposing Pokemon that switch onto the field as well. + */ class ImprisonTag extends ArenaTrapTag { private source: Pokemon; constructor(sourceId: integer, side: ArenaTagSide) { - console.log(side); super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } - onAdd(arena: Arena) { + /** + * This function applies the effects of Imprison to the opposing Pokemon already present on the field. + * @param arena + */ + override onAdd(arena: Arena) { this.source = arena.scene.getPokemonById(this.sourceId!)!; - const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); - party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { - p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); - }); - arena.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)})); + if (this.source) { + const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { + p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + }); + arena.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)})); + } } + /** + * Checks if the source Pokemon is still active on the field + * @param _arena + * @returns `true` if the source of the tag is still active on the field | `false` if not + */ lapse(_arena: Arena): boolean { return this.source.isActive(true); } + /** + * This applies the effects of Imprison to any opposing Pokemon that switch into the field while the source Pokemon is still active + * @param pokemon the Pokemon Imprison is applied to + * @returns `true` + */ activateTrap(pokemon: Pokemon): boolean { if (this.source.isActive(true)) { pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); @@ -947,6 +967,10 @@ class ImprisonTag extends ArenaTrapTag { return true; } + /** + * When the arena tag is removed, it also attempts to remove any related Battler Tags if they haven't already been removed from the affected Pokemon + * @param arena + */ onRemove(arena: Arena): void { const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); party?.forEach((p: PlayerPokemon | EnemyPokemon) => { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 1e98a5874bb4..7ec1e966b536 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2455,18 +2455,30 @@ export class TormentTag extends MoveRestrictionBattlerTag { pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } - override lapse(pokemon: Pokemon, tagType: BattlerTagLapseType): boolean { + /** + * Torment only ends when the affected Pokemon leaves the battle field + * @param pokemon the Pokemon under the effects of Torment + * @param _tagType + * @returns `true` if still present | `false` if not + */ + override lapse(pokemon: Pokemon, _tagType: BattlerTagLapseType): boolean { if (!pokemon.isActive(true)) { return false; } return true; } + /** + * This checks if the current move used is identical to the last used move with a MoveResult of SUCCESS/MISS + * @param move the move under investigation + * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other + */ isMoveRestricted(move: Moves): boolean { const lastMove = this.target.getLastXMoves(1)[0]; if ( !lastMove ) { return false; } + // This checks for moves like Rollout and Iceball, which are immune to the move restrictions of Torment const isLockedIntoMove = allMoves[lastMove.move].hasAttr(ConsecutiveUseDoublePowerAttr); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isLockedIntoMove) { @@ -2478,6 +2490,8 @@ export class TormentTag extends MoveRestrictionBattlerTag { override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); } + + //Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied } /** @@ -2495,6 +2509,11 @@ export class TauntTag extends MoveRestrictionBattlerTag { pokemon.scene.queueMessage(i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } + /** + * Checks if a move is a status move and determines its restriction status on that basis + * @param move the move under investigation + * @returns `true` if the move is a status move | `false` if not + */ override isMoveRestricted(move: Moves): boolean { return allMoves[move].category === MoveCategory.STATUS; } @@ -2524,15 +2543,26 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { this.source = pokemon.scene.getPokemonById(this.sourceId!)!; } + /** + * Checks if the source of Imprison is still active + * @param _pokemon + * @param _lapseType + * @returns `true` if the source is still active | `false` if not + */ override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { return this.source.isActive(true); } + /** + * Checks if the source of the tag has the parameter move in its moveset and that the source is still active + * @param move the move under investigation + * @returns `true` if both conditions are met | `false` if either conditions are not met + */ override isMoveRestricted(move: Moves): boolean { const sourceMoveset = this.source.getMoveset().map(m => { return m!.moveId; }); - return sourceMoveset.includes(move); + return sourceMoveset.includes(move) && this.source.isActive(true); } override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { From 25b27e8d3453e8b17d8fd3bf52e4cf3d71bb287e Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 14:17:21 -0700 Subject: [PATCH 20/41] Imports --- src/data/arena-tag.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 432cfdc2b17f..0dae82623886 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,14 +1,14 @@ -import { Arena } from "../field/arena"; -import { Type } from "./type"; -import * as Utils from "../utils"; -import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move"; -import { getPokemonNameWithAffix } from "../messages"; -import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "../field/pokemon"; -import { StatusEffect } from "./status-effect"; -import { BattlerIndex } from "../battle"; -import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; +import { Arena } from "#app/field/arena"; +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 { StatusEffect } from "#app/data/status-effect"; +import { BattlerIndex } from "#app/battle"; +import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; -import { CommonAnim, CommonBattleAnim } from "./battle-anims"; +import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import i18next from "i18next"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; From eef1f82dfcb221163d673717c6ba9851e45f738c Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 14:21:16 -0700 Subject: [PATCH 21/41] Flx Change --- src/data/ability.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index ac309af9152f..391d083fe8d1 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2856,10 +2856,10 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { private immuneTagTypes: BattlerTagType[]; private battlerTag: BattlerTag; - constructor(immuneTagTypes: BattlerTagType[]) { + constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { super(); - this.immuneTagTypes = immuneTagTypes; + this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [immuneTagTypes]; } applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { @@ -4930,7 +4930,7 @@ export function initAbilities() { .attr(StatMultiplierAbAttr, Stat.ACC, 1.3), new Ability(Abilities.INSOMNIA, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(BattlerTagImmunityAbAttr, [BattlerTagType.DROWSY]) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable(), new Ability(Abilities.COLOR_CHANGE, 3) .attr(PostDefendTypeChangeAbAttr) @@ -4945,7 +4945,7 @@ export function initAbilities() { .attr(IgnoreMoveEffectsAbAttr) .partial(), new Ability(Abilities.OWN_TEMPO, 3) - .attr(BattlerTagImmunityAbAttr, [BattlerTagType.CONFUSED]) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.SUCTION_CUPS, 3) @@ -5008,7 +5008,7 @@ export function initAbilities() { .attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON) .bypassFaint(), new Ability(Abilities.INNER_FOCUS, 3) - .attr(BattlerTagImmunityAbAttr, [BattlerTagType.FLINCHED]) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.FLINCHED) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.MAGMA_ARMOR, 3) @@ -5109,7 +5109,7 @@ export function initAbilities() { .attr(DoubleBattleChanceAbAttr), new Ability(Abilities.VITAL_SPIRIT, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(BattlerTagImmunityAbAttr, [BattlerTagType.DROWSY]) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable(), new Ability(Abilities.WHITE_SMOKE, 3) .attr(ProtectStatAbAttr) @@ -5428,7 +5428,7 @@ export function initAbilities() { .attr(MoveTypeChangeAbAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), new Ability(Abilities.SWEET_VEIL, 6) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(UserFieldBattlerTagImmunityAbAttr, [ BattlerTagType.DROWSY]) + .attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable() .partial(), // Mold Breaker ally should not be affected by Sweet Veil new Ability(Abilities.STANCE_CHANGE, 6) @@ -5582,7 +5582,7 @@ export function initAbilities() { .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr) .attr(StatusEffectImmunityAbAttr, ...getNonVolatileStatusEffects()) - .attr(BattlerTagImmunityAbAttr, [BattlerTagType.DROWSY]), + .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY), new Ability(Abilities.QUEENLY_MAJESTY, 7) .attr(FieldPriorityMoveImmunityAbAttr) .ignorable(), From 8b8f20bf37d0cdaf7a19ac0ad7cf99d16ef0373a Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 15:08:40 -0700 Subject: [PATCH 22/41] flx - adding overrides --- src/data/arena-tag.ts | 6 +++--- src/data/battler-tags.ts | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 0dae82623886..54de3f5d1cbc 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -951,7 +951,7 @@ class ImprisonTag extends ArenaTrapTag { * @param _arena * @returns `true` if the source of the tag is still active on the field | `false` if not */ - lapse(_arena: Arena): boolean { + override lapse(_arena: Arena): boolean { return this.source.isActive(true); } @@ -960,7 +960,7 @@ class ImprisonTag extends ArenaTrapTag { * @param pokemon the Pokemon Imprison is applied to * @returns `true` */ - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon): boolean { if (this.source.isActive(true)) { pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); } @@ -971,7 +971,7 @@ class ImprisonTag extends ArenaTrapTag { * When the arena tag is removed, it also attempts to remove any related Battler Tags if they haven't already been removed from the affected Pokemon * @param arena */ - onRemove(arena: Arena): void { + override onRemove(arena: Arena): void { const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); party?.forEach((p: PlayerPokemon | EnemyPokemon) => { p.removeTag(BattlerTagType.IMPRISON); diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 7ec1e966b536..da689c0502aa 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2441,6 +2441,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { * Battle Tag that applies the move Torment to the target Pokemon * Torment restricts the consecutive use of moves. * The tag is only removed if the target leaves the battle. + * 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; @@ -2449,7 +2450,12 @@ export class TormentTag extends MoveRestrictionBattlerTag { super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId); } - onAdd(pokemon: Pokemon) { + /** + * Adds the battler tag to the target Pokemon and defines the private class variable 'target' + * 'Target' is used to track the Pokemon's current status + * @param pokemon the Pokemon tormented + */ + override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); this.target = pokemon; pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); @@ -2473,7 +2479,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { * @param move the move under investigation * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other */ - isMoveRestricted(move: Moves): boolean { + override isMoveRestricted(move: Moves): boolean { const lastMove = this.target.getLastXMoves(1)[0]; if ( !lastMove ) { return false; @@ -2490,8 +2496,6 @@ export class TormentTag extends MoveRestrictionBattlerTag { override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); } - - //Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied } /** From c74b1ad19056cc26f4196228fc155b0c87b997ab Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:08:57 -0700 Subject: [PATCH 23/41] Update src/data/arena-tag.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- src/data/arena-tag.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 54de3f5d1cbc..b4935c5879ef 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -935,14 +935,14 @@ class ImprisonTag extends ArenaTrapTag { * This function applies the effects of Imprison to the opposing Pokemon already present on the field. * @param arena */ - override onAdd(arena: Arena) { - this.source = arena.scene.getPokemonById(this.sourceId!)!; + override onAdd({ scene }: Arena) { + this.source = scene.getPokemonById(this.sourceId!)!; if (this.source) { - const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + const party = !this.source.isPlayer() ? scene.getPlayerField() : scene.getEnemyField(); party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); }); - arena.scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)})); + scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)})); } } From a0c8b2818fcc9953d8881023e473c8b6ffb3ce0c Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 15:14:42 -0700 Subject: [PATCH 24/41] flx fixes --- src/data/arena-tag.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index b4935c5879ef..055a00d5556e 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,4 +1,5 @@ import { Arena } from "#app/field/arena"; +import BattleScene from "#app/battle-scene"; import { Type } from "#app/data/type"; import * as Utils from "#app/utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move"; @@ -931,6 +932,13 @@ class ImprisonTag extends ArenaTrapTag { super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } + 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 @@ -938,7 +946,7 @@ class ImprisonTag extends ArenaTrapTag { override onAdd({ scene }: Arena) { this.source = scene.getPokemonById(this.sourceId!)!; if (this.source) { - const party = !this.source.isPlayer() ? scene.getPlayerField() : scene.getEnemyField(); + const party = this.retrieveField(scene); party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); }); @@ -971,8 +979,8 @@ class ImprisonTag extends ArenaTrapTag { * When the arena tag is removed, it also attempts to remove any related Battler Tags if they haven't already been removed from the affected Pokemon * @param arena */ - override onRemove(arena: Arena): void { - const party = !this.source.isPlayer() ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); + override onRemove({ scene }: Arena): void { + const party = this.retrieveField(scene); party?.forEach((p: PlayerPokemon | EnemyPokemon) => { p.removeTag(BattlerTagType.IMPRISON); }); From 03ace56dc48bf51bc0081ffe1555a578a2c540cb Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 15:16:57 -0700 Subject: [PATCH 25/41] quick docs --- src/data/arena-tag.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 055a00d5556e..424ea84075fe 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -932,6 +932,11 @@ class ImprisonTag extends ArenaTrapTag { super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } + /** + * Helper function that retrieves the Pokemon effected + * @param scene medium to retrieve the involved Pokemon + * @returns list of PlayerPokemon or EnemyPokemon on the field + */ retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { if (!this.source.isPlayer()) { return scene.getPlayerField(); From f7b759ca7db9f3f9300c06bf8854059c008344b5 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 15:27:19 -0700 Subject: [PATCH 26/41] privated retrieveField --- src/data/arena-tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 424ea84075fe..3a2b1997c4ab 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -937,7 +937,7 @@ class ImprisonTag extends ArenaTrapTag { * @param scene medium to retrieve the involved Pokemon * @returns list of PlayerPokemon or EnemyPokemon on the field */ - retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { + private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { if (!this.source.isPlayer()) { return scene.getPlayerField(); } From dca0ec60514ad01807e830ac77763c1cef18ed17 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 15:32:37 -0700 Subject: [PATCH 27/41] Handling undefined --- src/data/arena-tag.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 3a2b1997c4ab..280cfc5e8da6 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -939,9 +939,9 @@ class ImprisonTag extends ArenaTrapTag { */ private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { if (!this.source.isPlayer()) { - return scene.getPlayerField(); + return scene.getPlayerField() ?? []; } - return scene.getEnemyField(); + return scene.getEnemyField() ?? []; } /** From f900c5ebfefcb7901eedfcc5fe3178abfdaf9ef3 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:08:54 -0700 Subject: [PATCH 28/41] Update src/data/arena-tag.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- src/data/arena-tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 280cfc5e8da6..ab0ce5e5d3d7 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -928,7 +928,7 @@ class SafeguardTag extends ArenaTag { class ImprisonTag extends ArenaTrapTag { private source: Pokemon; - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } From 789416e00685be516e67762d121999142a860ab1 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 19:59:10 -0700 Subject: [PATCH 29/41] forget to remove partials for heal block --- src/data/ability.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 391d083fe8d1..3ace872de3c2 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5885,7 +5885,6 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.EARTH_EATER, 9) .attr(TypeImmunityHealAbAttr, Type.GROUND) - .partial() // Healing not blocked by Heal Block .ignorable(), new Ability(Abilities.MYCELIUM_MIGHT, 9) .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2) @@ -5900,8 +5899,7 @@ export function initAbilities() { .attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1) .condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)), new Ability(Abilities.HOSPITALITY, 9) - .attr(PostSummonAllyHealAbAttr, 4, true) - .partial(), // Healing not blocked by Heal Block + .attr(PostSummonAllyHealAbAttr, 4, true), new Ability(Abilities.TOXIC_CHAIN, 9) .attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC), new Ability(Abilities.EMBODY_ASPECT_TEAL, 9) From 3d0603ddf12740d5a246bc7aefd9724e76bb12e9 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:45:08 -0700 Subject: [PATCH 30/41] Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 17 +++++--------- src/enums/arena-tag-type.ts | 2 +- src/locales/en/battler-tags.json | 2 +- src/test/abilities/aroma_veil.test.ts | 10 ++++---- src/test/moves/imprison.test.ts | 34 +++++++++++++-------------- src/test/moves/taunt.test.ts | 12 +++++----- src/test/moves/torment.test.ts | 12 +++++----- 7 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index da689c0502aa..c63f532a35ce 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2468,14 +2468,11 @@ export class TormentTag extends MoveRestrictionBattlerTag { * @returns `true` if still present | `false` if not */ override lapse(pokemon: Pokemon, _tagType: BattlerTagLapseType): boolean { - if (!pokemon.isActive(true)) { - return false; - } - return true; + return !pokemon.isActive(true); } /** - * This checks if the current move used is identical to the last used move with a MoveResult of SUCCESS/MISS + * This checks if the current move used is identical to the last used move with a {@linkcode MoveResult} of `SUCCESS`/`MISS` * @param move the move under investigation * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other */ @@ -2516,7 +2513,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { /** * Checks if a move is a status move and determines its restriction status on that basis * @param move the move under investigation - * @returns `true` if the move is a status move | `false` if not + * @returns `true` if the move is a status move */ override isMoveRestricted(move: Moves): boolean { return allMoves[move].category === MoveCategory.STATUS; @@ -2551,7 +2548,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { * Checks if the source of Imprison is still active * @param _pokemon * @param _lapseType - * @returns `true` if the source is still active | `false` if not + * @returns `true` if the source is still active */ override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { return this.source.isActive(true); @@ -2560,12 +2557,10 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Checks if the source of the tag has the parameter move in its moveset and that the source is still active * @param move the move under investigation - * @returns `true` if both conditions are met | `false` if either conditions are not met + * @returns `false` if either condition is not met */ override isMoveRestricted(move: Moves): boolean { - const sourceMoveset = this.source.getMoveset().map(m => { - return m!.moveId; - }); + const sourceMoveset = this.source.getMoveset().map(m => m!.moveId); return sourceMoveset.includes(move) && this.source.isActive(true); } diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts index f761b137fffc..c6f911cb4935 100644 --- a/src/enums/arena-tag-type.ts +++ b/src/enums/arena-tag-type.ts @@ -24,5 +24,5 @@ export enum ArenaTagType { HAPPY_HOUR = "HAPPY_HOUR", SAFEGUARD = "SAFEGUARD", NO_CRIT = "NO_CRIT", - IMPRISON = "IMPRISON" + IMPRISON = "IMPRISON", } diff --git a/src/locales/en/battler-tags.json b/src/locales/en/battler-tags.json index 944dc400b326..6a5eeee25771 100644 --- a/src/locales/en/battler-tags.json +++ b/src/locales/en/battler-tags.json @@ -77,5 +77,5 @@ "tormentOnAdd": "{{pokemonNameWithAffix}} was subjected to torment!", "tauntOnAdd": "{{pokemonNameWithAffix}} fell for the taunt!", "imprisonOnAdd": "{{pokemonNameWithAffix}} sealed the opponents move(s)!", - "autotomizeOnAdd": "{{pokemonNameWIthAffix}} became nimble!" + "autotomizeOnAdd": "{{pokemonNameWithAffix}} became nimble!" } diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index 7ee073b43745..7748502d81ea 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -1,11 +1,11 @@ -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#app/battle"; describe("Moves - Aroma Veil", () => { @@ -51,7 +51,7 @@ describe("Moves - Aroma Veil", () => { const playerPokemon = game.scene.getParty()!; game.move.select(Moves.GROWL); - game.move.select(Moves.GROWL); + game.move.select(Moves.GROWL, 1); await game.forceEnemyMove(Moves.IMPRISON, BattlerIndex.PLAYER); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); diff --git a/src/test/moves/imprison.test.ts b/src/test/moves/imprison.test.ts index d3dc8d33f985..abb4b3cac6c8 100644 --- a/src/test/moves/imprison.test.ts +++ b/src/test/moves/imprison.test.ts @@ -1,11 +1,11 @@ -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { ArenaTagType } from "#enums/arena-tag-type"; describe("Moves - Imprison", () => { let phaserGame: Phaser.Game; @@ -33,16 +33,16 @@ describe("Moves - Imprison", () => { it("Pokemon under Imprison cannot use shared moves", async () => { await game.classicMode.startBattle([Species.REGIELEKI]); - const playerPokemon = game.scene.getPlayerPokemon(); + const playerPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.TRANSFORM); await game.forceEnemyMove(Moves.IMPRISON); await game.toNextTurn(); - const playerMoveset = playerPokemon!.getMoveset().map(x => x?.moveId); + const playerMoveset = playerPokemon.getMoveset().map(x => x?.moveId); const enemyMoveset = game.scene.getEnemyPokemon()!.getMoveset().map(x => x?.moveId); expect(enemyMoveset.includes(playerMoveset[0])).toBeTruthy(); const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON); - const imprisonBattlerTag = playerPokemon!.getTag(BattlerTagType.IMPRISON); + const imprisonBattlerTag = playerPokemon.getTag(BattlerTagType.IMPRISON); expect(imprisonArenaTag).toBeDefined(); expect(imprisonBattlerTag).toBeDefined(); @@ -50,20 +50,20 @@ describe("Moves - Imprison", () => { game.move.select(Moves.SPLASH); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - const move1 = playerPokemon?.getLastXMoves(1)[0]!; + const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.STRUGGLE); }); it("Imprison applies to Pokemon switched into Battle", async () => { await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const playerPokemon1 = game.scene.getPlayerPokemon(); + const playerPokemon1 = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); await game.forceEnemyMove(Moves.IMPRISON); await game.toNextTurn(); const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON); - const imprisonBattlerTag1 = playerPokemon1!.getTag(BattlerTagType.IMPRISON); + const imprisonBattlerTag1 = playerPokemon1.getTag(BattlerTagType.IMPRISON); expect(imprisonArenaTag).toBeDefined(); expect(imprisonBattlerTag1).toBeDefined(); @@ -71,8 +71,8 @@ describe("Moves - Imprison", () => { game.doSwitchPokemon(1); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - const playerPokemon2 = game.scene.getPlayerPokemon(); - const imprisonBattlerTag2 = playerPokemon2!.getTag(BattlerTagType.IMPRISON); + const playerPokemon2 = game.scene.getPlayerPokemon()!; + const imprisonBattlerTag2 = playerPokemon2.getTag(BattlerTagType.IMPRISON); expect(playerPokemon1).not.toEqual(playerPokemon2); expect(imprisonBattlerTag2).toBeDefined(); }); @@ -81,18 +81,18 @@ describe("Moves - Imprison", () => { game.override.moveset([Moves.SPLASH, Moves.IMPRISON]); await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const playerPokemon = game.scene.getPlayerPokemon(); - const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; game.move.select(Moves.IMPRISON); await game.forceEnemyMove(Moves.GROWL); await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); - expect(enemyPokemon!.getTag(BattlerTagType.IMPRISON)).toBeDefined(); + expect(enemyPokemon.getTag(BattlerTagType.IMPRISON)).toBeDefined(); game.doSwitchPokemon(1); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - expect(playerPokemon?.isActive(true)).toBeFalsy(); + expect(playerPokemon.isActive(true)).toBeFalsy(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeUndefined(); - expect(enemyPokemon!.getTag(BattlerTagType.IMPRISON)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.IMPRISON)).toBeUndefined(); }); }); diff --git a/src/test/moves/taunt.test.ts b/src/test/moves/taunt.test.ts index 7b63a48bc5e3..50bb2fee9dfd 100644 --- a/src/test/moves/taunt.test.ts +++ b/src/test/moves/taunt.test.ts @@ -1,11 +1,11 @@ -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { MoveResult } from "#app/field/pokemon"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; describe("Moves - Taunt", () => { let phaserGame: Phaser.Game; @@ -33,13 +33,13 @@ describe("Moves - Taunt", () => { it("Pokemon should not be able to use Status Moves", async () => { await game.classicMode.startBattle([Species.REGIELEKI]); - const playerPokemon = game.scene.getPlayerPokemon(); + const playerPokemon = game.scene.getPlayerPokemon()!; // First turn, Player Pokemon succeeds using Growl without Taunt game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.TAUNT); await game.toNextTurn(); - const move1 = playerPokemon?.getLastXMoves(1)[0]!; + const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.GROWL); expect(move1.result).toBe(MoveResult.SUCCESS); expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); @@ -48,7 +48,7 @@ describe("Moves - Taunt", () => { game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - const move2 = playerPokemon?.getLastXMoves(1)[0]!; + const move2 = playerPokemon.getLastXMoves(1)[0]!; expect(move2.move).toBe(Moves.STRUGGLE); }); }); diff --git a/src/test/moves/torment.test.ts b/src/test/moves/torment.test.ts index 479736f06216..168a90ef5a62 100644 --- a/src/test/moves/torment.test.ts +++ b/src/test/moves/torment.test.ts @@ -1,11 +1,11 @@ -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { MoveResult } from "#app/field/pokemon"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; describe("Moves - Torment", () => { @@ -35,13 +35,13 @@ describe("Moves - Torment", () => { it("Pokemon should not be able to use the same move consecutively", async () => { await game.classicMode.startBattle([Species.CHANSEY]); - const playerPokemon = game.scene.getPlayerPokemon(); + const playerPokemon = game.scene.getPlayerPokemon()!; // First turn, Player Pokemon uses Tackle successfully game.move.select(Moves.TACKLE); await game.forceEnemyMove(Moves.TORMENT); await game.toNextTurn(); - const move1 = playerPokemon?.getLastXMoves(1)[0]!; + const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.TACKLE); expect(move1.result).toBe(MoveResult.SUCCESS); expect(playerPokemon?.getTag(BattlerTagType.TORMENT)).toBeDefined(); @@ -57,7 +57,7 @@ describe("Moves - Torment", () => { game.move.select(Moves.TACKLE); await game.forceEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); - const move3 = playerPokemon?.getLastXMoves(1)[0]!; + const move3 = playerPokemon.getLastXMoves(1)[0]!; expect(move3.move).toBe(Moves.TACKLE); }); }); From 6220457a80eb6a656f26627251a020df2fb8e014 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 20:58:14 -0700 Subject: [PATCH 31/41] Marked Torment as partial --- src/data/battler-tags.ts | 8 +++++--- src/data/move.ts | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c63f532a35ce..a0aee796a0d3 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2481,10 +2481,12 @@ export class TormentTag extends MoveRestrictionBattlerTag { if ( !lastMove ) { return false; } - // This checks for moves like Rollout and Iceball, which are immune to the move restrictions of Torment - const isLockedIntoMove = allMoves[lastMove.move].hasAttr(ConsecutiveUseDoublePowerAttr); + // 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.hasTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); - if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isLockedIntoMove) { + if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) { return true; } return false; diff --git a/src/data/move.ts b/src/data/move.ts index 48a134c45b6f..27c276966505 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7507,6 +7507,7 @@ export function initMoves() { .target(MoveTarget.BOTH_SIDES), new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3) .ignoresSubstitute() + .partial() // Incomplete implementation because of Uproar's partial implementation .attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1), new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1) From c0a0ab357295eae000581487e331f8c1269548a3 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:59:42 -0700 Subject: [PATCH 32/41] Update src/test/moves/torment.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/test/moves/torment.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/moves/torment.test.ts b/src/test/moves/torment.test.ts index 168a90ef5a62..d81c5c58f786 100644 --- a/src/test/moves/torment.test.ts +++ b/src/test/moves/torment.test.ts @@ -50,7 +50,7 @@ describe("Moves - Torment", () => { game.move.select(Moves.TACKLE); await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); - const move2 = playerPokemon?.getLastXMoves(1)[0]!; + const move2 = playerPokemon.getLastXMoves(1)[0]!; expect(move2.move).toBe(Moves.STRUGGLE); // Third turn, Tackle can be used. From 40960b80980afa4551fb68e2286b768267038a57 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 21:07:44 -0700 Subject: [PATCH 33/41] tsdocs --- src/data/battler-tags.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a0aee796a0d3..b65f8f3eccab 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2484,7 +2484,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { // 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.hasTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); + const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.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; From d9eb760be556a45f9ea3e520de96c0fb8082b2e6 Mon Sep 17 00:00:00 2001 From: frutescens Date: Tue, 24 Sep 2024 21:21:51 -0700 Subject: [PATCH 34/41] Prevents test pokemon from being immune to torment --- src/test/moves/torment.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/moves/torment.test.ts b/src/test/moves/torment.test.ts index d81c5c58f786..f725f2bc34ac 100644 --- a/src/test/moves/torment.test.ts +++ b/src/test/moves/torment.test.ts @@ -29,7 +29,8 @@ describe("Moves - Torment", () => { .enemyMoveset([Moves.TORMENT, Moves.SPLASH]) .enemySpecies(Species.SHUCKLE) .enemyLevel(30) - .moveset([Moves.TACKLE]); + .moveset([Moves.TACKLE]) + .ability(Abilities.BALL_FETCH); }); it("Pokemon should not be able to use the same move consecutively", async () => { From 1728e69130b9cc38a3f97d383da6c49d237cce4d Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:09:41 -0700 Subject: [PATCH 35/41] Update src/data/arena-tag.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- src/data/arena-tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index ab0ce5e5d3d7..8fac5eafbfad 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -934,7 +934,7 @@ class ImprisonTag extends ArenaTrapTag { /** * Helper function that retrieves the Pokemon effected - * @param scene medium to retrieve the involved Pokemon + * @param {BattleScene} scene medium to retrieve the involved Pokemon * @returns list of PlayerPokemon or EnemyPokemon on the field */ private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { From 0576826ca69a443433aa850d88b38f5f2c00857d Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:11:39 -0700 Subject: [PATCH 36/41] Apply suggestions from code review Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- src/data/arena-tag.ts | 2 +- src/data/battler-tags.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 8fac5eafbfad..8ffb3038629c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -970,7 +970,7 @@ class ImprisonTag extends ArenaTrapTag { /** * This applies the effects of Imprison to any opposing Pokemon that switch into the field while the source Pokemon is still active - * @param pokemon the Pokemon Imprison is applied to + * @param {Pokemon} pokemon the Pokemon Imprison is applied to * @returns `true` */ override activateTrap(pokemon: Pokemon): boolean { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index b65f8f3eccab..804a1f4ddbe4 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2438,7 +2438,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { } /** - * Battle Tag that applies the move Torment to the target Pokemon + * BattlerTag that applies the effects of Torment to the target Pokemon * Torment restricts the consecutive use of moves. * The tag is only removed if the target leaves the battle. * Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied @@ -2453,7 +2453,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { /** * Adds the battler tag to the target Pokemon and defines the private class variable 'target' * 'Target' is used to track the Pokemon's current status - * @param pokemon the Pokemon tormented + * @param {Pokemon} pokemon the Pokemon tormented */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); @@ -2463,7 +2463,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { /** * Torment only ends when the affected Pokemon leaves the battle field - * @param pokemon the Pokemon under the effects of Torment + * @param {Pokemon} pokemon the Pokemon under the effects of Torment * @param _tagType * @returns `true` if still present | `false` if not */ @@ -2473,7 +2473,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { /** * This checks if the current move used is identical to the last used move with a {@linkcode MoveResult} of `SUCCESS`/`MISS` - * @param move the move under investigation + * @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 { @@ -2498,7 +2498,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { } /** - * Battle Tag that applies the move Taunt to the target Pokemon + * BattlerTag that applies the effects of Taunt to the target Pokemon * Taunt restricts the use of status moves. * The tag is removed after 4 turns. */ @@ -2514,7 +2514,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { /** * Checks if a move is a status move and determines its restriction status on that basis - * @param move the move under investigation + * @param {Moves} move the move under investigation * @returns `true` if the move is a status move */ override isMoveRestricted(move: Moves): boolean { @@ -2531,7 +2531,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { } /** - * Battle Tag that applies the move Imprison to the target Pokemon + * BattlerTag that applies the effects of Imprison to the target Pokemon * Imprison restricts the opposing side's usage of moves shared by the source-user of Imprison. * The tag is only removed when the source-user is removed from the field. */ @@ -2558,7 +2558,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Checks if the source of the tag has the parameter move in its moveset and that the source is still active - * @param move the move under investigation + * @param {Moves} move the move under investigation * @returns `false` if either condition is not met */ override isMoveRestricted(move: Moves): boolean { From 024450ecfbac96745703bddb5ed59c140d9dcc95 Mon Sep 17 00:00:00 2001 From: frutescens Date: Wed, 25 Sep 2024 13:57:24 -0700 Subject: [PATCH 37/41] Torranx Fixes --- src/data/battler-tags.ts | 4 ++-- src/test/abilities/aroma_veil.test.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 804a1f4ddbe4..6fe2543b93e4 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2438,8 +2438,8 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { } /** - * BattlerTag that applies the effects of Torment to the target Pokemon - * Torment restricts the consecutive use of moves. + * Battle Tag that applies the move Torment to the target Pokemon + * Torment restricts the use of moves twice in a row. * The tag is only removed if the target leaves the battle. * Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied */ diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index 7748502d81ea..8ca46ce5f1eb 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -35,20 +35,21 @@ describe("Moves - Aroma Veil", () => { it("Aroma Veil protects the Pokemon's side against most Move Restriction Battler Tags", async () => { await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const playerPokemon = game.scene.getParty()!; + const party = game.scene.getParty()!; game.move.select(Moves.GROWL); game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.HEAL_BLOCK); await game.toNextTurn(); - expect(playerPokemon[0].getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); - expect(playerPokemon[1].getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); + for (const pokemon in party) { + expect(pokemon.getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); + } }); it("Aroma Veil does not protect against Imprison", async () => { await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const playerPokemon = game.scene.getParty()!; + const party = game.scene.getParty()!; game.move.select(Moves.GROWL); game.move.select(Moves.GROWL, 1); @@ -56,7 +57,8 @@ describe("Moves - Aroma Veil", () => { await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); - expect(playerPokemon[0].getTag(BattlerTagType.IMPRISON)).toBeDefined(); - expect(playerPokemon[1].getTag(BattlerTagType.IMPRISON)).toBeDefined(); + for (const pokemon in party) { + expect(pokemon).getTag(BattlerTagType.IMPRISON).toBeDefined(); + } }); }); From c1c8af5f6c5a4ac9bf0d061ca2aca2b1e5271051 Mon Sep 17 00:00:00 2001 From: frutescens Date: Wed, 25 Sep 2024 14:00:49 -0700 Subject: [PATCH 38/41] Check for this.source --- src/data/battler-tags.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 6fe2543b93e4..a55aa368a8c7 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2543,7 +2543,9 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { } override onAdd(pokemon: Pokemon) { - this.source = pokemon.scene.getPokemonById(this.sourceId!)!; + if (this.sourceId) { + this.source = pokemon.scene.getPokemonById(this.sourceId)!; + } } /** From ba0ae7c51b35481f47a32282a7fceaa8b8a934d9 Mon Sep 17 00:00:00 2001 From: frutescens Date: Wed, 25 Sep 2024 14:04:38 -0700 Subject: [PATCH 39/41] why --- src/test/abilities/aroma_veil.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index 8ca46ce5f1eb..63e18bcf70e8 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -58,7 +58,7 @@ describe("Moves - Aroma Veil", () => { await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); for (const pokemon in party) { - expect(pokemon).getTag(BattlerTagType.IMPRISON).toBeDefined(); + expect(pokemon.getTag(BattlerTagType.IMPRISON)).toBeDefined(); } }); }); From 301852714eb37e72cc8961a848e0e9c543c02232 Mon Sep 17 00:00:00 2001 From: frutescens Date: Wed, 25 Sep 2024 14:15:09 -0700 Subject: [PATCH 40/41] lighting things with my mind on fire --- src/test/abilities/aroma_veil.test.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index 63e18bcf70e8..b70308a5d608 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -7,6 +7,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerTagType } from "#enums/battler-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#app/battle"; +import { PlayerPokemon } from "#app/field/pokemon"; describe("Moves - Aroma Veil", () => { let phaserGame: Phaser.Game; @@ -35,21 +36,21 @@ describe("Moves - Aroma Veil", () => { it("Aroma Veil protects the Pokemon's side against most Move Restriction Battler Tags", async () => { await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const party = game.scene.getParty()!; + const party = game.scene.getParty()! as PlayerPokemon[]; game.move.select(Moves.GROWL); game.move.select(Moves.GROWL); await game.forceEnemyMove(Moves.HEAL_BLOCK); await game.toNextTurn(); - for (const pokemon in party) { - expect(pokemon.getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); - } + party.forEach(p => { + expect(p.getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); + }); }); it("Aroma Veil does not protect against Imprison", async () => { await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]); - const party = game.scene.getParty()!; + const party = game.scene.getParty()! as PlayerPokemon[]; game.move.select(Moves.GROWL); game.move.select(Moves.GROWL, 1); @@ -57,8 +58,8 @@ describe("Moves - Aroma Veil", () => { await game.forceEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); - for (const pokemon in party) { - expect(pokemon.getTag(BattlerTagType.IMPRISON)).toBeDefined(); - } + party.forEach(p => { + expect(p.getTag(BattlerTagType.IMPRISON)).toBeDefined(); + }); }); }); From 3a83833b5a0981bf5ba18200ca2bd778b864804e Mon Sep 17 00:00:00 2001 From: frutescens Date: Wed, 25 Sep 2024 14:26:55 -0700 Subject: [PATCH 41/41] aRHGHSHDKSHD --- src/data/battler-tags.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a55aa368a8c7..65af0fb6ee8d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2536,7 +2536,7 @@ 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; + private source: Pokemon | null; constructor(sourceId: number) { super(BattlerTagType.IMPRISON, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 1, Moves.IMPRISON, sourceId); @@ -2544,7 +2544,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { override onAdd(pokemon: Pokemon) { if (this.sourceId) { - this.source = pokemon.scene.getPokemonById(this.sourceId)!; + this.source = pokemon.scene.getPokemonById(this.sourceId); } } @@ -2555,7 +2555,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { * @returns `true` if the source is still active */ override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - return this.source.isActive(true); + return this.source?.isActive(true) ?? false; } /** @@ -2564,8 +2564,11 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { * @returns `false` if either condition is not met */ override isMoveRestricted(move: Moves): boolean { - const sourceMoveset = this.source.getMoveset().map(m => m!.moveId); - return sourceMoveset.includes(move) && this.source.isActive(true); + if (this.source) { + const sourceMoveset = this.source.getMoveset().map(m => m!.moveId); + return sourceMoveset?.includes(move) && this.source.isActive(true); + } + return false; } override selectionDeniedText(_pokemon: Pokemon, move: Moves): string {