|
|
@ -19,19 +19,88 @@ import { Stat } from "#enums/stat";
|
|
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
|
|
import type { Arena } from "#field/arena";
|
|
|
|
import type { Arena } from "#field/arena";
|
|
|
|
import type { Pokemon } from "#field/pokemon";
|
|
|
|
import type { Pokemon } from "#field/pokemon";
|
|
|
|
|
|
|
|
import type {
|
|
|
|
|
|
|
|
ArenaDelayedAttackTagType,
|
|
|
|
|
|
|
|
ArenaScreenTagType,
|
|
|
|
|
|
|
|
ArenaTagTypeData,
|
|
|
|
|
|
|
|
ArenaTrapTagType,
|
|
|
|
|
|
|
|
SerializableArenaTagType,
|
|
|
|
|
|
|
|
} from "#types/arena-tags";
|
|
|
|
|
|
|
|
import type { Mutable, NonFunctionProperties } from "#types/type-helpers";
|
|
|
|
import { BooleanHolder, NumberHolder, toDmgValue } from "#utils/common";
|
|
|
|
import { BooleanHolder, NumberHolder, toDmgValue } from "#utils/common";
|
|
|
|
import i18next from "i18next";
|
|
|
|
import i18next from "i18next";
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Add a class for tags that explicitly REQUIRE a source move (as currently we have a lot of bangs)
|
|
|
|
/*
|
|
|
|
|
|
|
|
ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon).
|
|
|
|
|
|
|
|
Examples include (but are not limited to)
|
|
|
|
|
|
|
|
- Cross-turn effects that persist even if the user/target switches out, such as Wish, Future Sight, and Happy Hour
|
|
|
|
|
|
|
|
- Effects that are applied to a specific side of the field, such as Crafty Shield, Reflect, and Spikes
|
|
|
|
|
|
|
|
- Field-Effects, like Gravity and Trick Room
|
|
|
|
|
|
|
|
|
|
|
|
export abstract class ArenaTag {
|
|
|
|
Any arena tag that persists across turns *must* extend from `SerializableArenaTag` in the class definition signature.
|
|
|
|
constructor(
|
|
|
|
|
|
|
|
public tagType: ArenaTagType,
|
|
|
|
Serializable ArenaTags have strict rules for their fields.
|
|
|
|
public turnCount: number,
|
|
|
|
These rules ensure that only the data necessary to reconstruct the tag is serialized, and that the
|
|
|
|
public sourceMove?: MoveId,
|
|
|
|
session loader is able to deserialize saved tags correctly.
|
|
|
|
public sourceId?: number,
|
|
|
|
|
|
|
|
public side: ArenaTagSide = ArenaTagSide.BOTH,
|
|
|
|
If the data is static (i.e. it is always the same for all instances of the class, such as the
|
|
|
|
) {}
|
|
|
|
type that is weakened by Mud Sport/Water Sport), then it must not be defined as a field, and must
|
|
|
|
|
|
|
|
instead be defined as a getter.
|
|
|
|
|
|
|
|
A static property is also acceptable, though static properties are less ergonomic with inheritance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If the data is mutable (i.e. it can change over the course of the tag's lifetime), then it *must*
|
|
|
|
|
|
|
|
be defined as a field, and it must be set in the `loadTag` method.
|
|
|
|
|
|
|
|
Such fields cannot be marked as `private/protected`, as if they were, typescript would omit them from
|
|
|
|
|
|
|
|
types that are based off of the class, namely, `ArenaTagTypeData`. It is preferrable to trade the
|
|
|
|
|
|
|
|
type-safety of private/protected fields for the type safety when deserializing arena tags from save data.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For data that is mutable only within a turn (e.g. SuppressAbilitiesTag's beingRemoved field),
|
|
|
|
|
|
|
|
where it does not make sense to be serialized, the field should use ES2020's private field syntax (a `#` prepended to the field name).
|
|
|
|
|
|
|
|
If the field should be accessible outside of the class, then a public getter should be used.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Interface containing the serializable fields of ArenaTagData. */
|
|
|
|
|
|
|
|
interface BaseArenaTag {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* The tag's remaining duration. Setting to any number `<=0` will make the tag's duration effectively infinite.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
turnCount: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* The {@linkcode MoveId} that created this tag, or `undefined` if not set by a move.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
sourceMove?: MoveId;
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created the tag, or `undefined` if not set by a Pokemon.
|
|
|
|
|
|
|
|
* @todo Implement handling for `ArenaTag`s created by non-pokemon sources (most tags will throw errors without a source)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Note: Intentionally not using `?`, as the property should always exist, but just be undefined if not present.
|
|
|
|
|
|
|
|
sourceId: number | undefined;
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* The {@linkcode ArenaTagSide | side of the field} that this arena tag affects.
|
|
|
|
|
|
|
|
* @defaultValue `ArenaTagSide.BOTH`
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
side: ArenaTagSide;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* An {@linkcode ArenaTag} represents a semi-persistent effect affecting a given _side_ of the field.
|
|
|
|
|
|
|
|
* Unlike {@linkcode BattlerTag}s (which are tied to individual {@linkcode Pokemon}), `ArenaTag`s function independently of
|
|
|
|
|
|
|
|
* the Pokemon currently on-field, only cleared on arena reset or through their respective {@linkcode ArenaTag.lapse | lapse} methods.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export abstract class ArenaTag implements BaseArenaTag {
|
|
|
|
|
|
|
|
/** The type of the arena tag */
|
|
|
|
|
|
|
|
public abstract readonly tagType: ArenaTagType;
|
|
|
|
|
|
|
|
public turnCount: number;
|
|
|
|
|
|
|
|
public sourceMove?: MoveId;
|
|
|
|
|
|
|
|
public sourceId: number | undefined;
|
|
|
|
|
|
|
|
public side: ArenaTagSide;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceMove?: MoveId, sourceId?: number, side: ArenaTagSide = ArenaTagSide.BOTH) {
|
|
|
|
|
|
|
|
this.turnCount = turnCount;
|
|
|
|
|
|
|
|
this.sourceMove = sourceMove;
|
|
|
|
|
|
|
|
this.sourceId = sourceId;
|
|
|
|
|
|
|
|
this.side = side;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
apply(_arena: Arena, _simulated: boolean, ..._args: unknown[]): boolean {
|
|
|
|
apply(_arena: Arena, _simulated: boolean, ..._args: unknown[]): boolean {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
@ -72,9 +141,9 @@ export abstract class ArenaTag {
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* When given a arena tag or json representing one, load the data for it.
|
|
|
|
* When given a arena tag or json representing one, load the data for it.
|
|
|
|
* This is meant to be inherited from by any arena tag with custom attributes
|
|
|
|
* This is meant to be inherited from by any arena tag with custom attributes
|
|
|
|
* @param {ArenaTag | any} source An arena tag
|
|
|
|
* @param source - The {@linkcode BaseArenaTag} being loaded
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
loadTag(source: ArenaTag | any): void {
|
|
|
|
loadTag(source: BaseArenaTag): void {
|
|
|
|
this.turnCount = source.turnCount;
|
|
|
|
this.turnCount = source.turnCount;
|
|
|
|
this.sourceMove = source.sourceMove;
|
|
|
|
this.sourceMove = source.sourceMove;
|
|
|
|
this.sourceId = source.sourceId;
|
|
|
|
this.sourceId = source.sourceId;
|
|
|
@ -107,13 +176,21 @@ export abstract class ArenaTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Abstract class for arena tags that can persist across turns.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export abstract class SerializableArenaTag extends ArenaTag {
|
|
|
|
|
|
|
|
abstract readonly tagType: SerializableArenaTagType;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Mist_(move) Mist}.
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Mist_(move) Mist}.
|
|
|
|
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
|
|
|
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class MistTag extends ArenaTag {
|
|
|
|
export class MistTag extends SerializableArenaTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
readonly tagType = ArenaTagType.MIST;
|
|
|
|
super(ArenaTagType.MIST, turnCount, MoveId.MIST, sourceId, side);
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.MIST, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
@ -170,33 +247,11 @@ export class MistTag extends ArenaTag {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Reduces the damage of specific move categories in the arena.
|
|
|
|
* Reduces the damage of specific move categories in the arena.
|
|
|
|
* @extends ArenaTag
|
|
|
|
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class WeakenMoveScreenTag extends ArenaTag {
|
|
|
|
export abstract class WeakenMoveScreenTag extends SerializableArenaTag {
|
|
|
|
protected weakenedCategories: MoveCategory[];
|
|
|
|
public abstract readonly tagType: ArenaScreenTagType;
|
|
|
|
|
|
|
|
// Getter to avoid unnecessary serialization and prevent modification
|
|
|
|
/**
|
|
|
|
protected abstract get weakenedCategories(): MoveCategory[];
|
|
|
|
* Creates a new instance of the WeakenMoveScreenTag class.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param tagType - The type of the arena tag.
|
|
|
|
|
|
|
|
* @param turnCount - The number of turns the tag is active.
|
|
|
|
|
|
|
|
* @param sourceMove - The move that created the tag.
|
|
|
|
|
|
|
|
* @param sourceId - The ID of the source of the tag.
|
|
|
|
|
|
|
|
* @param side - The side (player or enemy) the tag affects.
|
|
|
|
|
|
|
|
* @param weakenedCategories - The categories of moves that are weakened by this tag.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
|
|
|
tagType: ArenaTagType,
|
|
|
|
|
|
|
|
turnCount: number,
|
|
|
|
|
|
|
|
sourceMove: MoveId,
|
|
|
|
|
|
|
|
sourceId: number,
|
|
|
|
|
|
|
|
side: ArenaTagSide,
|
|
|
|
|
|
|
|
weakenedCategories: MoveCategory[],
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
super(tagType, turnCount, sourceMove, sourceId, side);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.weakenedCategories = weakenedCategories;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Applies the weakening effect to the move.
|
|
|
|
* Applies the weakening effect to the move.
|
|
|
@ -233,8 +288,13 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|
|
|
* Used by {@linkcode MoveId.REFLECT}
|
|
|
|
* Used by {@linkcode MoveId.REFLECT}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class ReflectTag extends WeakenMoveScreenTag {
|
|
|
|
class ReflectTag extends WeakenMoveScreenTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.REFLECT;
|
|
|
|
super(ArenaTagType.REFLECT, turnCount, MoveId.REFLECT, sourceId, side, [MoveCategory.PHYSICAL]);
|
|
|
|
protected get weakenedCategories(): [MoveCategory.PHYSICAL] {
|
|
|
|
|
|
|
|
return [MoveCategory.PHYSICAL];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.REFLECT, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
@ -253,8 +313,12 @@ class ReflectTag extends WeakenMoveScreenTag {
|
|
|
|
* Used by {@linkcode MoveId.LIGHT_SCREEN}
|
|
|
|
* Used by {@linkcode MoveId.LIGHT_SCREEN}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class LightScreenTag extends WeakenMoveScreenTag {
|
|
|
|
class LightScreenTag extends WeakenMoveScreenTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.LIGHT_SCREEN;
|
|
|
|
super(ArenaTagType.LIGHT_SCREEN, turnCount, MoveId.LIGHT_SCREEN, sourceId, side, [MoveCategory.SPECIAL]);
|
|
|
|
protected get weakenedCategories(): [MoveCategory.SPECIAL] {
|
|
|
|
|
|
|
|
return [MoveCategory.SPECIAL];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.LIGHT_SCREEN, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
@ -273,11 +337,13 @@ class LightScreenTag extends WeakenMoveScreenTag {
|
|
|
|
* Used by {@linkcode MoveId.AURORA_VEIL}
|
|
|
|
* Used by {@linkcode MoveId.AURORA_VEIL}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class AuroraVeilTag extends WeakenMoveScreenTag {
|
|
|
|
class AuroraVeilTag extends WeakenMoveScreenTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.AURORA_VEIL;
|
|
|
|
super(ArenaTagType.AURORA_VEIL, turnCount, MoveId.AURORA_VEIL, sourceId, side, [
|
|
|
|
protected get weakenedCategories(): [MoveCategory.PHYSICAL, MoveCategory.SPECIAL] {
|
|
|
|
MoveCategory.SPECIAL,
|
|
|
|
return [MoveCategory.PHYSICAL, MoveCategory.SPECIAL];
|
|
|
|
MoveCategory.PHYSICAL,
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.AURORA_VEIL, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
@ -297,21 +363,23 @@ type ProtectConditionFunc = (arena: Arena, moveId: MoveId) => boolean;
|
|
|
|
* Class to implement conditional team protection
|
|
|
|
* Class to implement conditional team protection
|
|
|
|
* applies protection based on the attributes of incoming moves
|
|
|
|
* applies protection based on the attributes of incoming moves
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class ConditionalProtectTag extends ArenaTag {
|
|
|
|
export abstract class ConditionalProtectTag extends ArenaTag {
|
|
|
|
/** The condition function to determine which moves are negated */
|
|
|
|
/** The condition function to determine which moves are negated */
|
|
|
|
protected protectConditionFunc: ProtectConditionFunc;
|
|
|
|
protected protectConditionFunc: ProtectConditionFunc;
|
|
|
|
/** Does this apply to all moves, including those that ignore other forms of protection? */
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Whether this protection effect should apply to _all_ moves, including ones that ignore other forms of protection.
|
|
|
|
|
|
|
|
* @defaultValue `false`
|
|
|
|
|
|
|
|
*/
|
|
|
|
protected ignoresBypass: boolean;
|
|
|
|
protected ignoresBypass: boolean;
|
|
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
constructor(
|
|
|
|
tagType: ArenaTagType,
|
|
|
|
|
|
|
|
sourceMove: MoveId,
|
|
|
|
sourceMove: MoveId,
|
|
|
|
sourceId: number,
|
|
|
|
sourceId: number | undefined,
|
|
|
|
side: ArenaTagSide,
|
|
|
|
side: ArenaTagSide,
|
|
|
|
condition: ProtectConditionFunc,
|
|
|
|
condition: ProtectConditionFunc,
|
|
|
|
ignoresBypass = false,
|
|
|
|
ignoresBypass = false,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
super(tagType, 1, sourceMove, sourceId, side);
|
|
|
|
super(1, sourceMove, sourceId, side);
|
|
|
|
|
|
|
|
|
|
|
|
this.protectConditionFunc = condition;
|
|
|
|
this.protectConditionFunc = condition;
|
|
|
|
this.ignoresBypass = ignoresBypass;
|
|
|
|
this.ignoresBypass = ignoresBypass;
|
|
|
@ -397,8 +465,9 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => {
|
|
|
|
* Condition: The incoming move has increased priority.
|
|
|
|
* Condition: The incoming move has increased priority.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class QuickGuardTag extends ConditionalProtectTag {
|
|
|
|
class QuickGuardTag extends ConditionalProtectTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.QUICK_GUARD;
|
|
|
|
super(ArenaTagType.QUICK_GUARD, MoveId.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -428,8 +497,9 @@ const WideGuardConditionFunc: ProtectConditionFunc = (_arena, moveId): boolean =
|
|
|
|
* can be an ally or enemy.
|
|
|
|
* can be an ally or enemy.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class WideGuardTag extends ConditionalProtectTag {
|
|
|
|
class WideGuardTag extends ConditionalProtectTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.WIDE_GUARD;
|
|
|
|
super(ArenaTagType.WIDE_GUARD, MoveId.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -450,8 +520,9 @@ const MatBlockConditionFunc: ProtectConditionFunc = (_arena, moveId): boolean =>
|
|
|
|
* Condition: The incoming move is a Physical or Special attack move.
|
|
|
|
* Condition: The incoming move is a Physical or Special attack move.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class MatBlockTag extends ConditionalProtectTag {
|
|
|
|
class MatBlockTag extends ConditionalProtectTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.MAT_BLOCK;
|
|
|
|
super(ArenaTagType.MAT_BLOCK, MoveId.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena) {
|
|
|
|
onAdd(_arena: Arena) {
|
|
|
@ -494,8 +565,9 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (_arena, moveId) => {
|
|
|
|
* not target all Pokemon or sides of the field.
|
|
|
|
* not target all Pokemon or sides of the field.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class CraftyShieldTag extends ConditionalProtectTag {
|
|
|
|
class CraftyShieldTag extends ConditionalProtectTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.CRAFTY_SHIELD;
|
|
|
|
super(ArenaTagType.CRAFTY_SHIELD, MoveId.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -503,17 +575,8 @@ class CraftyShieldTag extends ConditionalProtectTag {
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Lucky_Chant_(move) Lucky Chant}.
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Lucky_Chant_(move) Lucky Chant}.
|
|
|
|
* Prevents critical hits against the tag's side.
|
|
|
|
* Prevents critical hits against the tag's side.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class NoCritTag extends ArenaTag {
|
|
|
|
export class NoCritTag extends SerializableArenaTag {
|
|
|
|
/**
|
|
|
|
public readonly tagType = ArenaTagType.NO_CRIT;
|
|
|
|
* Constructor method for the NoCritTag class
|
|
|
|
|
|
|
|
* @param turnCount `number` the number of turns this effect lasts
|
|
|
|
|
|
|
|
* @param sourceMove {@linkcode MoveId} the move that created this effect
|
|
|
|
|
|
|
|
* @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect
|
|
|
|
|
|
|
|
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceMove: MoveId, sourceId: number, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Queues a message upon adding this effect to the field */
|
|
|
|
/** Queues a message upon adding this effect to the field */
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -545,13 +608,17 @@ export class NoCritTag extends ArenaTag {
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) | Wish}.
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) | Wish}.
|
|
|
|
* Heals the Pokémon in the user's position the turn after Wish is used.
|
|
|
|
* Heals the Pokémon in the user's position the turn after Wish is used.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class WishTag extends ArenaTag {
|
|
|
|
class WishTag extends SerializableArenaTag {
|
|
|
|
private battlerIndex: BattlerIndex;
|
|
|
|
// The following fields are meant to be inwardly mutable, but outwardly immutable.
|
|
|
|
private triggerMessage: string;
|
|
|
|
readonly battlerIndex: BattlerIndex;
|
|
|
|
private healHp: number;
|
|
|
|
readonly healHp: number;
|
|
|
|
|
|
|
|
readonly sourceName: string;
|
|
|
|
|
|
|
|
// End inwardly mutable fields
|
|
|
|
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.WISH;
|
|
|
|
super(ArenaTagType.WISH, turnCount, MoveId.WISH, sourceId, side);
|
|
|
|
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.WISH, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -561,45 +628,38 @@ class WishTag extends ArenaTag {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
super.onAdd(_arena);
|
|
|
|
(this as Mutable<this>).sourceName = getPokemonNameWithAffix(source);
|
|
|
|
this.healHp = toDmgValue(source.getMaxHp() / 2);
|
|
|
|
(this as Mutable<this>).healHp = toDmgValue(source.getMaxHp() / 2);
|
|
|
|
|
|
|
|
(this as Mutable<this>).battlerIndex = source.getBattlerIndex();
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
|
|
|
|
|
|
i18next.t("arenaTag:wishTagOnAdd", {
|
|
|
|
|
|
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onRemove(_arena: Arena): void {
|
|
|
|
onRemove(_arena: Arena): void {
|
|
|
|
const target = globalScene.getField()[this.battlerIndex];
|
|
|
|
const target = globalScene.getField()[this.battlerIndex];
|
|
|
|
if (target?.isActive(true)) {
|
|
|
|
if (target?.isActive(true)) {
|
|
|
|
globalScene.phaseManager.queueMessage(this.triggerMessage);
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
|
|
|
|
|
|
// TODO: Rename key as it triggers on activation
|
|
|
|
|
|
|
|
i18next.t("arenaTag:wishTagOnAdd", {
|
|
|
|
|
|
|
|
pokemonNameWithAffix: this.sourceName,
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
);
|
|
|
|
globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), this.healHp, null, true, false);
|
|
|
|
globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), this.healHp, null, true, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override loadTag(source: NonFunctionProperties<WishTag>): void {
|
|
|
|
|
|
|
|
super.loadTag(source);
|
|
|
|
|
|
|
|
(this as Mutable<this>).battlerIndex = source.battlerIndex;
|
|
|
|
|
|
|
|
(this as Mutable<this>).healHp = source.healHp;
|
|
|
|
|
|
|
|
(this as Mutable<this>).sourceName = source.sourceName;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Abstract class to implement weakened moves of a specific type.
|
|
|
|
* Abstract class to implement weakened moves of a specific type.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class WeakenMoveTypeTag extends ArenaTag {
|
|
|
|
export abstract class WeakenMoveTypeTag extends SerializableArenaTag {
|
|
|
|
private weakenedType: PokemonType;
|
|
|
|
abstract readonly tagType: ArenaTagType.MUD_SPORT | ArenaTagType.WATER_SPORT;
|
|
|
|
|
|
|
|
abstract get weakenedType(): PokemonType;
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Creates a new instance of the WeakenMoveTypeTag class.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param tagType - The type of the arena tag.
|
|
|
|
|
|
|
|
* @param turnCount - The number of turns the tag is active.
|
|
|
|
|
|
|
|
* @param type - The type being weakened from this tag.
|
|
|
|
|
|
|
|
* @param sourceMove - The move that created the tag.
|
|
|
|
|
|
|
|
* @param sourceId - The ID of the source of the tag.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(tagType: ArenaTagType, turnCount: number, type: PokemonType, sourceMove: MoveId, sourceId: number) {
|
|
|
|
|
|
|
|
super(tagType, turnCount, sourceMove, sourceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.weakenedType = type;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Reduces an attack's power by 0.33x if it matches this tag's weakened type.
|
|
|
|
* Reduces an attack's power by 0.33x if it matches this tag's weakened type.
|
|
|
@ -623,8 +683,12 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
|
|
|
* Weakens Electric type moves for a set amount of turns, usually 5.
|
|
|
|
* Weakens Electric type moves for a set amount of turns, usually 5.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class MudSportTag extends WeakenMoveTypeTag {
|
|
|
|
class MudSportTag extends WeakenMoveTypeTag {
|
|
|
|
constructor(turnCount: number, sourceId: number) {
|
|
|
|
public readonly tagType = ArenaTagType.MUD_SPORT;
|
|
|
|
super(ArenaTagType.MUD_SPORT, turnCount, PokemonType.ELECTRIC, MoveId.MUD_SPORT, sourceId);
|
|
|
|
override get weakenedType(): PokemonType.ELECTRIC {
|
|
|
|
|
|
|
|
return PokemonType.ELECTRIC;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId?: number) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.MUD_SPORT, sourceId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -641,8 +705,12 @@ class MudSportTag extends WeakenMoveTypeTag {
|
|
|
|
* Weakens Fire type moves for a set amount of turns, usually 5.
|
|
|
|
* Weakens Fire type moves for a set amount of turns, usually 5.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class WaterSportTag extends WeakenMoveTypeTag {
|
|
|
|
class WaterSportTag extends WeakenMoveTypeTag {
|
|
|
|
constructor(turnCount: number, sourceId: number) {
|
|
|
|
public readonly tagType = ArenaTagType.WATER_SPORT;
|
|
|
|
super(ArenaTagType.WATER_SPORT, turnCount, PokemonType.FIRE, MoveId.WATER_SPORT, sourceId);
|
|
|
|
override get weakenedType(): PokemonType.FIRE {
|
|
|
|
|
|
|
|
return PokemonType.FIRE;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(turnCount: number, sourceId?: number) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.WATER_SPORT, sourceId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -660,8 +728,9 @@ class WaterSportTag extends WeakenMoveTypeTag {
|
|
|
|
* Converts Normal-type moves to Electric type for the rest of the turn.
|
|
|
|
* Converts Normal-type moves to Electric type for the rest of the turn.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class IonDelugeTag extends ArenaTag {
|
|
|
|
export class IonDelugeTag extends ArenaTag {
|
|
|
|
|
|
|
|
public readonly tagType = ArenaTagType.ION_DELUGE;
|
|
|
|
constructor(sourceMove?: MoveId) {
|
|
|
|
constructor(sourceMove?: MoveId) {
|
|
|
|
super(ArenaTagType.ION_DELUGE, 1, sourceMove);
|
|
|
|
super(1, sourceMove);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Queues an on-add message */
|
|
|
|
/** Queues an on-add message */
|
|
|
@ -690,7 +759,8 @@ export class IonDelugeTag extends ArenaTag {
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Abstract class to implement arena traps.
|
|
|
|
* Abstract class to implement arena traps.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class ArenaTrapTag extends ArenaTag {
|
|
|
|
export abstract class ArenaTrapTag extends SerializableArenaTag {
|
|
|
|
|
|
|
|
abstract readonly tagType: ArenaTrapTagType;
|
|
|
|
public layers: number;
|
|
|
|
public layers: number;
|
|
|
|
public maxLayers: number;
|
|
|
|
public maxLayers: number;
|
|
|
|
|
|
|
|
|
|
|
@ -703,8 +773,8 @@ export class ArenaTrapTag extends ArenaTag {
|
|
|
|
* @param side - The side (player or enemy) the tag affects.
|
|
|
|
* @param side - The side (player or enemy) the tag affects.
|
|
|
|
* @param maxLayers - The maximum amount of layers this tag can have.
|
|
|
|
* @param maxLayers - The maximum amount of layers this tag can have.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
constructor(tagType: ArenaTagType, sourceMove: MoveId, sourceId: number, side: ArenaTagSide, maxLayers: number) {
|
|
|
|
constructor(sourceMove: MoveId, sourceId: number | undefined, side: ArenaTagSide, maxLayers: number) {
|
|
|
|
super(tagType, 0, sourceMove, sourceId, side);
|
|
|
|
super(0, sourceMove, sourceId, side);
|
|
|
|
|
|
|
|
|
|
|
|
this.layers = 1;
|
|
|
|
this.layers = 1;
|
|
|
|
this.maxLayers = maxLayers;
|
|
|
|
this.maxLayers = maxLayers;
|
|
|
@ -743,7 +813,7 @@ export class ArenaTrapTag extends ArenaTag {
|
|
|
|
: Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
|
|
|
: Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadTag(source: any): void {
|
|
|
|
loadTag(source: NonFunctionProperties<ArenaTrapTag>): void {
|
|
|
|
super.loadTag(source);
|
|
|
|
super.loadTag(source);
|
|
|
|
this.layers = source.layers;
|
|
|
|
this.layers = source.layers;
|
|
|
|
this.maxLayers = source.maxLayers;
|
|
|
|
this.maxLayers = source.maxLayers;
|
|
|
@ -756,8 +826,9 @@ export class ArenaTrapTag extends ArenaTag {
|
|
|
|
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
|
|
|
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class SpikesTag extends ArenaTrapTag {
|
|
|
|
class SpikesTag extends ArenaTrapTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.SPIKES;
|
|
|
|
super(ArenaTagType.SPIKES, MoveId.SPIKES, sourceId, side, 3);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.SPIKES, sourceId, side, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
@ -814,11 +885,12 @@ class SpikesTag extends ArenaTrapTag {
|
|
|
|
* Pokémon summoned into this trap remove it entirely.
|
|
|
|
* Pokémon summoned into this trap remove it entirely.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class ToxicSpikesTag extends ArenaTrapTag {
|
|
|
|
class ToxicSpikesTag extends ArenaTrapTag {
|
|
|
|
private neutralized: boolean;
|
|
|
|
#neutralized: boolean;
|
|
|
|
|
|
|
|
public readonly tagType = ArenaTagType.TOXIC_SPIKES;
|
|
|
|
|
|
|
|
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
super(ArenaTagType.TOXIC_SPIKES, MoveId.TOXIC_SPIKES, sourceId, side, 2);
|
|
|
|
super(MoveId.TOXIC_SPIKES, sourceId, side, 2);
|
|
|
|
this.neutralized = false;
|
|
|
|
this.#neutralized = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
@ -844,7 +916,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onRemove(arena: Arena): void {
|
|
|
|
onRemove(arena: Arena): void {
|
|
|
|
if (!this.neutralized) {
|
|
|
|
if (!this.#neutralized) {
|
|
|
|
super.onRemove(arena);
|
|
|
|
super.onRemove(arena);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -855,7 +927,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pokemon.isOfType(PokemonType.POISON)) {
|
|
|
|
if (pokemon.isOfType(PokemonType.POISON)) {
|
|
|
|
this.neutralized = true;
|
|
|
|
this.#neutralized = true;
|
|
|
|
if (globalScene.arena.removeTag(this.tagType)) {
|
|
|
|
if (globalScene.arena.removeTag(this.tagType)) {
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
|
|
i18next.t("arenaTag:toxicSpikesActivateTrapPoison", {
|
|
|
|
i18next.t("arenaTag:toxicSpikesActivateTrapPoison", {
|
|
|
@ -889,55 +961,15 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Arena Tag class for delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}.
|
|
|
|
|
|
|
|
* Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used),
|
|
|
|
|
|
|
|
* and deals damage after the turn count is reached.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export class DelayedAttackTag extends ArenaTag {
|
|
|
|
|
|
|
|
public targetIndex: BattlerIndex;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
|
|
|
tagType: ArenaTagType,
|
|
|
|
|
|
|
|
sourceMove: MoveId | undefined,
|
|
|
|
|
|
|
|
sourceId: number,
|
|
|
|
|
|
|
|
targetIndex: BattlerIndex,
|
|
|
|
|
|
|
|
side: ArenaTagSide = ArenaTagSide.BOTH,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
super(tagType, 3, sourceMove, sourceId, side);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.targetIndex = targetIndex;
|
|
|
|
|
|
|
|
this.side = side;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lapse(arena: Arena): boolean {
|
|
|
|
|
|
|
|
const ret = super.lapse(arena);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
|
|
|
|
// TODO: This should not add to move history (for Spite)
|
|
|
|
|
|
|
|
globalScene.phaseManager.unshiftNew(
|
|
|
|
|
|
|
|
"MoveEffectPhase",
|
|
|
|
|
|
|
|
this.sourceId!,
|
|
|
|
|
|
|
|
[this.targetIndex],
|
|
|
|
|
|
|
|
allMoves[this.sourceMove!],
|
|
|
|
|
|
|
|
MoveUseMode.FOLLOW_UP,
|
|
|
|
|
|
|
|
); // TODO: are those bangs correct?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onRemove(_arena: Arena): void {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Stealth_Rock_(move) Stealth Rock}.
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Stealth_Rock_(move) Stealth Rock}.
|
|
|
|
* Applies up to 1 layer of Stealth Rocks, dealing percentage-based damage to any Pokémon
|
|
|
|
* Applies up to 1 layer of Stealth Rocks, dealing percentage-based damage to any Pokémon
|
|
|
|
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
|
|
|
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class StealthRockTag extends ArenaTrapTag {
|
|
|
|
class StealthRockTag extends ArenaTrapTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.STEALTH_ROCK;
|
|
|
|
super(ArenaTagType.STEALTH_ROCK, MoveId.STEALTH_ROCK, sourceId, side, 1);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.STEALTH_ROCK, sourceId, side, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
@ -1025,8 +1057,9 @@ class StealthRockTag extends ArenaTrapTag {
|
|
|
|
* to any Pokémon who is summoned into this trap.
|
|
|
|
* to any Pokémon who is summoned into this trap.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class StickyWebTag extends ArenaTrapTag {
|
|
|
|
class StickyWebTag extends ArenaTrapTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.STICKY_WEB;
|
|
|
|
super(ArenaTagType.STICKY_WEB, MoveId.STICKY_WEB, sourceId, side, 1);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.STICKY_WEB, sourceId, side, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
|
onAdd(arena: Arena, quiet = false): void {
|
|
|
@ -1093,14 +1126,57 @@ class StickyWebTag extends ArenaTrapTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Arena Tag class for delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}.
|
|
|
|
|
|
|
|
* Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used),
|
|
|
|
|
|
|
|
* and deals damage after the turn count is reached.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export class DelayedAttackTag extends SerializableArenaTag {
|
|
|
|
|
|
|
|
public targetIndex: BattlerIndex;
|
|
|
|
|
|
|
|
public readonly tagType: ArenaDelayedAttackTagType;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
|
|
|
tagType: ArenaTagType.DOOM_DESIRE | ArenaTagType.FUTURE_SIGHT,
|
|
|
|
|
|
|
|
sourceMove: MoveId | undefined,
|
|
|
|
|
|
|
|
sourceId: number | undefined,
|
|
|
|
|
|
|
|
targetIndex: BattlerIndex,
|
|
|
|
|
|
|
|
side: ArenaTagSide = ArenaTagSide.BOTH,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
super(3, sourceMove, sourceId, side);
|
|
|
|
|
|
|
|
this.tagType = tagType;
|
|
|
|
|
|
|
|
this.targetIndex = targetIndex;
|
|
|
|
|
|
|
|
this.side = side;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lapse(arena: Arena): boolean {
|
|
|
|
|
|
|
|
const ret = super.lapse(arena);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
|
|
|
|
// TODO: This should not add to move history (for Spite)
|
|
|
|
|
|
|
|
globalScene.phaseManager.unshiftNew(
|
|
|
|
|
|
|
|
"MoveEffectPhase",
|
|
|
|
|
|
|
|
this.sourceId!,
|
|
|
|
|
|
|
|
[this.targetIndex],
|
|
|
|
|
|
|
|
allMoves[this.sourceMove!],
|
|
|
|
|
|
|
|
MoveUseMode.FOLLOW_UP,
|
|
|
|
|
|
|
|
); // TODO: are those bangs correct?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onRemove(_arena: Arena): void {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Trick_Room_(move) Trick Room}.
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Trick_Room_(move) Trick Room}.
|
|
|
|
* Reverses the Speed stats for all Pokémon on the field as long as this arena tag is up,
|
|
|
|
* Reverses the Speed stats for all Pokémon on the field as long as this arena tag is up,
|
|
|
|
* also reversing the turn order for all Pokémon on the field as well.
|
|
|
|
* also reversing the turn order for all Pokémon on the field as well.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class TrickRoomTag extends ArenaTag {
|
|
|
|
export class TrickRoomTag extends SerializableArenaTag {
|
|
|
|
constructor(turnCount: number, sourceId: number) {
|
|
|
|
public readonly tagType = ArenaTagType.TRICK_ROOM;
|
|
|
|
super(ArenaTagType.TRICK_ROOM, turnCount, MoveId.TRICK_ROOM, sourceId);
|
|
|
|
constructor(turnCount: number, sourceId?: number) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.TRICK_ROOM, sourceId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
@ -1142,9 +1218,10 @@ export class TrickRoomTag extends ArenaTag {
|
|
|
|
* Grounds all Pokémon on the field, including Flying-types and those with
|
|
|
|
* Grounds all Pokémon on the field, including Flying-types and those with
|
|
|
|
* {@linkcode AbilityId.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
|
|
|
* {@linkcode AbilityId.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class GravityTag extends ArenaTag {
|
|
|
|
export class GravityTag extends SerializableArenaTag {
|
|
|
|
constructor(turnCount: number) {
|
|
|
|
public readonly tagType = ArenaTagType.GRAVITY;
|
|
|
|
super(ArenaTagType.GRAVITY, turnCount, MoveId.GRAVITY);
|
|
|
|
constructor(turnCount: number, sourceId?: number) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.GRAVITY, sourceId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -1170,9 +1247,10 @@ export class GravityTag extends ArenaTag {
|
|
|
|
* Doubles the Speed of the Pokémon who created this arena tag, as well as all allied Pokémon.
|
|
|
|
* Doubles the Speed of the Pokémon who created this arena tag, as well as all allied Pokémon.
|
|
|
|
* Applies this arena tag for 4 turns (including the turn the move was used).
|
|
|
|
* Applies this arena tag for 4 turns (including the turn the move was used).
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class TailwindTag extends ArenaTag {
|
|
|
|
class TailwindTag extends SerializableArenaTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.TAILWIND;
|
|
|
|
super(ArenaTagType.TAILWIND, turnCount, MoveId.TAILWIND, sourceId, side);
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.TAILWIND, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
|
onAdd(_arena: Arena, quiet = false): void {
|
|
|
@ -1238,9 +1316,10 @@ class TailwindTag extends ArenaTag {
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Happy_Hour_(move) Happy Hour}.
|
|
|
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Happy_Hour_(move) Happy Hour}.
|
|
|
|
* Doubles the prize money from trainers and money moves like {@linkcode MoveId.PAY_DAY} and {@linkcode MoveId.MAKE_IT_RAIN}.
|
|
|
|
* Doubles the prize money from trainers and money moves like {@linkcode MoveId.PAY_DAY} and {@linkcode MoveId.MAKE_IT_RAIN}.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class HappyHourTag extends ArenaTag {
|
|
|
|
class HappyHourTag extends SerializableArenaTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.HAPPY_HOUR;
|
|
|
|
super(ArenaTagType.HAPPY_HOUR, turnCount, MoveId.HAPPY_HOUR, sourceId, side);
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.HAPPY_HOUR, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -1253,8 +1332,9 @@ class HappyHourTag extends ArenaTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class SafeguardTag extends ArenaTag {
|
|
|
|
class SafeguardTag extends ArenaTag {
|
|
|
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.SAFEGUARD;
|
|
|
|
super(ArenaTagType.SAFEGUARD, turnCount, MoveId.SAFEGUARD, sourceId, side);
|
|
|
|
constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.SAFEGUARD, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -1275,18 +1355,21 @@ class SafeguardTag extends ArenaTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class NoneTag extends ArenaTag {
|
|
|
|
class NoneTag extends ArenaTag {
|
|
|
|
|
|
|
|
public readonly tagType = ArenaTagType.NONE;
|
|
|
|
constructor() {
|
|
|
|
constructor() {
|
|
|
|
super(ArenaTagType.NONE, 0);
|
|
|
|
super(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* This arena tag facilitates the application of the move Imprison
|
|
|
|
* 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 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.
|
|
|
|
* Imprison will apply to any opposing Pokemon that switch onto the field as well.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class ImprisonTag extends ArenaTrapTag {
|
|
|
|
class ImprisonTag extends ArenaTrapTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.IMPRISON;
|
|
|
|
super(ArenaTagType.IMPRISON, MoveId.IMPRISON, sourceId, side, 1);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(MoveId.IMPRISON, sourceId, side, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
@ -1341,7 +1424,9 @@ class ImprisonTag extends ArenaTrapTag {
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
override onRemove(): void {
|
|
|
|
override onRemove(): void {
|
|
|
|
const party = this.getAffectedPokemon();
|
|
|
|
const party = this.getAffectedPokemon();
|
|
|
|
party.forEach(p => p.removeTag(BattlerTagType.IMPRISON));
|
|
|
|
party.forEach(p => {
|
|
|
|
|
|
|
|
p.removeTag(BattlerTagType.IMPRISON);
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -1352,9 +1437,10 @@ class ImprisonTag extends ArenaTrapTag {
|
|
|
|
* Damages all non-Fire-type Pokemon on the given side of the field at the end
|
|
|
|
* Damages all non-Fire-type Pokemon on the given side of the field at the end
|
|
|
|
* of each turn for 4 turns.
|
|
|
|
* of each turn for 4 turns.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class FireGrassPledgeTag extends ArenaTag {
|
|
|
|
class FireGrassPledgeTag extends SerializableArenaTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.FIRE_GRASS_PLEDGE;
|
|
|
|
super(ArenaTagType.FIRE_GRASS_PLEDGE, 4, MoveId.FIRE_PLEDGE, sourceId, side);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(4, MoveId.FIRE_PLEDGE, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override onAdd(_arena: Arena): void {
|
|
|
|
override onAdd(_arena: Arena): void {
|
|
|
@ -1400,9 +1486,10 @@ class FireGrassPledgeTag extends ArenaTag {
|
|
|
|
* Doubles the secondary effect chance of moves from Pokemon on the
|
|
|
|
* Doubles the secondary effect chance of moves from Pokemon on the
|
|
|
|
* given side of the field for 4 turns.
|
|
|
|
* given side of the field for 4 turns.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class WaterFirePledgeTag extends ArenaTag {
|
|
|
|
class WaterFirePledgeTag extends SerializableArenaTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.WATER_FIRE_PLEDGE;
|
|
|
|
super(ArenaTagType.WATER_FIRE_PLEDGE, 4, MoveId.WATER_PLEDGE, sourceId, side);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(4, MoveId.WATER_PLEDGE, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override onAdd(_arena: Arena): void {
|
|
|
|
override onAdd(_arena: Arena): void {
|
|
|
@ -1434,9 +1521,10 @@ class WaterFirePledgeTag extends ArenaTag {
|
|
|
|
* and {@link https://bulbapedia.bulbagarden.net/wiki/Water_Pledge_(move) | Water Pledge}.
|
|
|
|
* and {@link https://bulbapedia.bulbagarden.net/wiki/Water_Pledge_(move) | Water Pledge}.
|
|
|
|
* Quarters the Speed of Pokemon on the given side of the field for 4 turns.
|
|
|
|
* Quarters the Speed of Pokemon on the given side of the field for 4 turns.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
class GrassWaterPledgeTag extends ArenaTag {
|
|
|
|
class GrassWaterPledgeTag extends SerializableArenaTag {
|
|
|
|
constructor(sourceId: number, side: ArenaTagSide) {
|
|
|
|
public readonly tagType = ArenaTagType.GRASS_WATER_PLEDGE;
|
|
|
|
super(ArenaTagType.GRASS_WATER_PLEDGE, 4, MoveId.GRASS_PLEDGE, sourceId, side);
|
|
|
|
constructor(sourceId: number | undefined, side: ArenaTagSide) {
|
|
|
|
|
|
|
|
super(4, MoveId.GRASS_PLEDGE, sourceId, side);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override onAdd(_arena: Arena): void {
|
|
|
|
override onAdd(_arena: Arena): void {
|
|
|
@ -1456,9 +1544,10 @@ class GrassWaterPledgeTag extends ArenaTag {
|
|
|
|
* If a Pokémon that's on the field when Fairy Lock is used goes on to faint later in the same turn,
|
|
|
|
* If a Pokémon that's on the field when Fairy Lock is used goes on to faint later in the same turn,
|
|
|
|
* the Pokémon that replaces it will still be unable to switch out in the following turn.
|
|
|
|
* the Pokémon that replaces it will still be unable to switch out in the following turn.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class FairyLockTag extends ArenaTag {
|
|
|
|
export class FairyLockTag extends SerializableArenaTag {
|
|
|
|
constructor(turnCount: number, sourceId: number) {
|
|
|
|
public readonly tagType = ArenaTagType.FAIRY_LOCK;
|
|
|
|
super(ArenaTagType.FAIRY_LOCK, turnCount, MoveId.FAIRY_LOCK, sourceId);
|
|
|
|
constructor(turnCount: number, sourceId?: number) {
|
|
|
|
|
|
|
|
super(turnCount, MoveId.FAIRY_LOCK, sourceId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
|
onAdd(_arena: Arena): void {
|
|
|
@ -1472,15 +1561,29 @@ export class FairyLockTag extends ArenaTag {
|
|
|
|
* Keeps track of the number of pokemon on the field with Neutralizing Gas - If it drops to zero, the effect is ended and abilities are reactivated
|
|
|
|
* Keeps track of the number of pokemon on the field with Neutralizing Gas - If it drops to zero, the effect is ended and abilities are reactivated
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Additionally ends onLose abilities when it is activated
|
|
|
|
* Additionally ends onLose abilities when it is activated
|
|
|
|
|
|
|
|
* @sealed
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export class SuppressAbilitiesTag extends ArenaTag {
|
|
|
|
export class SuppressAbilitiesTag extends SerializableArenaTag {
|
|
|
|
private sourceCount: number;
|
|
|
|
// Source count is allowed to be inwardly mutable, but outwardly immutable
|
|
|
|
private beingRemoved: boolean;
|
|
|
|
public readonly sourceCount: number;
|
|
|
|
|
|
|
|
public readonly tagType = ArenaTagType.NEUTRALIZING_GAS;
|
|
|
|
|
|
|
|
// Private field prevents field from appearing during serialization
|
|
|
|
|
|
|
|
/** Whether the tag is in the process of being removed */
|
|
|
|
|
|
|
|
#beingRemoved: boolean;
|
|
|
|
|
|
|
|
/** Whether the tag is in the process of being removed */
|
|
|
|
|
|
|
|
public get beingRemoved(): boolean {
|
|
|
|
|
|
|
|
return this.#beingRemoved;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
constructor(sourceId: number) {
|
|
|
|
constructor(sourceId?: number) {
|
|
|
|
super(ArenaTagType.NEUTRALIZING_GAS, 0, undefined, sourceId);
|
|
|
|
super(0, undefined, sourceId);
|
|
|
|
this.sourceCount = 1;
|
|
|
|
this.sourceCount = 1;
|
|
|
|
this.beingRemoved = false;
|
|
|
|
this.#beingRemoved = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override loadTag(source: NonFunctionProperties<SuppressAbilitiesTag>): void {
|
|
|
|
|
|
|
|
super.loadTag(source);
|
|
|
|
|
|
|
|
(this as Mutable<this>).sourceCount = source.sourceCount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override onAdd(_arena: Arena): void {
|
|
|
|
public override onAdd(_arena: Arena): void {
|
|
|
@ -1492,19 +1595,21 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|
|
|
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
|
|
|
|
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
|
|
|
|
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
|
|
|
|
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
|
|
|
|
// the appropriate attributes (preLEaveField and IllusionBreak)
|
|
|
|
// the appropriate attributes (preLEaveField and IllusionBreak)
|
|
|
|
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: fieldPokemon, passive }));
|
|
|
|
[true, false].forEach(passive => {
|
|
|
|
|
|
|
|
applyOnLoseAbAttrs({ pokemon: fieldPokemon, passive });
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override onOverlap(_arena: Arena, source: Pokemon | null): void {
|
|
|
|
public override onOverlap(_arena: Arena, source: Pokemon | null): void {
|
|
|
|
this.sourceCount++;
|
|
|
|
(this as Mutable<this>).sourceCount++;
|
|
|
|
this.playActivationMessage(source);
|
|
|
|
this.playActivationMessage(source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public onSourceLeave(arena: Arena): void {
|
|
|
|
public onSourceLeave(arena: Arena): void {
|
|
|
|
this.sourceCount--;
|
|
|
|
(this as Mutable<this>).sourceCount--;
|
|
|
|
if (this.sourceCount <= 0) {
|
|
|
|
if (this.sourceCount <= 0) {
|
|
|
|
arena.removeTag(ArenaTagType.NEUTRALIZING_GAS);
|
|
|
|
arena.removeTag(ArenaTagType.NEUTRALIZING_GAS);
|
|
|
|
} else if (this.sourceCount === 1) {
|
|
|
|
} else if (this.sourceCount === 1) {
|
|
|
@ -1522,7 +1627,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override onRemove(_arena: Arena, quiet = false) {
|
|
|
|
public override onRemove(_arena: Arena, quiet = false) {
|
|
|
|
this.beingRemoved = true;
|
|
|
|
this.#beingRemoved = true;
|
|
|
|
if (!quiet) {
|
|
|
|
if (!quiet) {
|
|
|
|
globalScene.phaseManager.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove"));
|
|
|
|
globalScene.phaseManager.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove"));
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1530,7 +1635,9 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|
|
|
for (const pokemon of globalScene.getField(true)) {
|
|
|
|
for (const pokemon of globalScene.getField(true)) {
|
|
|
|
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
|
|
|
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
|
|
|
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
|
|
|
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
|
|
|
[true, false].forEach(passive => applyOnGainAbAttrs({ pokemon, passive }));
|
|
|
|
[true, false].forEach(passive => {
|
|
|
|
|
|
|
|
applyOnGainAbAttrs({ pokemon, passive });
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1539,10 +1646,6 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|
|
|
return this.sourceCount > 1;
|
|
|
|
return this.sourceCount > 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public isBeingRemoved() {
|
|
|
|
|
|
|
|
return this.beingRemoved;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private playActivationMessage(pokemon: Pokemon | null) {
|
|
|
|
private playActivationMessage(pokemon: Pokemon | null) {
|
|
|
|
if (pokemon) {
|
|
|
|
if (pokemon) {
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
|
@ -1559,7 +1662,7 @@ export function getArenaTag(
|
|
|
|
tagType: ArenaTagType,
|
|
|
|
tagType: ArenaTagType,
|
|
|
|
turnCount: number,
|
|
|
|
turnCount: number,
|
|
|
|
sourceMove: MoveId | undefined,
|
|
|
|
sourceMove: MoveId | undefined,
|
|
|
|
sourceId: number,
|
|
|
|
sourceId: number | undefined,
|
|
|
|
targetIndex?: BattlerIndex,
|
|
|
|
targetIndex?: BattlerIndex,
|
|
|
|
side: ArenaTagSide = ArenaTagSide.BOTH,
|
|
|
|
side: ArenaTagSide = ArenaTagSide.BOTH,
|
|
|
|
): ArenaTag | null {
|
|
|
|
): ArenaTag | null {
|
|
|
@ -1575,7 +1678,7 @@ export function getArenaTag(
|
|
|
|
case ArenaTagType.CRAFTY_SHIELD:
|
|
|
|
case ArenaTagType.CRAFTY_SHIELD:
|
|
|
|
return new CraftyShieldTag(sourceId, side);
|
|
|
|
return new CraftyShieldTag(sourceId, side);
|
|
|
|
case ArenaTagType.NO_CRIT:
|
|
|
|
case ArenaTagType.NO_CRIT:
|
|
|
|
return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct?
|
|
|
|
return new NoCritTag(turnCount, sourceMove, sourceId, side);
|
|
|
|
case ArenaTagType.MUD_SPORT:
|
|
|
|
case ArenaTagType.MUD_SPORT:
|
|
|
|
return new MudSportTag(turnCount, sourceId);
|
|
|
|
return new MudSportTag(turnCount, sourceId);
|
|
|
|
case ArenaTagType.WATER_SPORT:
|
|
|
|
case ArenaTagType.WATER_SPORT:
|
|
|
@ -1588,7 +1691,10 @@ export function getArenaTag(
|
|
|
|
return new ToxicSpikesTag(sourceId, side);
|
|
|
|
return new ToxicSpikesTag(sourceId, side);
|
|
|
|
case ArenaTagType.FUTURE_SIGHT:
|
|
|
|
case ArenaTagType.FUTURE_SIGHT:
|
|
|
|
case ArenaTagType.DOOM_DESIRE:
|
|
|
|
case ArenaTagType.DOOM_DESIRE:
|
|
|
|
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!, side); // TODO:questionable bang
|
|
|
|
if (!targetIndex) {
|
|
|
|
|
|
|
|
return null; // If missing target index, no tag is created
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex, side);
|
|
|
|
case ArenaTagType.WISH:
|
|
|
|
case ArenaTagType.WISH:
|
|
|
|
return new WishTag(turnCount, sourceId, side);
|
|
|
|
return new WishTag(turnCount, sourceId, side);
|
|
|
|
case ArenaTagType.STEALTH_ROCK:
|
|
|
|
case ArenaTagType.STEALTH_ROCK:
|
|
|
@ -1598,7 +1704,7 @@ export function getArenaTag(
|
|
|
|
case ArenaTagType.TRICK_ROOM:
|
|
|
|
case ArenaTagType.TRICK_ROOM:
|
|
|
|
return new TrickRoomTag(turnCount, sourceId);
|
|
|
|
return new TrickRoomTag(turnCount, sourceId);
|
|
|
|
case ArenaTagType.GRAVITY:
|
|
|
|
case ArenaTagType.GRAVITY:
|
|
|
|
return new GravityTag(turnCount);
|
|
|
|
return new GravityTag(turnCount, sourceId);
|
|
|
|
case ArenaTagType.REFLECT:
|
|
|
|
case ArenaTagType.REFLECT:
|
|
|
|
return new ReflectTag(turnCount, sourceId, side);
|
|
|
|
return new ReflectTag(turnCount, sourceId, side);
|
|
|
|
case ArenaTagType.LIGHT_SCREEN:
|
|
|
|
case ArenaTagType.LIGHT_SCREEN:
|
|
|
@ -1630,10 +1736,10 @@ export function getArenaTag(
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* When given a battler tag or json representing one, creates an actual ArenaTag object with the same data.
|
|
|
|
* When given a battler tag or json representing one, creates an actual ArenaTag object with the same data.
|
|
|
|
* @param {ArenaTag | any} source An arena tag
|
|
|
|
* @param source - An arena tag
|
|
|
|
* @return {ArenaTag} The valid arena tag
|
|
|
|
* @returns The valid arena tag
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function loadArenaTag(source: ArenaTag | any): ArenaTag {
|
|
|
|
export function loadArenaTag(source: (ArenaTag | ArenaTagTypeData) & { targetIndex?: BattlerIndex }): ArenaTag {
|
|
|
|
const tag =
|
|
|
|
const tag =
|
|
|
|
getArenaTag(
|
|
|
|
getArenaTag(
|
|
|
|
source.tagType,
|
|
|
|
source.tagType,
|
|
|
@ -1646,3 +1752,37 @@ export function loadArenaTag(source: ArenaTag | any): ArenaTag {
|
|
|
|
tag.loadTag(source);
|
|
|
|
tag.loadTag(source);
|
|
|
|
return tag;
|
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export type ArenaTagTypeMap = {
|
|
|
|
|
|
|
|
[ArenaTagType.MUD_SPORT]: MudSportTag;
|
|
|
|
|
|
|
|
[ArenaTagType.WATER_SPORT]: WaterSportTag;
|
|
|
|
|
|
|
|
[ArenaTagType.ION_DELUGE]: IonDelugeTag;
|
|
|
|
|
|
|
|
[ArenaTagType.SPIKES]: SpikesTag;
|
|
|
|
|
|
|
|
[ArenaTagType.MIST]: MistTag;
|
|
|
|
|
|
|
|
[ArenaTagType.QUICK_GUARD]: QuickGuardTag;
|
|
|
|
|
|
|
|
[ArenaTagType.WIDE_GUARD]: WideGuardTag;
|
|
|
|
|
|
|
|
[ArenaTagType.MAT_BLOCK]: MatBlockTag;
|
|
|
|
|
|
|
|
[ArenaTagType.CRAFTY_SHIELD]: CraftyShieldTag;
|
|
|
|
|
|
|
|
[ArenaTagType.NO_CRIT]: NoCritTag;
|
|
|
|
|
|
|
|
[ArenaTagType.TOXIC_SPIKES]: ToxicSpikesTag;
|
|
|
|
|
|
|
|
[ArenaTagType.FUTURE_SIGHT]: DelayedAttackTag;
|
|
|
|
|
|
|
|
[ArenaTagType.DOOM_DESIRE]: DelayedAttackTag;
|
|
|
|
|
|
|
|
[ArenaTagType.WISH]: WishTag;
|
|
|
|
|
|
|
|
[ArenaTagType.STEALTH_ROCK]: StealthRockTag;
|
|
|
|
|
|
|
|
[ArenaTagType.STICKY_WEB]: StickyWebTag;
|
|
|
|
|
|
|
|
[ArenaTagType.TRICK_ROOM]: TrickRoomTag;
|
|
|
|
|
|
|
|
[ArenaTagType.GRAVITY]: GravityTag;
|
|
|
|
|
|
|
|
[ArenaTagType.REFLECT]: ReflectTag;
|
|
|
|
|
|
|
|
[ArenaTagType.LIGHT_SCREEN]: LightScreenTag;
|
|
|
|
|
|
|
|
[ArenaTagType.AURORA_VEIL]: AuroraVeilTag;
|
|
|
|
|
|
|
|
[ArenaTagType.TAILWIND]: TailwindTag;
|
|
|
|
|
|
|
|
[ArenaTagType.HAPPY_HOUR]: HappyHourTag;
|
|
|
|
|
|
|
|
[ArenaTagType.SAFEGUARD]: SafeguardTag;
|
|
|
|
|
|
|
|
[ArenaTagType.IMPRISON]: ImprisonTag;
|
|
|
|
|
|
|
|
[ArenaTagType.FIRE_GRASS_PLEDGE]: FireGrassPledgeTag;
|
|
|
|
|
|
|
|
[ArenaTagType.WATER_FIRE_PLEDGE]: WaterFirePledgeTag;
|
|
|
|
|
|
|
|
[ArenaTagType.GRASS_WATER_PLEDGE]: GrassWaterPledgeTag;
|
|
|
|
|
|
|
|
[ArenaTagType.FAIRY_LOCK]: FairyLockTag;
|
|
|
|
|
|
|
|
[ArenaTagType.NEUTRALIZING_GAS]: SuppressAbilitiesTag;
|
|
|
|
|
|
|
|
[ArenaTagType.NONE]: NoneTag;
|
|
|
|
|
|
|
|
};
|
|
|
|