Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Moves][Ability] Implement Torment / Taunt / Imprison + Aroma Veil #4378

Merged
merged 46 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8f5f10b
Torment
Sep 22, 2024
a1bdbdd
Taunt and Imprison
Sep 23, 2024
969d32b
ability immunities
Sep 23, 2024
9c73451
Merge branch 'beta' into restrictionMoves
flx-sta Sep 23, 2024
9f18f0c
Aroma Veil
Sep 23, 2024
145167f
Merge branch 'beta' into restrictionMoves
frutescens Sep 23, 2024
66465a5
Imprison
Sep 23, 2024
f8b143d
Test Files
Sep 23, 2024
8807362
Added exceptions for Rollout and check for active ability
Sep 24, 2024
87b4af0
adding tests so that git doesn't auto-fail
Sep 24, 2024
fa38991
Blah
Sep 24, 2024
15b7b52
please
Sep 24, 2024
1d17d7d
some documentation
Sep 24, 2024
2e7bfb0
Removed random newlines
Sep 24, 2024
bf3a2db
Added check for ability's presence mid battle
Sep 24, 2024
7b08366
Changed BattlerTagImmunityAbAttr to look at lists instead
Sep 24, 2024
8a2ffde
Work?
Sep 24, 2024
1f09561
Merge branch 'beta' into restrictionMoves
frutescens Sep 24, 2024
11de99b
Imprison and Taunt Tests
Sep 24, 2024
c1a2b76
Tests
Sep 24, 2024
542ac83
Final tests before documentation
Sep 24, 2024
070c710
Merge branch 'beta' into restrictionMoves
flx-sta Sep 24, 2024
7c3b888
documentation blah
Sep 24, 2024
25b27e8
Imports
Sep 24, 2024
eef1f82
Flx Change
Sep 24, 2024
8b8f20b
flx - adding overrides
Sep 24, 2024
c74b1ad
Update src/data/arena-tag.ts
frutescens Sep 24, 2024
a0c8b28
flx fixes
Sep 24, 2024
03ace56
quick docs
Sep 24, 2024
f7b759c
privated retrieveField
Sep 24, 2024
dca0ec6
Handling undefined
Sep 24, 2024
f900c5e
Update src/data/arena-tag.ts
frutescens Sep 25, 2024
789416e
forget to remove partials for heal block
Sep 25, 2024
3d0603d
Apply suggestions from code review
frutescens Sep 25, 2024
6220457
Marked Torment as partial
Sep 25, 2024
c0a0ab3
Update src/test/moves/torment.test.ts
frutescens Sep 25, 2024
40960b8
tsdocs
Sep 25, 2024
d9eb760
Prevents test pokemon from being immune to torment
Sep 25, 2024
caf0fad
Merge branch 'beta' into restrictionMoves
flx-sta Sep 25, 2024
1728e69
Update src/data/arena-tag.ts
frutescens Sep 25, 2024
0576826
Apply suggestions from code review
frutescens Sep 25, 2024
024450e
Torranx Fixes
Sep 25, 2024
c1c8af5
Check for this.source
Sep 25, 2024
ba0ae7c
why
Sep 25, 2024
3018527
lighting things with my mind on fire
Sep 25, 2024
3a83833
aRHGHSHDKSHD
Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions src/data/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,7 @@ export class PostSummonAbAttr extends AbAttr {
return false;
}
}

/**
* Removes specified arena tags when a Pokemon is summoned.
*/
Expand Down Expand Up @@ -2852,17 +2853,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 | BattlerTagType[]) {
super();

this.immuneTagType = immuneTagType;
this.immuneTagTypes = Array.isArray(immuneTagTypes) ? 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;
Expand Down Expand Up @@ -4916,7 +4917,7 @@ export function initAbilities() {
.attr(TypeImmunityHealAbAttr, Type.WATER)
.ignorable(),
new Ability(Abilities.OBLIVIOUS, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
.attr(BattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT])
.attr(IntimidateImmunityAbAttr)
.ignorable(),
new Ability(Abilities.CLOUD_NINE, 3)
Expand Down Expand Up @@ -5402,8 +5403,7 @@ 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(UserFieldBattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT, BattlerTagType.DISABLED, BattlerTagType.TORMENT, BattlerTagType.HEAL_BLOCK]),
new Ability(Abilities.FLOWER_VEIL, 6)
.ignorable()
.unimplemented(),
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
94 changes: 84 additions & 10 deletions src/data/arena-tag.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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, PokemonMove } 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 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";
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";
Expand Down Expand Up @@ -919,6 +920,77 @@ 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;

frutescens marked this conversation as resolved.
Show resolved Hide resolved
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1);
}

/**
* Helper function that retrieves the Pokemon effected
* @param {BattleScene} scene medium to retrieve the involved Pokemon
frutescens marked this conversation as resolved.
Show resolved Hide resolved
* @returns list of PlayerPokemon or EnemyPokemon on the field
*/
private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] {
if (!this.source.isPlayer()) {
return scene.getPlayerField() ?? [];
}
return scene.getEnemyField() ?? [];
}

/**
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
* @param arena
*/
override onAdd({ scene }: Arena) {
this.source = scene.getPokemonById(this.sourceId!)!;
if (this.source) {
frutescens marked this conversation as resolved.
Show resolved Hide resolved
frutescens marked this conversation as resolved.
Show resolved Hide resolved
const party = this.retrieveField(scene);
party?.forEach((p: PlayerPokemon | EnemyPokemon ) => {
p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
});
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
*/
override 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} pokemon the Pokemon Imprison is applied to
* @returns `true`
*/
override activateTrap(pokemon: Pokemon): boolean {
if (this.source.isActive(true)) {
pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
}
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
*/
override onRemove({ scene }: Arena): void {
const party = this.retrieveField(scene);
party?.forEach((p: PlayerPokemon | EnemyPokemon) => {
p.removeTag(BattlerTagType.IMPRISON);
});
}
}

export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
switch (tagType) {
Expand Down Expand Up @@ -967,6 +1039,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);
default:
return null;
}
Expand Down
149 changes: 148 additions & 1 deletion src/data/battler-tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -2437,6 +2437,147 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
}
}

/**
* Battle Tag that applies the move Torment to the target Pokemon
frutescens marked this conversation as resolved.
Show resolved Hide resolved
* 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
*/
export class TormentTag extends MoveRestrictionBattlerTag {
private target: Pokemon;

constructor(sourceId: number) {
super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId);
}

/**
* 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} 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);
}

/**
* Torment only ends when the affected Pokemon leaves the battle field
* @param {Pokemon} 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 {
return !pokemon.isActive(true);
}

/**
* This checks if the current move used is identical to the last used move with a {@linkcode MoveResult} of `SUCCESS`/`MISS`
* @param {Moves} move the move under investigation
* @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other
*/
override isMoveRestricted(move: Moves): boolean {
const lastMove = this.target.getLastXMoves(1)[0];
if ( !lastMove ) {
return false;
}
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
const moveObj = allMoves[lastMove.move];
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr);
const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS);
if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) {
return true;
}
return false;
}

override selectionDeniedText(_pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name });
}
}

/**
* 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.
*/
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);
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 {Moves} move the move under investigation
* @returns `true` if the move is a status move
*/
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 });
}
}

/**
* 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.
*/
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) {
if (this.sourceId) {
this.source = pokemon.scene.getPokemonById(this.sourceId)!;
frutescens marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Checks if the source of Imprison is still active
* @param _pokemon
* @param _lapseType
* @returns `true` if the source is still active
*/
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 {Moves} move the move under investigation
* @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);
}

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.
*
Expand Down Expand Up @@ -2604,6 +2745,12 @@ 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.TAUNT:
return new TauntTag();
case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
Expand Down
9 changes: 5 additions & 4 deletions src/data/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7507,7 +7507,8 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES),
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
.ignoresSubstitute()
.unimplemented(),
.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)
.attr(ConfuseAttr),
Expand Down Expand Up @@ -7538,7 +7539,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()
Expand Down Expand Up @@ -7581,9 +7582,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(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)),
Expand Down
3 changes: 2 additions & 1 deletion src/enums/arena-tag-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export enum ArenaTagType {
TAILWIND = "TAILWIND",
HAPPY_HOUR = "HAPPY_HOUR",
SAFEGUARD = "SAFEGUARD",
NO_CRIT = "NO_CRIT"
NO_CRIT = "NO_CRIT",
IMPRISON = "IMPRISON",
}
3 changes: 3 additions & 0 deletions src/enums/battler-tag-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,7 @@ export enum BattlerTagType {
AUTOTOMIZED = "AUTOTOMIZED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
HEAL_BLOCK = "HEAL_BLOCK",
TORMENT = "TORMENT",
TAUNT = "TAUNT",
IMPRISON = "IMPRISON",
}
Loading
Loading