[Moves][Ability] Implement Torment / Taunt / Imprison + Aroma Veil (#4378)
* Torment * Taunt and Imprison * ability immunities * Aroma Veil * Imprison * Test Files * Added exceptions for Rollout and check for active ability * adding tests so that git doesn't auto-fail * Blah * please * some documentation * Removed random newlines * Added check for ability's presence mid battle * Changed BattlerTagImmunityAbAttr to look at lists instead * Work? * Imprison and Taunt Tests * Tests * Final tests before documentation * documentation blah * Imports * Flx Change * flx - adding overrides * Update src/data/arena-tag.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * flx fixes * quick docs * privated retrieveField * Handling undefined * Update src/data/arena-tag.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * forget to remove partials for heal block * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Marked Torment as partial * Update src/test/moves/torment.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * tsdocs * Prevents test pokemon from being immune to torment * Update src/data/arena-tag.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Torranx Fixes * Check for this.source * why * lighting things with my mind on fire * aRHGHSHDKSHD --------- Co-authored-by: frutescens <info@laptop> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com>
This commit is contained in:
parent
eab610ca22
commit
57f39efdae
|
@ -2026,6 +2026,7 @@ export class PostSummonAbAttr extends AbAttr {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes specified arena tags when a Pokemon is summoned.
|
* Removes specified arena tags when a Pokemon is summoned.
|
||||||
*/
|
*/
|
||||||
|
@ -2852,17 +2853,17 @@ export class PreApplyBattlerTagAbAttr extends AbAttr {
|
||||||
* Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets.
|
* Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets.
|
||||||
*/
|
*/
|
||||||
export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
||||||
private immuneTagType: BattlerTagType;
|
private immuneTagTypes: BattlerTagType[];
|
||||||
private battlerTag: BattlerTag;
|
private battlerTag: BattlerTag;
|
||||||
|
|
||||||
constructor(immuneTagType: BattlerTagType) {
|
constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) {
|
||||||
super();
|
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 {
|
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;
|
cancelled.value = true;
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
this.battlerTag = tag;
|
this.battlerTag = tag;
|
||||||
|
@ -4916,7 +4917,7 @@ export function initAbilities() {
|
||||||
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.OBLIVIOUS, 3)
|
new Ability(Abilities.OBLIVIOUS, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
.attr(BattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT])
|
||||||
.attr(IntimidateImmunityAbAttr)
|
.attr(IntimidateImmunityAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.CLOUD_NINE, 3)
|
new Ability(Abilities.CLOUD_NINE, 3)
|
||||||
|
@ -5402,8 +5403,7 @@ export function initAbilities() {
|
||||||
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
||||||
.attr(MoveAbilityBypassAbAttr),
|
.attr(MoveAbilityBypassAbAttr),
|
||||||
new Ability(Abilities.AROMA_VEIL, 6)
|
new Ability(Abilities.AROMA_VEIL, 6)
|
||||||
.ignorable()
|
.attr(UserFieldBattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT, BattlerTagType.DISABLED, BattlerTagType.TORMENT, BattlerTagType.HEAL_BLOCK]),
|
||||||
.unimplemented(),
|
|
||||||
new Ability(Abilities.FLOWER_VEIL, 6)
|
new Ability(Abilities.FLOWER_VEIL, 6)
|
||||||
.ignorable()
|
.ignorable()
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
|
@ -5885,7 +5885,6 @@ export function initAbilities() {
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.EARTH_EATER, 9)
|
new Ability(Abilities.EARTH_EATER, 9)
|
||||||
.attr(TypeImmunityHealAbAttr, Type.GROUND)
|
.attr(TypeImmunityHealAbAttr, Type.GROUND)
|
||||||
.partial() // Healing not blocked by Heal Block
|
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.MYCELIUM_MIGHT, 9)
|
new Ability(Abilities.MYCELIUM_MIGHT, 9)
|
||||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
|
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
|
||||||
|
@ -5900,8 +5899,7 @@ export function initAbilities() {
|
||||||
.attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1)
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1)
|
||||||
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
|
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
|
||||||
new Ability(Abilities.HOSPITALITY, 9)
|
new Ability(Abilities.HOSPITALITY, 9)
|
||||||
.attr(PostSummonAllyHealAbAttr, 4, true)
|
.attr(PostSummonAllyHealAbAttr, 4, true),
|
||||||
.partial(), // Healing not blocked by Heal Block
|
|
||||||
new Ability(Abilities.TOXIC_CHAIN, 9)
|
new Ability(Abilities.TOXIC_CHAIN, 9)
|
||||||
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
|
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
|
||||||
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)
|
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { Arena } from "../field/arena";
|
import { Arena } from "#app/field/arena";
|
||||||
import { Type } from "./type";
|
import BattleScene from "#app/battle-scene";
|
||||||
import * as Utils from "../utils";
|
import { Type } from "#app/data/type";
|
||||||
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move";
|
import * as Utils from "#app/utils";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move";
|
||||||
import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { StatusEffect } from "./status-effect";
|
import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "#app/field/pokemon";
|
||||||
import { BattlerIndex } from "../battle";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
|
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
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
|
||||||
|
* @returns list of PlayerPokemon or EnemyPokemon on the field
|
||||||
|
*/
|
||||||
|
private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] {
|
||||||
|
if (!this.source.isPlayer()) {
|
||||||
|
return scene.getPlayerField() ?? [];
|
||||||
|
}
|
||||||
|
return scene.getEnemyField() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
|
||||||
|
* @param arena
|
||||||
|
*/
|
||||||
|
override onAdd({ scene }: Arena) {
|
||||||
|
this.source = scene.getPokemonById(this.sourceId!)!;
|
||||||
|
if (this.source) {
|
||||||
|
const party = this.retrieveField(scene);
|
||||||
|
party?.forEach((p: PlayerPokemon | EnemyPokemon ) => {
|
||||||
|
p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
|
||||||
|
});
|
||||||
|
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 {
|
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||||
switch (tagType) {
|
switch (tagType) {
|
||||||
|
@ -967,6 +1039,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
|
||||||
return new HappyHourTag(turnCount, sourceId, side);
|
return new HappyHourTag(turnCount, sourceId, side);
|
||||||
case ArenaTagType.SAFEGUARD:
|
case ArenaTagType.SAFEGUARD:
|
||||||
return new SafeguardTag(turnCount, sourceId, side);
|
return new SafeguardTag(turnCount, sourceId, side);
|
||||||
|
case ArenaTagType.IMPRISON:
|
||||||
|
return new ImprisonTag(sourceId, side);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "../messages";
|
||||||
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
||||||
import { StatusEffect } from "./status-effect";
|
import { StatusEffect } from "./status-effect";
|
||||||
import * as Utils from "../utils";
|
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 { Type } from "./type";
|
||||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
||||||
import { TerrainType } from "./terrain";
|
import { TerrainType } from "./terrain";
|
||||||
|
@ -2437,6 +2437,150 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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 | null;
|
||||||
|
|
||||||
|
constructor(sourceId: number) {
|
||||||
|
super(BattlerTagType.IMPRISON, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 1, Moves.IMPRISON, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAdd(pokemon: Pokemon) {
|
||||||
|
if (this.sourceId) {
|
||||||
|
this.source = pokemon.scene.getPokemonById(this.sourceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the source of Imprison is still active
|
||||||
|
* @param _pokemon
|
||||||
|
* @param _lapseType
|
||||||
|
* @returns `true` if the source is still active
|
||||||
|
*/
|
||||||
|
override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||||
|
return this.source?.isActive(true) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
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 {
|
||||||
|
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.
|
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
|
||||||
*
|
*
|
||||||
|
@ -2604,6 +2748,12 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||||
return new MysteryEncounterPostSummonTag();
|
return new MysteryEncounterPostSummonTag();
|
||||||
case BattlerTagType.HEAL_BLOCK:
|
case BattlerTagType.HEAL_BLOCK:
|
||||||
return new HealBlockTag(turnCount, sourceMove);
|
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:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
|
|
@ -7507,7 +7507,8 @@ export function initMoves() {
|
||||||
.target(MoveTarget.BOTH_SIDES),
|
.target(MoveTarget.BOTH_SIDES),
|
||||||
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
|
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
|
||||||
.ignoresSubstitute()
|
.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)
|
new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], 1)
|
.attr(StatStageChangeAttr, [ Stat.SPATK ], 1)
|
||||||
.attr(ConfuseAttr),
|
.attr(ConfuseAttr),
|
||||||
|
@ -7538,7 +7539,7 @@ export function initMoves() {
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false),
|
.attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false),
|
||||||
new StatusMove(Moves.TAUNT, Type.DARK, 100, 20, -1, 0, 3)
|
new StatusMove(Moves.TAUNT, Type.DARK, 100, 20, -1, 0, 3)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.unimplemented(),
|
.attr(AddBattlerTagAttr, BattlerTagType.TAUNT, false, true, 4),
|
||||||
new StatusMove(Moves.HELPING_HAND, Type.NORMAL, -1, 20, -1, 5, 3)
|
new StatusMove(Moves.HELPING_HAND, Type.NORMAL, -1, 20, -1, 5, 3)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND)
|
.attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
|
@ -7581,9 +7582,9 @@ export function initMoves() {
|
||||||
new StatusMove(Moves.SKILL_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
new StatusMove(Moves.SKILL_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.attr(SwitchAbilitiesAttr),
|
.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()
|
.ignoresSubstitute()
|
||||||
.unimplemented(),
|
.attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false),
|
||||||
new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3)
|
new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3)
|
||||||
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN)
|
.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)),
|
.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)),
|
||||||
|
|
|
@ -23,5 +23,6 @@ export enum ArenaTagType {
|
||||||
TAILWIND = "TAILWIND",
|
TAILWIND = "TAILWIND",
|
||||||
HAPPY_HOUR = "HAPPY_HOUR",
|
HAPPY_HOUR = "HAPPY_HOUR",
|
||||||
SAFEGUARD = "SAFEGUARD",
|
SAFEGUARD = "SAFEGUARD",
|
||||||
NO_CRIT = "NO_CRIT"
|
NO_CRIT = "NO_CRIT",
|
||||||
|
IMPRISON = "IMPRISON",
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,4 +82,7 @@ export enum BattlerTagType {
|
||||||
AUTOTOMIZED = "AUTOTOMIZED",
|
AUTOTOMIZED = "AUTOTOMIZED",
|
||||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
||||||
HEAL_BLOCK = "HEAL_BLOCK",
|
HEAL_BLOCK = "HEAL_BLOCK",
|
||||||
|
TORMENT = "TORMENT",
|
||||||
|
TAUNT = "TAUNT",
|
||||||
|
IMPRISON = "IMPRISON",
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,5 +74,8 @@
|
||||||
"substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!",
|
"substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!",
|
||||||
"substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!",
|
"substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!",
|
||||||
"substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!",
|
"substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!",
|
||||||
|
"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!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
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 "#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;
|
||||||
|
let game: GameManager;
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("double")
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset([Moves.HEAL_BLOCK, Moves.IMPRISON, Moves.SPLASH])
|
||||||
|
.enemySpecies(Species.SHUCKLE)
|
||||||
|
.ability(Abilities.AROMA_VEIL)
|
||||||
|
.moveset([Moves.GROWL]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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()! as PlayerPokemon[];
|
||||||
|
|
||||||
|
game.move.select(Moves.GROWL);
|
||||||
|
game.move.select(Moves.GROWL);
|
||||||
|
await game.forceEnemyMove(Moves.HEAL_BLOCK);
|
||||||
|
await game.toNextTurn();
|
||||||
|
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()! as PlayerPokemon[];
|
||||||
|
|
||||||
|
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();
|
||||||
|
expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined();
|
||||||
|
party.forEach(p => {
|
||||||
|
expect(p.getTag(BattlerTagType.IMPRISON)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,98 @@
|
||||||
|
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 "#enums/battler-tag-type";
|
||||||
|
import { ArenaTagType } from "#enums/arena-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, Moves.GROWL])
|
||||||
|
.enemySpecies(Species.SHUCKLE)
|
||||||
|
.moveset([Moves.TRANSFORM, Moves.SPLASH]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Pokemon under Imprison cannot use shared moves", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.REGIELEKI]);
|
||||||
|
|
||||||
|
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 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.STRUGGLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Imprison applies to Pokemon switched into Battle", async () => {
|
||||||
|
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, 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.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.GROWL);
|
||||||
|
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(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeUndefined();
|
||||||
|
expect(enemyPokemon.getTag(BattlerTagType.IMPRISON)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,54 @@
|
||||||
|
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 "#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 without Taunt
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,64 @@
|
||||||
|
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 "#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])
|
||||||
|
.ability(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue