mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-10 17:39:31 +02:00
Merge branch 'beta' into future-sight
This commit is contained in:
commit
0def2279a4
48
src/@types/arena-tags.ts
Normal file
48
src/@types/arena-tags.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import type { ArenaTagTypeMap } from "#data/arena-tag";
|
||||||
|
import type { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
import type { NonFunctionProperties } from "./type-helpers";
|
||||||
|
|
||||||
|
/** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */
|
||||||
|
export type ArenaTrapTagType =
|
||||||
|
| ArenaTagType.STICKY_WEB
|
||||||
|
| ArenaTagType.SPIKES
|
||||||
|
| ArenaTagType.TOXIC_SPIKES
|
||||||
|
| ArenaTagType.STEALTH_ROCK
|
||||||
|
| ArenaTagType.IMPRISON;
|
||||||
|
|
||||||
|
/** Subset of {@linkcode ArenaTagType}s that are considered delayed attacks */
|
||||||
|
export type ArenaDelayedAttackTagType = ArenaTagType.FUTURE_SIGHT | ArenaTagType.DOOM_DESIRE;
|
||||||
|
|
||||||
|
/** Subset of {@linkcode ArenaTagType}s that create {@link https://bulbapedia.bulbagarden.net/wiki/Category:Screen-creating_moves | screens}. */
|
||||||
|
export type ArenaScreenTagType = ArenaTagType.REFLECT | ArenaTagType.LIGHT_SCREEN | ArenaTagType.AURORA_VEIL;
|
||||||
|
|
||||||
|
/** Subset of {@linkcode ArenaTagType}s for moves that add protection */
|
||||||
|
export type TurnProtectArenaTagType =
|
||||||
|
| ArenaTagType.QUICK_GUARD
|
||||||
|
| ArenaTagType.WIDE_GUARD
|
||||||
|
| ArenaTagType.MAT_BLOCK
|
||||||
|
| ArenaTagType.CRAFTY_SHIELD;
|
||||||
|
|
||||||
|
/** Subset of {@linkcode ArenaTagType}s that cannot persist across turns, and thus should not be serialized in {@linkcode SessionSaveData}. */
|
||||||
|
export type NonSerializableArenaTagType = ArenaTagType.NONE | TurnProtectArenaTagType | ArenaTagType.ION_DELUGE;
|
||||||
|
|
||||||
|
/** Subset of {@linkcode ArenaTagType}s that may persist across turns, and thus must be serialized in {@linkcode SessionSaveData}. */
|
||||||
|
export type SerializableArenaTagType = Exclude<ArenaTagType, NonSerializableArenaTagType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe representation of the serializable data of an ArenaTag
|
||||||
|
*/
|
||||||
|
export type ArenaTagTypeData = NonFunctionProperties<
|
||||||
|
ArenaTagTypeMap[keyof {
|
||||||
|
[K in keyof ArenaTagTypeMap as K extends SerializableArenaTagType ? K : never]: ArenaTagTypeMap[K];
|
||||||
|
}]
|
||||||
|
>;
|
||||||
|
|
||||||
|
/** Dummy, typescript-only declaration to ensure that
|
||||||
|
* {@linkcode ArenaTagTypeMap} has a map for all ArenaTagTypes.
|
||||||
|
*
|
||||||
|
* If an arena tag is missing from the map, typescript will throw an error on this statement.
|
||||||
|
*
|
||||||
|
* ⚠️ Does not actually exist at runtime, so it must not be used!
|
||||||
|
*/
|
||||||
|
declare const EnsureAllArenaTagTypesAreMapped: ArenaTagTypeMap[ArenaTagType] & never;
|
@ -44,3 +44,34 @@ export type Mutable<T> = {
|
|||||||
export type InferKeys<O extends Record<keyof any, unknown>, V extends EnumValues<O>> = {
|
export type InferKeys<O extends Record<keyof any, unknown>, V extends EnumValues<O>> = {
|
||||||
[K in keyof O]: O[K] extends V ? K : never;
|
[K in keyof O]: O[K] extends V ? K : never;
|
||||||
}[keyof O];
|
}[keyof O];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type helper that matches any `Function` type. Equivalent to `Function`, but will not raise a warning from Biome.
|
||||||
|
*/
|
||||||
|
export type AnyFn = (...args: any[]) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type helper to extract non-function properties from a type.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Useful to produce a type that is roughly the same as the type of `{... obj}`, where `obj` is an instance of `T`.
|
||||||
|
* A couple of differences:
|
||||||
|
* - Private and protected properties are not included.
|
||||||
|
* - Nested properties are not recursively extracted. For this, use {@linkcode NonFunctionPropertiesRecursive}
|
||||||
|
*/
|
||||||
|
export type NonFunctionProperties<T> = {
|
||||||
|
[K in keyof T as T[K] extends AnyFn ? never : K]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type helper to extract out non-function properties from a type, recursively applying to nested properties.
|
||||||
|
*/
|
||||||
|
export type NonFunctionPropertiesRecursive<Class> = {
|
||||||
|
[K in keyof Class as Class[K] extends AnyFn ? never : K]: Class[K] extends Array<infer U>
|
||||||
|
? NonFunctionPropertiesRecursive<U>[]
|
||||||
|
: Class[K] extends object
|
||||||
|
? NonFunctionPropertiesRecursive<Class[K]>
|
||||||
|
: Class[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AbstractConstructor<T> = abstract new (...args: any[]) => T;
|
||||||
|
@ -50,7 +50,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
|||||||
import { BiomeId } from "#enums/biome-id";
|
import { BiomeId } from "#enums/biome-id";
|
||||||
import { EaseType } from "#enums/ease-type";
|
import { EaseType } from "#enums/ease-type";
|
||||||
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
|
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
|
||||||
import type { ExpNotification } from "#enums/exp-notification";
|
import { ExpNotification } from "#enums/exp-notification";
|
||||||
import { FormChangeItem } from "#enums/form-change-item";
|
import { FormChangeItem } from "#enums/form-change-item";
|
||||||
import { GameModes } from "#enums/game-modes";
|
import { GameModes } from "#enums/game-modes";
|
||||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
@ -197,6 +197,7 @@ export class BattleScene extends SceneBase {
|
|||||||
public enableMoveInfo = true;
|
public enableMoveInfo = true;
|
||||||
public enableRetries = false;
|
public enableRetries = false;
|
||||||
public hideIvs = false;
|
public hideIvs = false;
|
||||||
|
// TODO: Remove all plain numbers in place of enums or `const object` equivalents for clarity
|
||||||
/**
|
/**
|
||||||
* Determines the condition for a notification should be shown for Candy Upgrades
|
* Determines the condition for a notification should be shown for Candy Upgrades
|
||||||
* - 0 = 'Off'
|
* - 0 = 'Off'
|
||||||
@ -214,7 +215,7 @@ export class BattleScene extends SceneBase {
|
|||||||
public uiTheme: UiTheme = UiTheme.DEFAULT;
|
public uiTheme: UiTheme = UiTheme.DEFAULT;
|
||||||
public windowType = 0;
|
public windowType = 0;
|
||||||
public experimentalSprites = false;
|
public experimentalSprites = false;
|
||||||
public musicPreference: number = MusicPreference.ALLGENS;
|
public musicPreference: MusicPreference = MusicPreference.ALLGENS;
|
||||||
public moveAnimations = true;
|
public moveAnimations = true;
|
||||||
public expGainsSpeed: ExpGainsSpeed = ExpGainsSpeed.DEFAULT;
|
public expGainsSpeed: ExpGainsSpeed = ExpGainsSpeed.DEFAULT;
|
||||||
public skipSeenDialogues = false;
|
public skipSeenDialogues = false;
|
||||||
@ -225,33 +226,18 @@ export class BattleScene extends SceneBase {
|
|||||||
* - 2 = Always (automatically skip animation when hatching 2 or more eggs)
|
* - 2 = Always (automatically skip animation when hatching 2 or more eggs)
|
||||||
*/
|
*/
|
||||||
public eggSkipPreference = 0;
|
public eggSkipPreference = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the experience gain display mode.
|
* Defines the {@linkcode ExpNotification | Experience gain display mode}.
|
||||||
*
|
* @defaultValue {@linkcode ExpNotification.DEFAULT}
|
||||||
* @remarks
|
|
||||||
* The `expParty` can have several modes:
|
|
||||||
* - `0` - Default: The normal experience gain display, nothing changed.
|
|
||||||
* - `1` - Level Up Notification: Displays the level up in the small frame instead of a message.
|
|
||||||
* - `2` - Skip: No level up frame nor message.
|
|
||||||
*
|
|
||||||
* Modes `1` and `2` are still compatible with stats display, level up, new move, etc.
|
|
||||||
* @default 0 - Uses the default normal experience gain display.
|
|
||||||
*/
|
*/
|
||||||
public expParty: ExpNotification = 0;
|
public expParty: ExpNotification = ExpNotification.DEFAULT;
|
||||||
public hpBarSpeed = 0;
|
public hpBarSpeed = 0;
|
||||||
public fusionPaletteSwaps = true;
|
public fusionPaletteSwaps = true;
|
||||||
public enableTouchControls = false;
|
public enableTouchControls = false;
|
||||||
public enableVibration = false;
|
public enableVibration = false;
|
||||||
public showBgmBar = true;
|
public showBgmBar = true;
|
||||||
|
/** Determines the selected battle style. */
|
||||||
/**
|
public battleStyle: BattleStyle = BattleStyle.SWITCH;
|
||||||
* Determines the selected battle style.
|
|
||||||
* - 0 = 'Switch'
|
|
||||||
* - 1 = 'Set' - The option to switch the active pokemon at the start of a battle will not display.
|
|
||||||
*/
|
|
||||||
public battleStyle: number = BattleStyle.SWITCH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines whether or not to show type effectiveness hints
|
* Defines whether or not to show type effectiveness hints
|
||||||
* - true: No hints
|
* - true: No hints
|
||||||
@ -829,7 +815,8 @@ export class BattleScene extends SceneBase {
|
|||||||
/**
|
/**
|
||||||
* Returns an array of Pokemon on both sides of the battle - player first, then enemy.
|
* Returns an array of Pokemon on both sides of the battle - player first, then enemy.
|
||||||
* Does not actually check if the pokemon are on the field or not, and always has length 4 regardless of battle type.
|
* Does not actually check if the pokemon are on the field or not, and always has length 4 regardless of battle type.
|
||||||
* @param activeOnly - Whether to consider only active pokemon; default `false`
|
* @param activeOnly - Whether to consider only active pokemon (as described by {@linkcode Pokemon.isActive()}); default `false`.
|
||||||
|
* If `true`, will also remove all `null` values from the array.
|
||||||
* @returns An array of {@linkcode Pokemon}, as described above.
|
* @returns An array of {@linkcode Pokemon}, as described above.
|
||||||
*/
|
*/
|
||||||
public getField(activeOnly = false): Pokemon[] {
|
public getField(activeOnly = false): Pokemon[] {
|
||||||
@ -842,9 +829,9 @@ export class BattleScene extends SceneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in doubles battles to redirect moves from one pokemon to another when one faints or is removed from the field
|
* Attempt to redirect a move in double battles from a fainted/removed Pokemon to its ally.
|
||||||
* @param removedPokemon {@linkcode Pokemon} the pokemon that is being removed from the field (flee, faint), moves to be redirected FROM
|
* @param removedPokemon - The {@linkcode Pokemon} having been removed from the field.
|
||||||
* @param allyPokemon {@linkcode Pokemon} the pokemon that will have the moves be redirected TO
|
* @param allyPokemon - The {@linkcode Pokemon} allied with the removed Pokemon; will have moves redirected to it
|
||||||
*/
|
*/
|
||||||
redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void {
|
redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void {
|
||||||
// failsafe: if not a double battle just return
|
// failsafe: if not a double battle just return
|
||||||
@ -870,10 +857,10 @@ export class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere
|
* Returns the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere
|
||||||
* @param isEnemy Whether to return the enemy's modifier bar
|
* @param isEnemy - Whether to return the enemy modifier bar instead of the player bar; default `false`
|
||||||
* @returns {ModifierBar}
|
* @returns The {@linkcode ModifierBar} for the given side of the field
|
||||||
*/
|
*/
|
||||||
getModifierBar(isEnemy?: boolean): ModifierBar {
|
getModifierBar(isEnemy = false): ModifierBar {
|
||||||
return isEnemy ? this.enemyModifierBar : this.modifierBar;
|
return isEnemy ? this.enemyModifierBar : this.modifierBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1475,10 +1462,12 @@ export class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
if (!waveIndex && lastBattle) {
|
if (!waveIndex && lastBattle) {
|
||||||
const isNewBiome = this.isNewBiome(lastBattle);
|
const isNewBiome = this.isNewBiome(lastBattle);
|
||||||
|
/** Whether to reset and recall pokemon */
|
||||||
const resetArenaState =
|
const resetArenaState =
|
||||||
isNewBiome ||
|
isNewBiome ||
|
||||||
[BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) ||
|
[BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) ||
|
||||||
this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
|
this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
|
||||||
|
|
||||||
for (const enemyPokemon of this.getEnemyParty()) {
|
for (const enemyPokemon of this.getEnemyParty()) {
|
||||||
enemyPokemon.destroy();
|
enemyPokemon.destroy();
|
||||||
}
|
}
|
||||||
@ -1853,7 +1842,7 @@ export class BattleScene extends SceneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resetSeed(waveIndex?: number): void {
|
resetSeed(waveIndex?: number): void {
|
||||||
const wave = waveIndex || this.currentBattle?.waveIndex || 0;
|
const wave = waveIndex ?? this.currentBattle?.waveIndex ?? 0;
|
||||||
this.waveSeed = shiftCharCodes(this.seed, wave);
|
this.waveSeed = shiftCharCodes(this.seed, wave);
|
||||||
Phaser.Math.RND.sow([this.waveSeed]);
|
Phaser.Math.RND.sow([this.waveSeed]);
|
||||||
console.log("Wave Seed:", this.waveSeed, wave);
|
console.log("Wave Seed:", this.waveSeed, wave);
|
||||||
|
@ -145,76 +145,141 @@ export class Ability implements Localizable {
|
|||||||
return this.attrs.some(attr => attr instanceof targetAttr);
|
return this.attrs.some(attr => attr instanceof targetAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
/**
|
||||||
|
* Create a new {@linkcode AbAttr} instance and add it to this {@linkcode Ability}.
|
||||||
|
* @param attrType - The constructor of the {@linkcode AbAttr} to create.
|
||||||
|
* @param args - The arguments needed to instantiate the given class.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
|
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): this {
|
||||||
const attr = new AttrType(...args);
|
const attr = new AttrType(...args);
|
||||||
this.attrs.push(attr);
|
this.attrs.push(attr);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@linkcode AbAttr} instance with the given condition and add it to this {@linkcode Ability}.
|
||||||
|
* Checked before all other conditions, and is unique to the individual {@linkcode AbAttr} being created.
|
||||||
|
* @param condition - The {@linkcode AbAttrCondition} to add.
|
||||||
|
* @param attrType - The constructor of the {@linkcode AbAttr} to create.
|
||||||
|
* @param args - The arguments needed to instantiate the given class.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
conditionalAttr<T extends Constructor<AbAttr>>(
|
conditionalAttr<T extends Constructor<AbAttr>>(
|
||||||
condition: AbAttrCondition,
|
condition: AbAttrCondition,
|
||||||
AttrType: T,
|
attrType: T,
|
||||||
...args: ConstructorParameters<T>
|
...args: ConstructorParameters<T>
|
||||||
): Ability {
|
): this {
|
||||||
const attr = new AttrType(...args);
|
const attr = new attrType(...args);
|
||||||
attr.addCondition(condition);
|
attr.addCondition(condition);
|
||||||
this.attrs.push(attr);
|
this.attrs.push(attr);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bypassFaint(): Ability {
|
/**
|
||||||
|
* Make this ability trigger even if the user faints.
|
||||||
|
* @returns `this`
|
||||||
|
* @remarks
|
||||||
|
* This is also required for abilities to trigger when revived via Reviver Seed.
|
||||||
|
*/
|
||||||
|
bypassFaint(): this {
|
||||||
this.isBypassFaint = true;
|
this.isBypassFaint = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ignorable(): Ability {
|
/**
|
||||||
|
* Make this ability ignorable by effects like {@linkcode MoveId.SUNSTEEL_STRIKE | Sunsteel Strike} or {@linkcode AbilityId.MOLD_BREAKER | Mold Breaker}.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
|
ignorable(): this {
|
||||||
this.isIgnorable = true;
|
this.isIgnorable = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsuppressable(): Ability {
|
/**
|
||||||
|
* Make this ability unsuppressable by effects like {@linkcode MoveId.GASTRO_ACID | Gastro Acid} or {@linkcode AbilityId.NEUTRALIZING_GAS | Neutralizing Gas}.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
|
unsuppressable(): this {
|
||||||
this.isSuppressable = false;
|
this.isSuppressable = false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
uncopiable(): Ability {
|
/**
|
||||||
|
* Make this ability uncopiable by effects like {@linkcode MoveId.ROLE_PLAY | Role Play} or {@linkcode AbilityId.TRACE | Trace}.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
|
uncopiable(): this {
|
||||||
this.isCopiable = false;
|
this.isCopiable = false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
unreplaceable(): Ability {
|
/**
|
||||||
|
* Make this ability unreplaceable by effects like {@linkcode MoveId.SIMPLE_BEAM | Simple Beam} or {@linkcode MoveId.ENTRAINMENT | Entrainment}.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
|
unreplaceable(): this {
|
||||||
this.isReplaceable = false;
|
this.isReplaceable = false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
condition(condition: AbAttrCondition): Ability {
|
/**
|
||||||
|
* Add a condition for this ability to be applied.
|
||||||
|
* Applies to **all** attributes of the given ability.
|
||||||
|
* @param condition - The {@linkcode AbAttrCondition} to add
|
||||||
|
* @returns `this`
|
||||||
|
* @see {@linkcode AbAttr.canApply} for setting conditions per attribute type
|
||||||
|
* @see {@linkcode conditionalAttr} for setting individual conditions per attribute instance
|
||||||
|
* @todo Review if this is necessary anymore - this is used extremely sparingly
|
||||||
|
*/
|
||||||
|
condition(condition: AbAttrCondition): this {
|
||||||
this.conditions.push(condition);
|
this.conditions.push(condition);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an ability as partially implemented.
|
||||||
|
* Partial abilities are expected to have some of their core functionality implemented, but may lack
|
||||||
|
* certain notable features or interactions with other moves or abilities.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
partial(): this {
|
partial(): this {
|
||||||
this.nameAppend += " (P)";
|
this.nameAppend += " (P)";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an ability as unimplemented.
|
||||||
|
* Unimplemented abilities are ones which have _none_ of their basic functionality enabled.
|
||||||
|
* @returns `this`
|
||||||
|
*/
|
||||||
unimplemented(): this {
|
unimplemented(): this {
|
||||||
this.nameAppend += " (N)";
|
this.nameAppend += " (N)";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
|
* Mark an ability as having one or more edge cases.
|
||||||
* @returns the ability
|
* It may lack certain niche interactions with other moves/abilities, but still functions
|
||||||
|
* as intended in most cases.
|
||||||
|
* Does not show up in game and is solely for internal dev use.
|
||||||
|
*
|
||||||
|
* When using this, make sure to **document the edge case** (or else this becomes pointless).
|
||||||
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
edgeCase(): this {
|
edgeCase(): this {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Base set of parameters passed to every ability attribute's apply method */
|
/**
|
||||||
|
* Base set of parameters passed to every ability attribute's {@linkcode AbAttr.apply | apply} method.
|
||||||
|
*
|
||||||
|
* Extended by sub-classes to contain additional parameters pertaining to the ability type(s) being triggered.
|
||||||
|
*/
|
||||||
export interface AbAttrBaseParams {
|
export interface AbAttrBaseParams {
|
||||||
/** The pokemon that has the ability being applied */
|
/** The pokemon that has the ability being applied */
|
||||||
readonly pokemon: Pokemon;
|
readonly pokemon: Pokemon;
|
||||||
@ -245,9 +310,20 @@ export interface AbAttrParamsWithCancel extends AbAttrBaseParams {
|
|||||||
readonly cancelled: BooleanHolder;
|
readonly cancelled: BooleanHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for all ability attributes.
|
||||||
|
*
|
||||||
|
* Each {@linkcode Ability} may have any number of individual attributes, each functioning independently from one another.
|
||||||
|
*/
|
||||||
export abstract class AbAttr {
|
export abstract class AbAttr {
|
||||||
public showAbility: boolean;
|
/**
|
||||||
private extraCondition: AbAttrCondition;
|
* Whether to show this ability as a flyout when applying its effects.
|
||||||
|
* Should be kept in parity with mainline where possible.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
public showAbility = true;
|
||||||
|
/** The additional condition associated with this AbAttr, if any. */
|
||||||
|
private extraCondition?: AbAttrCondition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether this attribute is of the given type.
|
* Return whether this attribute is of the given type.
|
||||||
@ -275,21 +351,43 @@ export abstract class AbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply ability effects without checking conditions.
|
* Apply this attribute's effects without checking conditions.
|
||||||
* **Never call this method directly, use {@linkcode applyAbAttrs} instead.**
|
*
|
||||||
|
* @remarks
|
||||||
|
* **Never call this method directly!** \
|
||||||
|
* Use {@linkcode applyAbAttrs} instead.
|
||||||
*/
|
*/
|
||||||
apply(_params: AbAttrBaseParams): void {}
|
apply(_params: AbAttrBaseParams): void {}
|
||||||
|
|
||||||
// The `Exact` in the next two signatures enforces that the type of the _params operand
|
/**
|
||||||
// is always compatible with the type of apply. This allows fewer fields, but never a type with more.
|
* Return the trigger message to show when this attribute is executed.
|
||||||
|
* @param _params - The parameters passed to this attribute's {@linkcode apply} function; must match type exactly
|
||||||
|
* @param _abilityName - The name of the current ability.
|
||||||
|
* @privateRemarks
|
||||||
|
* If more fields are provided than needed, any excess can be discarded using destructuring.
|
||||||
|
* @todo Remove `null` from signature in lieu of using an empty string
|
||||||
|
*/
|
||||||
getTriggerMessage(_params: Exact<Parameters<this["apply"]>[0]>, _abilityName: string): string | null {
|
getTriggerMessage(_params: Exact<Parameters<this["apply"]>[0]>, _abilityName: string): string | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this attribute can have its effects successfully applied.
|
||||||
|
* Applies to **all** instances of the given attribute.
|
||||||
|
* @param _params - The parameters passed to this attribute's {@linkcode apply} function; must match type exactly
|
||||||
|
* @privateRemarks
|
||||||
|
* If more fields are provided than needed, any excess can be discarded using destructuring.
|
||||||
|
*/
|
||||||
canApply(_params: Exact<Parameters<this["apply"]>[0]>): boolean {
|
canApply(_params: Exact<Parameters<this["apply"]>[0]>): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the additional condition associated with this particular AbAttr instance, if any.
|
||||||
|
* @returns The extra condition for this {@linkcode AbAttr}, or `null` if none exist
|
||||||
|
* @todo Make this use `undefined` instead of `null`
|
||||||
|
* @todo Prevent this from being overridden by sub-classes
|
||||||
|
*/
|
||||||
getCondition(): AbAttrCondition | null {
|
getCondition(): AbAttrCondition | null {
|
||||||
return this.extraCondition || null;
|
return this.extraCondition || null;
|
||||||
}
|
}
|
||||||
@ -593,7 +691,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
|
|||||||
private immuneType: PokemonType | null;
|
private immuneType: PokemonType | null;
|
||||||
private condition: AbAttrCondition | null;
|
private condition: AbAttrCondition | null;
|
||||||
|
|
||||||
// TODO: `immuneType` shouldn't be able to be `null`
|
// TODO: Change `NonSuperEffectiveImmunityAbAttr` to not pass `null` as immune type
|
||||||
constructor(immuneType: PokemonType | null, condition?: AbAttrCondition) {
|
constructor(immuneType: PokemonType | null, condition?: AbAttrCondition) {
|
||||||
super(true);
|
super(true);
|
||||||
|
|
||||||
@ -1526,6 +1624,11 @@ export interface FieldMultiplyStatAbAttrParams extends AbAttrBaseParams {
|
|||||||
export class FieldMultiplyStatAbAttr extends AbAttr {
|
export class FieldMultiplyStatAbAttr extends AbAttr {
|
||||||
private stat: Stat;
|
private stat: Stat;
|
||||||
private multiplier: number;
|
private multiplier: number;
|
||||||
|
/**
|
||||||
|
* Whether this ability can stack with others of the same type for this stat.
|
||||||
|
* @defaultValue `false`
|
||||||
|
* @todo Remove due to being literally useless - the ruin abilities are hardcoded to never stack in game
|
||||||
|
*/
|
||||||
private canStack: boolean;
|
private canStack: boolean;
|
||||||
|
|
||||||
constructor(stat: Stat, multiplier: number, canStack = false) {
|
constructor(stat: Stat, multiplier: number, canStack = false) {
|
||||||
@ -1546,7 +1649,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* applyFieldStat: Tries to multiply a Pokemon's Stat
|
* Atttempt to multiply a Pokemon's Stat.
|
||||||
*/
|
*/
|
||||||
apply({ statVal, hasApplied }: FieldMultiplyStatAbAttrParams): void {
|
apply({ statVal, hasApplied }: FieldMultiplyStatAbAttrParams): void {
|
||||||
statVal.value *= this.multiplier;
|
statVal.value *= this.multiplier;
|
||||||
@ -1572,23 +1675,25 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the move type change attribute can be applied
|
* Determine if the move type change attribute can be applied.
|
||||||
*
|
*
|
||||||
* Can be applied if:
|
* Can be applied if:
|
||||||
* - The ability's condition is met, e.g. pixilate only boosts normal moves,
|
* - The ability's condition is met, e.g. pixilate only boosts normal moves,
|
||||||
* - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK}
|
* - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK}
|
||||||
* - The user is not terastallized and using tera blast
|
* - The user is not Terastallized and using Tera Blast
|
||||||
* - The user is not a terastallized terapagos with tera stellar using tera starstorm
|
* - The user is not a Terastallized Terapagos using Stellar-type Tera Starstorm
|
||||||
*/
|
*/
|
||||||
override canApply({ pokemon, opponent: target, move }: MoveTypeChangeAbAttrParams): boolean {
|
override canApply({ pokemon, opponent: target, move }: MoveTypeChangeAbAttrParams): boolean {
|
||||||
return (
|
return (
|
||||||
(!this.condition || this.condition(pokemon, target, move)) &&
|
(!this.condition || this.condition(pokemon, target, move)) &&
|
||||||
!noAbilityTypeOverrideMoves.has(move.id) &&
|
!noAbilityTypeOverrideMoves.has(move.id) &&
|
||||||
(!pokemon.isTerastallized ||
|
!(
|
||||||
(move.id !== MoveId.TERA_BLAST &&
|
pokemon.isTerastallized &&
|
||||||
(move.id !== MoveId.TERA_STARSTORM ||
|
(move.id === MoveId.TERA_BLAST ||
|
||||||
pokemon.getTeraType() !== PokemonType.STELLAR ||
|
(move.id === MoveId.TERA_STARSTORM &&
|
||||||
!pokemon.hasSpecies(SpeciesId.TERAPAGOS))))
|
pokemon.getTeraType() === PokemonType.STELLAR &&
|
||||||
|
pokemon.hasSpecies(SpeciesId.TERAPAGOS)))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1661,23 +1766,19 @@ export interface AddSecondStrikeAbAttrParams extends Omit<AugmentMoveInteraction
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for abilities that convert single-strike moves to two-strike moves (i.e. Parental Bond).
|
* Class for abilities that add additional strikes to single-target moves.
|
||||||
* @param damageMultiplier the damage multiplier for the second strike, relative to the first.
|
* Used by {@linkcode Moves.PARENTAL_BOND | Parental Bond}.
|
||||||
*/
|
*/
|
||||||
export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
|
export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
|
||||||
private damageMultiplier: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param damageMultiplier - The damage multiplier for the second strike, relative to the first
|
* @param damageMultiplier - The damage multiplier for the second strike, relative to the first
|
||||||
*/
|
*/
|
||||||
constructor(damageMultiplier: number) {
|
constructor(private damageMultiplier: number) {
|
||||||
super(false);
|
super(false);
|
||||||
|
|
||||||
this.damageMultiplier = damageMultiplier;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the move can be multi-strike enhanced
|
* Return whether the move can be multi-strike enhanced.
|
||||||
*/
|
*/
|
||||||
override canApply({ pokemon, move }: AddSecondStrikeAbAttrParams): boolean {
|
override canApply({ pokemon, move }: AddSecondStrikeAbAttrParams): boolean {
|
||||||
return move.canBeMultiStrikeEnhanced(pokemon, true);
|
return move.canBeMultiStrikeEnhanced(pokemon, true);
|
||||||
@ -2021,9 +2122,10 @@ export abstract class PostAttackAbAttr extends AbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, this method checks that the move used is a damaging attack.
|
* By default, this method checks that the move used is a damaging attack before
|
||||||
* This can be changed by providing a different {@link attackCondition} to the constructor.
|
* applying the effect of any inherited class.
|
||||||
* @see {@link ConfusionOnStatusEffectAbAttr} for an example of an effect that does not require a damaging move.
|
* This can be changed by providing a different {@linkcode attackCondition} to the constructor.
|
||||||
|
* @see {@linkcode ConfusionOnStatusEffectAbAttr} for an example of an effect that does not require a damaging move.
|
||||||
*/
|
*/
|
||||||
override canApply({ pokemon, opponent, move }: Closed<PostMoveInteractionAbAttrParams>): boolean {
|
override canApply({ pokemon, opponent, move }: Closed<PostMoveInteractionAbAttrParams>): boolean {
|
||||||
return this.attackCondition(pokemon, opponent, move);
|
return this.attackCondition(pokemon, opponent, move);
|
||||||
@ -3511,18 +3613,18 @@ export interface ConfusionOnStatusEffectAbAttrParams extends AbAttrBaseParams {
|
|||||||
*/
|
*/
|
||||||
export class ConfusionOnStatusEffectAbAttr extends AbAttr {
|
export class ConfusionOnStatusEffectAbAttr extends AbAttr {
|
||||||
/** List of effects to apply confusion after */
|
/** List of effects to apply confusion after */
|
||||||
private effects: StatusEffect[];
|
private effects: ReadonlySet<StatusEffect>;
|
||||||
|
|
||||||
constructor(...effects: StatusEffect[]) {
|
constructor(...effects: StatusEffect[]) {
|
||||||
super();
|
super();
|
||||||
this.effects = effects;
|
this.effects = new Set(effects);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns Whether the ability can apply confusion to the opponent
|
* @returns Whether the ability can apply confusion to the opponent
|
||||||
*/
|
*/
|
||||||
override canApply({ opponent, effect }: ConfusionOnStatusEffectAbAttrParams): boolean {
|
override canApply({ opponent, effect }: ConfusionOnStatusEffectAbAttrParams): boolean {
|
||||||
return this.effects.includes(effect) && !opponent.isFainted() && opponent.canAddTag(BattlerTagType.CONFUSED);
|
return this.effects.has(effect) && !opponent.isFainted() && opponent.canAddTag(BattlerTagType.CONFUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -4500,8 +4602,8 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After the turn ends, resets the status of either the ability holder or their ally
|
* After the turn ends, resets the status of either the user or their ally.
|
||||||
* @param allyTarget Whether to target ally, defaults to false (self-target)
|
* @param allyTarget Whether to target the user's ally; default `false` (self-target)
|
||||||
*
|
*
|
||||||
* @sealed
|
* @sealed
|
||||||
*/
|
*/
|
||||||
@ -4786,17 +4888,22 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
|
|||||||
!opp.switchOutStatus,
|
!opp.switchOutStatus,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/** Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) */
|
|
||||||
|
/** Deal damage to all sleeping, on-field opponents equal to 1/8 of their max hp (min 1). */
|
||||||
override apply({ pokemon, simulated }: AbAttrBaseParams): void {
|
override apply({ pokemon, simulated }: AbAttrBaseParams): void {
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const opp of pokemon.getOpponents()) {
|
for (const opp of pokemon.getOpponents()) {
|
||||||
if (
|
if ((opp.status?.effect !== StatusEffect.SLEEP && !opp.hasAbility(AbilityId.COMATOSE)) || opp.switchOutStatus) {
|
||||||
(opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) &&
|
continue;
|
||||||
!opp.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") &&
|
}
|
||||||
!opp.switchOutStatus
|
|
||||||
) {
|
const cancelled = new BooleanHolder(false);
|
||||||
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, simulated, cancelled });
|
||||||
|
|
||||||
|
if (!cancelled.value) {
|
||||||
opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }),
|
i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }),
|
||||||
@ -4809,7 +4916,8 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
|
|||||||
/**
|
/**
|
||||||
* Grabs the last failed Pokeball used
|
* Grabs the last failed Pokeball used
|
||||||
* @sealed
|
* @sealed
|
||||||
* @see {@linkcode applyPostTurn} */
|
* @see {@linkcode applyPostTurn}
|
||||||
|
*/
|
||||||
export class FetchBallAbAttr extends PostTurnAbAttr {
|
export class FetchBallAbAttr extends PostTurnAbAttr {
|
||||||
override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean {
|
override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean {
|
||||||
return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer;
|
return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer;
|
||||||
@ -5681,11 +5789,13 @@ export class InfiltratorAbAttr extends AbAttr {
|
|||||||
* Allows the source to bounce back {@linkcode MoveFlags.REFLECTABLE | Reflectable}
|
* Allows the source to bounce back {@linkcode MoveFlags.REFLECTABLE | Reflectable}
|
||||||
* moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}.
|
* moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}.
|
||||||
* @sealed
|
* @sealed
|
||||||
|
* @todo Make reflection a part of this ability's effects
|
||||||
*/
|
*/
|
||||||
export class ReflectStatusMoveAbAttr extends AbAttr {
|
export class ReflectStatusMoveAbAttr extends AbAttr {
|
||||||
private declare readonly _: never;
|
private declare readonly _: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make these ability attributes be flags instead of dummy attributes
|
||||||
/** @sealed */
|
/** @sealed */
|
||||||
export class NoTransformAbilityAbAttr extends AbAttr {
|
export class NoTransformAbilityAbAttr extends AbAttr {
|
||||||
private declare readonly _: never;
|
private declare readonly _: never;
|
||||||
|
@ -8,23 +8,18 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
messages: string[] = [],
|
messages: string[] = [],
|
||||||
) {
|
) {
|
||||||
const { simulated = false, passive = false, pokemon } = params;
|
const { simulated = false, passive = false, pokemon } = params;
|
||||||
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
if (!pokemon.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
|
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
|
||||||
if (
|
const attrs = ability.getAttrs(attrType);
|
||||||
gainedMidTurn &&
|
if (gainedMidTurn && attrs.some(attr => attr.is("PostSummonAbAttr") && !attr.shouldActivateOnGain())) {
|
||||||
ability.getAttrs(attrType).some(attr => {
|
|
||||||
attr.is("PostSummonAbAttr") && !attr.shouldActivateOnGain();
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const attr of ability.getAttrs(attrType)) {
|
for (const attr of attrs) {
|
||||||
const condition = attr.getCondition();
|
const condition = attr.getCondition();
|
||||||
let abShown = false;
|
|
||||||
// We require an `as any` cast to suppress an error about the `params` type not being assignable to
|
// We require an `as any` cast to suppress an error about the `params` type not being assignable to
|
||||||
// the type of the argument expected by `attr.canApply()`. This is OK, because we know that
|
// the type of the argument expected by `attr.canApply()`. This is OK, because we know that
|
||||||
// `attr` is an instance of the `attrType` class provided to the method, and typescript _will_ check
|
// `attr` is an instance of the `attrType` class provided to the method, and typescript _will_ check
|
||||||
@ -33,7 +28,7 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.setPhaseQueueSplice();
|
let abShown = false;
|
||||||
|
|
||||||
if (attr.showAbility && !simulated) {
|
if (attr.showAbility && !simulated) {
|
||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||||
@ -45,6 +40,7 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
globalScene.phaseManager.queueMessage(message);
|
globalScene.phaseManager.queueMessage(message);
|
||||||
}
|
}
|
||||||
|
// TODO: Should messages be added to the array if they aren't actually shown?
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
// The `as any` cast here uses the same reasoning as above.
|
// The `as any` cast here uses the same reasoning as above.
|
||||||
@ -57,8 +53,6 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
pokemon.waveData.abilitiesApplied.add(ability.id);
|
pokemon.waveData.abilitiesApplied.add(ability.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.clearPhaseQueueSplice();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,22 +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";
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
Any arena tag that persists across turns *must* extend from `SerializableArenaTag` in the class definition signature.
|
||||||
|
|
||||||
|
Serializable ArenaTags have strict rules for their fields.
|
||||||
|
These rules ensure that only the data necessary to reconstruct the tag is serialized, and that the
|
||||||
|
session loader is able to deserialize saved tags correctly.
|
||||||
|
|
||||||
|
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.
|
* 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
|
* 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.
|
* the Pokemon currently on-field, only cleared on arena reset or through their respective {@linkcode ArenaTag.lapse | lapse} methods.
|
||||||
*/
|
*/
|
||||||
export abstract class ArenaTag {
|
export abstract class ArenaTag implements BaseArenaTag {
|
||||||
constructor(
|
/** The type of the arena tag */
|
||||||
public tagType: ArenaTagType,
|
public abstract readonly tagType: ArenaTagType;
|
||||||
public turnCount: number,
|
public turnCount: number;
|
||||||
public sourceMove?: MoveId,
|
public sourceMove?: MoveId;
|
||||||
public sourceId?: number,
|
public sourceId: number | undefined;
|
||||||
public side: ArenaTagSide = ArenaTagSide.BOTH,
|
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;
|
||||||
@ -55,8 +121,17 @@ export abstract class ArenaTag {
|
|||||||
|
|
||||||
onOverlap(_arena: Arena, _source: Pokemon | null): void {}
|
onOverlap(_arena: Arena, _source: Pokemon | null): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger this {@linkcode ArenaTag}'s effect, reducing its duration as applicable.
|
||||||
|
* Will ignore durations of all tags with durations `<=0`.
|
||||||
|
* @param _arena - The {@linkcode Arena} at the moment the tag is being lapsed.
|
||||||
|
* Unused by default but can be used by sub-classes.
|
||||||
|
* @returns `true` if this tag should be kept; `false` if it should be removed.
|
||||||
|
*/
|
||||||
lapse(_arena: Arena): boolean {
|
lapse(_arena: Arena): boolean {
|
||||||
return this.turnCount < 1 || !!--this.turnCount;
|
// TODO: Rather than treating negative duration tags as being indefinite,
|
||||||
|
// make all duration based classes inherit from their own sub-class
|
||||||
|
return this.turnCount < 1 || --this.turnCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMoveName(): string | null {
|
getMoveName(): string | null {
|
||||||
@ -66,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;
|
||||||
@ -101,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 {
|
||||||
@ -164,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.
|
||||||
@ -227,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 {
|
||||||
@ -247,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 {
|
||||||
@ -267,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 {
|
||||||
@ -291,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;
|
||||||
@ -391,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,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) {
|
||||||
@ -488,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,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 {
|
||||||
@ -538,23 +607,9 @@ export class NoCritTag extends ArenaTag {
|
|||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -578,8 +633,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 {
|
||||||
@ -596,8 +655,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 {
|
||||||
@ -615,8 +678,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 */
|
||||||
@ -645,7 +709,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;
|
||||||
|
|
||||||
@ -658,8 +723,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;
|
||||||
@ -698,7 +763,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;
|
||||||
@ -711,8 +776,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 {
|
||||||
@ -769,11 +835,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 {
|
||||||
@ -799,7 +866,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -810,7 +877,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", {
|
||||||
@ -850,8 +917,9 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||||||
* 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 {
|
||||||
@ -939,8 +1007,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 {
|
||||||
@ -1007,14 +1076,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1056,9 +1168,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 {
|
||||||
@ -1084,9 +1197,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 {
|
||||||
@ -1152,9 +1266,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 {
|
||||||
@ -1167,8 +1282,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 {
|
||||||
@ -1189,18 +1305,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1255,7 +1374,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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1266,9 +1387,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 {
|
||||||
@ -1314,9 +1436,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 {
|
||||||
@ -1348,9 +1471,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 {
|
||||||
@ -1370,9 +1494,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 {
|
||||||
@ -1386,15 +1511,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 {
|
||||||
@ -1406,19 +1545,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) {
|
||||||
@ -1436,7 +1577,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"));
|
||||||
}
|
}
|
||||||
@ -1444,7 +1585,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 });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1453,10 +1596,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(
|
||||||
@ -1473,7 +1612,7 @@ export function getArenaTag(
|
|||||||
tagType: ArenaTagType,
|
tagType: ArenaTagType,
|
||||||
turnCount: number,
|
turnCount: number,
|
||||||
sourceMove: MoveId | undefined,
|
sourceMove: MoveId | undefined,
|
||||||
sourceId: number,
|
sourceId: number | undefined,
|
||||||
side: ArenaTagSide = ArenaTagSide.BOTH,
|
side: ArenaTagSide = ArenaTagSide.BOTH,
|
||||||
): ArenaTag | null {
|
): ArenaTag | null {
|
||||||
switch (tagType) {
|
switch (tagType) {
|
||||||
@ -1488,7 +1627,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:
|
||||||
@ -1506,7 +1645,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:
|
||||||
@ -1538,12 +1677,43 @@ 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): ArenaTag {
|
||||||
const tag =
|
const tag =
|
||||||
getArenaTag(source.tagType, source.sourceId, source.sourceMove, source.turnCount, source.side) ?? new NoneTag();
|
getArenaTag(source.tagType, source.sourceId, source.sourceMove, source.turnCount, source.side) ?? new NoneTag();
|
||||||
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.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;
|
||||||
|
};
|
||||||
|
@ -74,10 +74,13 @@ export class BattlerTag {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tick down this {@linkcode BattlerTag}'s duration.
|
* Tick down this {@linkcode BattlerTag}'s duration.
|
||||||
* @returns `true` if the tag should be kept (`turnCount > 0`)
|
* @param _pokemon - The {@linkcode Pokemon} whom this tag belongs to.
|
||||||
|
* Unused by default but can be used by subclasses.
|
||||||
|
* @param _lapseType - The {@linkcode BattlerTagLapseType} being lapsed.
|
||||||
|
* Unused by default but can be used by subclasses.
|
||||||
|
* @returns `true` if the tag should be kept (`turnCount` > 0`)
|
||||||
*/
|
*/
|
||||||
lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||||
// TODO: Maybe flip this (return `true` if tag needs removal)
|
|
||||||
return --this.turnCount > 0;
|
return --this.turnCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ export interface TerrainBattlerTag {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for tags that restrict the usage of moves. This effect is generally referred to as "disabling" a move
|
* Base class for tags that restrict the usage of moves. This effect is generally referred to as "disabling" a move
|
||||||
* in-game. This is not to be confused with {@linkcode MoveId.DISABLE}.
|
* in-game (not to be confused with {@linkcode MoveId.DISABLE}).
|
||||||
*
|
*
|
||||||
* Descendants can override {@linkcode isMoveRestricted} to restrict moves that
|
* Descendants can override {@linkcode isMoveRestricted} to restrict moves that
|
||||||
* match a condition. A restricted move gets cancelled before it is used.
|
* match a condition. A restricted move gets cancelled before it is used.
|
||||||
|
@ -168,9 +168,9 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all move attributes that match `attrType`
|
* Get all move attributes that match `attrType`.
|
||||||
* @param attrType any attribute that extends {@linkcode MoveAttr}
|
* @param attrType - The name of a {@linkcode MoveAttr} to search for
|
||||||
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
* @returns An array containing all attributes matching `attrType`, or an empty array if none match.
|
||||||
*/
|
*/
|
||||||
getAttrs<T extends MoveAttrString>(attrType: T): (MoveAttrMap[T])[] {
|
getAttrs<T extends MoveAttrString>(attrType: T): (MoveAttrMap[T])[] {
|
||||||
const targetAttr = MoveAttrs[attrType];
|
const targetAttr = MoveAttrs[attrType];
|
||||||
@ -181,9 +181,9 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a move has an attribute that matches `attrType`
|
* Check if a move has an attribute that matches `attrType`.
|
||||||
* @param attrType any attribute that extends {@linkcode MoveAttr}
|
* @param attrType - The name of a {@linkcode MoveAttr} to search for
|
||||||
* @returns true if the move has attribute `attrType`
|
* @returns Whether this move has at least 1 attribute that matches `attrType`
|
||||||
*/
|
*/
|
||||||
hasAttr(attrType: MoveAttrString): boolean {
|
hasAttr(attrType: MoveAttrString): boolean {
|
||||||
const targetAttr = MoveAttrs[attrType];
|
const targetAttr = MoveAttrs[attrType];
|
||||||
@ -195,23 +195,25 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes as input a boolean function and returns the first MoveAttr in attrs that matches true
|
* Find the first attribute that matches a given predicate function.
|
||||||
* @param attrPredicate
|
* @param attrPredicate - The predicate function to search `MoveAttr`s by
|
||||||
* @returns the first {@linkcode MoveAttr} element in attrs that makes the input function return true
|
* @returns The first {@linkcode MoveAttr} for which `attrPredicate` returns `true`
|
||||||
*/
|
*/
|
||||||
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
|
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
|
||||||
return this.attrs.find(attrPredicate)!; // TODO: is the bang correct?
|
// TODO: Remove bang and make return type `MoveAttr | undefined`,
|
||||||
|
// as well as add overload for functions of type `x is T`
|
||||||
|
return this.attrs.find(attrPredicate)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new MoveAttr to the move (appends to the attr array)
|
* Adds a new MoveAttr to this move (appends to the attr array).
|
||||||
* if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition}
|
* If the MoveAttr also comes with a condition, it is added to its {@linkcode MoveCondition} array.
|
||||||
* @param AttrType {@linkcode MoveAttr} the constructor of a MoveAttr class
|
* @param attrType - The {@linkcode MoveAttr} to add
|
||||||
* @param args the args needed to instantiate a the given class
|
* @param args - The arguments needed to instantiate the given class
|
||||||
* @returns the called object {@linkcode Move}
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
attr<T extends Constructor<MoveAttr>>(AttrType: T, ...args: ConstructorParameters<T>): this {
|
attr<T extends Constructor<MoveAttr>>(attrType: T, ...args: ConstructorParameters<T>): this {
|
||||||
const attr = new AttrType(...args);
|
const attr = new attrType(...args);
|
||||||
this.attrs.push(attr);
|
this.attrs.push(attr);
|
||||||
let attrCondition = attr.getCondition();
|
let attrCondition = attr.getCondition();
|
||||||
if (attrCondition) {
|
if (attrCondition) {
|
||||||
@ -225,11 +227,13 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new MoveAttr to the move (appends to the attr array)
|
* Adds a new MoveAttr to this move (appends to the attr array).
|
||||||
* if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition}
|
* If the MoveAttr also comes with a condition, it is added to its {@linkcode MoveCondition} array.
|
||||||
* Almost identical to {@link attr}, except you are passing in a MoveAttr object, instead of a constructor and it's arguments
|
*
|
||||||
* @param attrAdd {@linkcode MoveAttr} the attribute to add
|
* Similar to {@linkcode attr}, except this takes an already instantiated {@linkcode MoveAttr} object
|
||||||
* @returns the called object {@linkcode Move}
|
* as opposed to a constructor and its arguments.
|
||||||
|
* @param attrAdd - The {@linkcode MoveAttr} to add
|
||||||
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
addAttr(attrAdd: MoveAttr): this {
|
addAttr(attrAdd: MoveAttr): this {
|
||||||
this.attrs.push(attrAdd);
|
this.attrs.push(attrAdd);
|
||||||
@ -246,8 +250,8 @@ export abstract class Move implements Localizable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the move target of this move
|
* Sets the move target of this move
|
||||||
* @param moveTarget {@linkcode MoveTarget} the move target to set
|
* @param moveTarget - The {@linkcode MoveTarget} to set
|
||||||
* @returns the called object {@linkcode Move}
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
target(moveTarget: MoveTarget): this {
|
target(moveTarget: MoveTarget): this {
|
||||||
this.moveTarget = moveTarget;
|
this.moveTarget = moveTarget;
|
||||||
@ -255,13 +259,13 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter function that returns if this Move has a MoveFlag
|
* Getter function that returns if this Move has a given MoveFlag.
|
||||||
* @param flag {@linkcode MoveFlags} to check
|
* @param flag - The {@linkcode MoveFlags} to check
|
||||||
* @returns boolean
|
* @returns Whether this Move has the specified flag.
|
||||||
*/
|
*/
|
||||||
hasFlag(flag: MoveFlags): boolean {
|
hasFlag(flag: MoveFlags): boolean {
|
||||||
// internally it is taking the bitwise AND (MoveFlags are represented as bit-shifts) and returning False if result is 0 and true otherwise
|
// Flags are internally represented as bitmasks, so we check by taking the bitwise AND.
|
||||||
return !!(this.flags & flag);
|
return (this.flags & flag) !== MoveFlags.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,13 +310,13 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the move is immune to certain types.
|
* Checks if the target is immune to this Move's type.
|
||||||
*
|
|
||||||
* Currently looks at cases of Grass types with powder moves and Dark types with moves affected by Prankster.
|
* Currently looks at cases of Grass types with powder moves and Dark types with moves affected by Prankster.
|
||||||
* @param user - The source of this move
|
* @param user - The {@linkcode Pokemon} using this move
|
||||||
* @param target - The target of this move
|
* @param target - The {@linkcode Pokemon} targeted by this move
|
||||||
* @param type - The type of the move's target
|
* @param type - The {@linkcode PokemonType} of the target
|
||||||
* @returns boolean
|
* @returns Whether the move is blocked by the target's type.
|
||||||
|
* Self-targeted moves will return `false` regardless of circumstances.
|
||||||
*/
|
*/
|
||||||
isTypeImmune(user: Pokemon, target: Pokemon, type: PokemonType): boolean {
|
isTypeImmune(user: Pokemon, target: Pokemon, type: PokemonType): boolean {
|
||||||
if (this.moveTarget === MoveTarget.USER) {
|
if (this.moveTarget === MoveTarget.USER) {
|
||||||
@ -326,7 +330,7 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PokemonType.DARK:
|
case PokemonType.DARK:
|
||||||
if (user.hasAbility(AbilityId.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) {
|
if (user.hasAbility(AbilityId.PRANKSTER) && this.category === MoveCategory.STATUS && user.isOpponent(target)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -336,9 +340,9 @@ export abstract class Move implements Localizable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the move would hit its target's Substitute instead of the target itself.
|
* Checks if the move would hit its target's Substitute instead of the target itself.
|
||||||
* @param user The {@linkcode Pokemon} using this move
|
* @param user - The {@linkcode Pokemon} using this move
|
||||||
* @param target The {@linkcode Pokemon} targeted by this move
|
* @param target - The {@linkcode Pokemon} targeted by this move
|
||||||
* @returns `true` if the move can bypass the target's Substitute; `false` otherwise.
|
* @returns Whether this Move will hit the target's Substitute (assuming one exists).
|
||||||
*/
|
*/
|
||||||
hitsSubstitute(user: Pokemon, target?: Pokemon): boolean {
|
hitsSubstitute(user: Pokemon, target?: Pokemon): boolean {
|
||||||
if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget)
|
if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget)
|
||||||
@ -356,13 +360,14 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a move condition to the move
|
* Adds a condition to this move (in addition to any provided by its prior {@linkcode MoveAttr}s).
|
||||||
* @param condition {@linkcode MoveCondition} or {@linkcode MoveConditionFunc}, appends to conditions array a new MoveCondition object
|
* The move will fail upon use if at least 1 of its conditions is not met.
|
||||||
* @returns the called object {@linkcode Move}
|
* @param condition - The {@linkcode MoveCondition} or {@linkcode MoveConditionFunc} to add to the conditions array.
|
||||||
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
condition(condition: MoveCondition | MoveConditionFunc): this {
|
condition(condition: MoveCondition | MoveConditionFunc): this {
|
||||||
if (typeof condition === "function") {
|
if (typeof condition === "function") {
|
||||||
condition = new MoveCondition(condition as MoveConditionFunc);
|
condition = new MoveCondition(condition);
|
||||||
}
|
}
|
||||||
this.conditions.push(condition);
|
this.conditions.push(condition);
|
||||||
|
|
||||||
@ -370,16 +375,22 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dev flag for documenting edge cases. When using this, please document the known edge case.
|
* Mark a move as having one or more edge cases.
|
||||||
* @returns the called object {@linkcode Move}
|
* The move may lack certain niche interactions with other moves/abilities,
|
||||||
|
* but still functions as intended in most cases.
|
||||||
|
*
|
||||||
|
* When using this, **make sure to document the edge case** (or else this becomes pointless).
|
||||||
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
edgeCase(): this {
|
edgeCase(): this {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the move as "partial": appends texts to the move name
|
* Mark this move as partially implemented.
|
||||||
* @returns the called object {@linkcode Move}
|
* Partial moves are expected to have some core functionality implemented, but may lack
|
||||||
|
* certain notable features or interactions with other moves or abilities.
|
||||||
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
partial(): this {
|
partial(): this {
|
||||||
this.nameAppend += " (P)";
|
this.nameAppend += " (P)";
|
||||||
@ -387,8 +398,10 @@ export abstract class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the move as "unimplemented": appends texts to the move name
|
* Mark this move as unimplemented.
|
||||||
* @returns the called object {@linkcode Move}
|
* Unimplemented moves are ones which have _none_ of their basic functionality enabled,
|
||||||
|
* and cannot be used.
|
||||||
|
* @returns `this`
|
||||||
*/
|
*/
|
||||||
unimplemented(): this {
|
unimplemented(): this {
|
||||||
this.nameAppend += " (N)";
|
this.nameAppend += " (N)";
|
||||||
@ -963,10 +976,8 @@ export class AttackMove extends Move {
|
|||||||
constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
|
constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
|
||||||
super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation);
|
super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation);
|
||||||
|
|
||||||
/**
|
// > All damaging Fire-type moves can... thaw a frozen target, regardless of whether or not they have a chance to burn.
|
||||||
* {@link https://bulbapedia.bulbagarden.net/wiki/Freeze_(status_condition)}
|
// - https://bulbapedia.bulbagarden.net/wiki/Freeze_(status_condition)
|
||||||
* > All damaging Fire-type moves can now thaw a frozen target, regardless of whether or not they have a chance to burn;
|
|
||||||
*/
|
|
||||||
if (this.type === PokemonType.FIRE) {
|
if (this.type === PokemonType.FIRE) {
|
||||||
this.addAttr(new HealStatusEffectAttr(false, StatusEffect.FREEZE));
|
this.addAttr(new HealStatusEffectAttr(false, StatusEffect.FREEZE));
|
||||||
}
|
}
|
||||||
@ -1222,7 +1233,8 @@ interface MoveEffectAttrOptions {
|
|||||||
effectChanceOverride?: number;
|
effectChanceOverride?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Base class defining all Move Effect Attributes
|
/**
|
||||||
|
* Base class defining all Move Effect Attributes
|
||||||
* @extends MoveAttr
|
* @extends MoveAttr
|
||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
*/
|
*/
|
||||||
@ -1240,8 +1252,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines when this effect should trigger in the move's effect order.
|
* Defines when this effect should trigger in the move's effect order.
|
||||||
* @default MoveEffectTrigger.POST_APPLY
|
* @defaultValue {@linkcode MoveEffectTrigger.POST_APPLY}
|
||||||
* @see {@linkcode MoveEffectTrigger}
|
|
||||||
*/
|
*/
|
||||||
public get trigger () {
|
public get trigger () {
|
||||||
return this.options?.trigger ?? MoveEffectTrigger.POST_APPLY;
|
return this.options?.trigger ?? MoveEffectTrigger.POST_APPLY;
|
||||||
@ -1250,7 +1261,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
/**
|
/**
|
||||||
* `true` if this effect should only trigger on the first hit of
|
* `true` if this effect should only trigger on the first hit of
|
||||||
* multi-hit moves.
|
* multi-hit moves.
|
||||||
* @default false
|
* @defaultValue `false`
|
||||||
*/
|
*/
|
||||||
public get firstHitOnly () {
|
public get firstHitOnly () {
|
||||||
return this.options?.firstHitOnly ?? false;
|
return this.options?.firstHitOnly ?? false;
|
||||||
@ -1259,7 +1270,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
/**
|
/**
|
||||||
* `true` if this effect should only trigger on the last hit of
|
* `true` if this effect should only trigger on the last hit of
|
||||||
* multi-hit moves.
|
* multi-hit moves.
|
||||||
* @default false
|
* @defaultValue `false`
|
||||||
*/
|
*/
|
||||||
public get lastHitOnly () {
|
public get lastHitOnly () {
|
||||||
return this.options?.lastHitOnly ?? false;
|
return this.options?.lastHitOnly ?? false;
|
||||||
@ -1268,7 +1279,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
/**
|
/**
|
||||||
* `true` if this effect should apply only upon hitting a target
|
* `true` if this effect should apply only upon hitting a target
|
||||||
* for the first time when targeting multiple {@linkcode Pokemon}.
|
* for the first time when targeting multiple {@linkcode Pokemon}.
|
||||||
* @default false
|
* @defaultValue `false`
|
||||||
*/
|
*/
|
||||||
public get firstTargetOnly () {
|
public get firstTargetOnly () {
|
||||||
return this.options?.firstTargetOnly ?? false;
|
return this.options?.firstTargetOnly ?? false;
|
||||||
@ -2572,9 +2583,12 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the effect of Psycho Shift to its target
|
* Applies the effect of {@linkcode Moves.PSYCHO_SHIFT} to its target.
|
||||||
* Psycho Shift takes the user's status effect and passes it onto the target. The user is then healed after the move has been successfully executed.
|
* Psycho Shift takes the user's status effect and passes it onto the target.
|
||||||
* @returns `true` if Psycho Shift's effect is able to be applied to the target
|
* The user is then healed after the move has been successfully executed.
|
||||||
|
* @param user - The {@linkcode Pokemon} using the move
|
||||||
|
* @param target - The {@linkcode Pokemon} targeted by the move.
|
||||||
|
* @returns - Whether the effect was successfully applied to the target.
|
||||||
*/
|
*/
|
||||||
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||||
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||||
@ -2907,6 +2921,12 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to add the {@linkcode BattlerTagType.BYPASS_SLEEP | BYPASS_SLEEP Battler Tag} for 1 turn to the user before move use.
|
||||||
|
* Used by {@linkcode Moves.SNORE} and {@linkcode Moves.SLEEP_TALK}.
|
||||||
|
*/
|
||||||
|
// TODO: Should this use a battler tag?
|
||||||
|
// TODO: Give this `userSleptOrComatoseCondition` by default
|
||||||
export class BypassSleepAttr extends MoveAttr {
|
export class BypassSleepAttr extends MoveAttr {
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
if (user.status?.effect === StatusEffect.SLEEP) {
|
if (user.status?.effect === StatusEffect.SLEEP) {
|
||||||
@ -2924,7 +2944,7 @@ export class BypassSleepAttr extends MoveAttr {
|
|||||||
* @param move
|
* @param move
|
||||||
*/
|
*/
|
||||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
return user.status && user.status.effect === StatusEffect.SLEEP ? 200 : -10;
|
return user.status?.effect === StatusEffect.SLEEP ? 200 : -10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3309,7 +3329,7 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* `true` to display a message for the stat change.
|
* `true` to display a message for the stat change.
|
||||||
* @default true
|
* @defaultValue `true`
|
||||||
*/
|
*/
|
||||||
private get showMessage () {
|
private get showMessage () {
|
||||||
return this.options?.showMessage ?? true;
|
return this.options?.showMessage ?? true;
|
||||||
@ -5472,7 +5492,10 @@ export class NoEffectAttr extends MoveAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const crashDamageFunc = (user: Pokemon, move: Move) => {
|
/**
|
||||||
|
* Function to deal Crash Damage (1/2 max hp) to the user on apply.
|
||||||
|
*/
|
||||||
|
const crashDamageFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -7111,7 +7134,8 @@ export class CopyMoveAttr extends CallMoveAttr {
|
|||||||
/**
|
/**
|
||||||
* Attribute used for moves that cause the target to repeat their last used move.
|
* Attribute used for moves that cause the target to repeat their last used move.
|
||||||
*
|
*
|
||||||
* Used for [Instruct](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)).
|
* Used by {@linkcode Moves.INSTRUCT | Instruct}.
|
||||||
|
* @see [Instruct on Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move))
|
||||||
*/
|
*/
|
||||||
export class RepeatMoveAttr extends MoveEffectAttr {
|
export class RepeatMoveAttr extends MoveEffectAttr {
|
||||||
private movesetMove: PokemonMove;
|
private movesetMove: PokemonMove;
|
||||||
|
@ -36,13 +36,13 @@ export class PokemonMove {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
|
* Checks whether this move can be selected/performed by a Pokemon, without consideration for the move's targets.
|
||||||
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
|
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
|
||||||
*
|
*
|
||||||
* @param pokemon - {@linkcode Pokemon} that would be using this move
|
* @param pokemon - The {@linkcode Pokemon} attempting to use this move
|
||||||
* @param ignorePp - If `true`, skips the PP check
|
* @param ignorePp - Whether to ignore checking if the move is out of PP; default `false`
|
||||||
* @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
|
* @param ignoreRestrictionTags - Whether to skip checks for {@linkcode MoveRestrictionBattlerTag}s; default `false`
|
||||||
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
|
* @returns Whether this {@linkcode PokemonMove} can be selected by this Pokemon.
|
||||||
*/
|
*/
|
||||||
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
|
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
|
||||||
// TODO: Add Sky Drop's 1 turn stall
|
// TODO: Add Sky Drop's 1 turn stall
|
||||||
|
@ -406,12 +406,12 @@ export async function applyModifierTypeToPlayerPokemon(
|
|||||||
// Check if the Pokemon has max stacks of that item already
|
// Check if the Pokemon has max stacks of that item already
|
||||||
const modifier = modType.newModifier(pokemon);
|
const modifier = modType.newModifier(pokemon);
|
||||||
const existing = globalScene.findModifier(
|
const existing = globalScene.findModifier(
|
||||||
m =>
|
(m): m is PokemonHeldItemModifier =>
|
||||||
m instanceof PokemonHeldItemModifier &&
|
m instanceof PokemonHeldItemModifier &&
|
||||||
m.type.id === modType.id &&
|
m.type.id === modType.id &&
|
||||||
m.pokemonId === pokemon.id &&
|
m.pokemonId === pokemon.id &&
|
||||||
m.matchType(modifier),
|
m.matchType(modifier),
|
||||||
) as PokemonHeldItemModifier;
|
) as PokemonHeldItemModifier | undefined;
|
||||||
|
|
||||||
// At max stacks
|
// At max stacks
|
||||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||||
|
@ -13,6 +13,11 @@ export enum TerrainType {
|
|||||||
PSYCHIC,
|
PSYCHIC,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SerializedTerrain {
|
||||||
|
terrainType: TerrainType;
|
||||||
|
turnsLeft: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Terrain {
|
export class Terrain {
|
||||||
public terrainType: TerrainType;
|
public terrainType: TerrainType;
|
||||||
public turnsLeft: number;
|
public turnsLeft: number;
|
||||||
|
@ -11,6 +11,11 @@ import type { Move } from "#moves/move";
|
|||||||
import { randSeedInt } from "#utils/common";
|
import { randSeedInt } from "#utils/common";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
export interface SerializedWeather {
|
||||||
|
weatherType: WeatherType;
|
||||||
|
turnsLeft: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Weather {
|
export class Weather {
|
||||||
public weatherType: WeatherType;
|
public weatherType: WeatherType;
|
||||||
public turnsLeft: number;
|
public turnsLeft: number;
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* A list of possible flags that various moves may have.
|
||||||
|
* Represented internally as a bitmask.
|
||||||
|
*/
|
||||||
export enum MoveFlags {
|
export enum MoveFlags {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
MAKES_CONTACT = 1 << 0,
|
MAKES_CONTACT = 1 << 0,
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
import type { ArenaTagTypeMap } from "#data/arena-tag";
|
||||||
|
import type { NonSerializableArenaTagType, SerializableArenaTagType } from "#types/arena-tags";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing all different types of {@linkcode ArenaTag}s.
|
||||||
|
* @privateRemarks
|
||||||
|
* ⚠️ When modifying the fields in this enum, ensure that:
|
||||||
|
* - The entry is added to / removed from {@linkcode ArenaTagTypeMap}
|
||||||
|
* - The tag is added to / removed from {@linkcode NonSerializableArenaTagType} or {@linkcode SerializableArenaTagType}
|
||||||
|
*/
|
||||||
export enum ArenaTagType {
|
export enum ArenaTagType {
|
||||||
NONE = "NONE",
|
NONE = "NONE",
|
||||||
MUD_SPORT = "MUD_SPORT",
|
MUD_SPORT = "MUD_SPORT",
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
/**
|
/** Enum for selected battle style. */
|
||||||
* Determines the selected battle style.
|
|
||||||
* - 'Switch' - The option to switch the active pokemon at the start of a battle will be displayed.
|
|
||||||
* - 'Set' - The option to switch the active pokemon at the start of a battle will not display.
|
|
||||||
*/
|
|
||||||
export enum BattleStyle {
|
export enum BattleStyle {
|
||||||
SWITCH,
|
/** Display option to switch active pokemon at battle start. */
|
||||||
SET
|
SWITCH,
|
||||||
|
/** Hide option to switch active pokemon at battle start. */
|
||||||
|
SET
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* The index of a given Pokemon on-field.
|
||||||
|
* Used as an index into `globalScene.getField`, as well as for most target-specifying effects.
|
||||||
|
*/
|
||||||
export enum BattlerIndex {
|
export enum BattlerIndex {
|
||||||
ATTACKER = -1,
|
ATTACKER = -1,
|
||||||
PLAYER,
|
PLAYER,
|
||||||
|
@ -1,15 +1,4 @@
|
|||||||
/**
|
/** Defines the speed of gaining experience. */
|
||||||
* Defines the speed of gaining experience.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* The `expGainSpeed` can have several modes:
|
|
||||||
* - `0` - Default: The normal speed.
|
|
||||||
* - `1` - Fast: Fast speed.
|
|
||||||
* - `2` - Faster: Faster speed.
|
|
||||||
* - `3` - Skip: Skip gaining exp animation.
|
|
||||||
*
|
|
||||||
* @default 0 - Uses the default normal speed.
|
|
||||||
*/
|
|
||||||
export enum ExpGainsSpeed {
|
export enum ExpGainsSpeed {
|
||||||
/** The normal speed. */
|
/** The normal speed. */
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
/**
|
/** Enum for party experience gain notification style. */
|
||||||
* Determines exp notification style.
|
|
||||||
* - Default - the normal exp gain display, nothing changed
|
|
||||||
* - Only level up - we display the level up in the small frame instead of a message
|
|
||||||
* - Skip - no level up frame nor message
|
|
||||||
*/
|
|
||||||
export enum ExpNotification {
|
export enum ExpNotification {
|
||||||
DEFAULT,
|
/** Display amount flyout for all off-field party members upon gaining any amount of EXP. */
|
||||||
ONLY_LEVEL_UP,
|
DEFAULT,
|
||||||
SKIP
|
/** Display smaller flyout showing level gained on gaining a new level. */
|
||||||
|
ONLY_LEVEL_UP,
|
||||||
|
/** Do not show any flyouts for EXP gains or levelups. */
|
||||||
|
SKIP
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEven
|
|||||||
import type { Pokemon } from "#field/pokemon";
|
import type { Pokemon } from "#field/pokemon";
|
||||||
import { FieldEffectModifier } from "#modifiers/modifier";
|
import { FieldEffectModifier } from "#modifiers/modifier";
|
||||||
import type { Move } from "#moves/move";
|
import type { Move } from "#moves/move";
|
||||||
|
import type { AbstractConstructor } from "#types/type-helpers";
|
||||||
import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common";
|
import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common";
|
||||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||||
|
|
||||||
@ -652,7 +653,7 @@ export class Arena {
|
|||||||
* @param args array of parameters that the called upon tags may need
|
* @param args array of parameters that the called upon tags may need
|
||||||
*/
|
*/
|
||||||
applyTagsForSide(
|
applyTagsForSide(
|
||||||
tagType: ArenaTagType | Constructor<ArenaTag>,
|
tagType: ArenaTagType | Constructor<ArenaTag> | AbstractConstructor<ArenaTag>,
|
||||||
side: ArenaTagSide,
|
side: ArenaTagSide,
|
||||||
simulated: boolean,
|
simulated: boolean,
|
||||||
...args: unknown[]
|
...args: unknown[]
|
||||||
@ -674,7 +675,11 @@ export class Arena {
|
|||||||
* @param simulated if `true`, this applies arena tags without changing game state
|
* @param simulated if `true`, this applies arena tags without changing game state
|
||||||
* @param args array of parameters that the called upon tags may need
|
* @param args array of parameters that the called upon tags may need
|
||||||
*/
|
*/
|
||||||
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, simulated: boolean, ...args: unknown[]): void {
|
applyTags(
|
||||||
|
tagType: ArenaTagType | Constructor<ArenaTag> | AbstractConstructor<ArenaTag>,
|
||||||
|
simulated: boolean,
|
||||||
|
...args: unknown[]
|
||||||
|
): void {
|
||||||
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args);
|
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,21 +732,19 @@ export class Arena {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides
|
* Attempt to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides
|
||||||
* @param tagType The {@linkcode ArenaTagType} to retrieve
|
* @param tagType - The {@linkcode ArenaTagType} to retrieve
|
||||||
* @returns The existing {@linkcode ArenaTag}, or `undefined` if not present.
|
* @returns The existing {@linkcode ArenaTag}, or `undefined` if not present.
|
||||||
* @overload
|
* @overload
|
||||||
*/
|
*/
|
||||||
getTag(tagType: ArenaTagType): ArenaTag | undefined;
|
getTag(tagType: ArenaTagType): ArenaTag | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides
|
* Attempt to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides
|
||||||
* @param tagType The {@linkcode ArenaTag} to retrieve
|
* @param tagType - The constructor of the {@linkcode ArenaTag} to retrieve
|
||||||
* @returns The existing {@linkcode ArenaTag}, or `undefined` if not present.
|
* @returns The existing {@linkcode ArenaTag}, or `undefined` if not present.
|
||||||
* @overload
|
* @overload
|
||||||
*/
|
*/
|
||||||
getTag<T extends ArenaTag>(tagType: Constructor<T>): T | undefined;
|
getTag<T extends ArenaTag>(tagType: Constructor<T> | AbstractConstructor<T>): T | undefined;
|
||||||
|
getTag(tagType: ArenaTagType | Constructor<ArenaTag> | AbstractConstructor<ArenaTag>): ArenaTag | undefined {
|
||||||
getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag | undefined {
|
|
||||||
return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
|
return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,7 +760,10 @@ export class Arena {
|
|||||||
* @param side The {@linkcode ArenaTagSide} to look at
|
* @param side The {@linkcode ArenaTagSide} to look at
|
||||||
* @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there
|
* @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there
|
||||||
*/
|
*/
|
||||||
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined {
|
getTagOnSide(
|
||||||
|
tagType: ArenaTagType | Constructor<ArenaTag> | AbstractConstructor<ArenaTag>,
|
||||||
|
side: ArenaTagSide,
|
||||||
|
): ArenaTag | undefined {
|
||||||
return typeof tagType === "string"
|
return typeof tagType === "string"
|
||||||
? this.tags.find(
|
? this.tags.find(
|
||||||
t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side),
|
t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side),
|
||||||
|
@ -375,7 +375,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.ivs = ivs || getIvsFromId(this.id);
|
this.ivs = ivs || getIvsFromId(this.id);
|
||||||
|
|
||||||
if (this.gender === undefined) {
|
if (this.gender === undefined) {
|
||||||
this.generateGender();
|
this.gender = this.species.generateGender();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.formIndex === undefined) {
|
if (this.formIndex === undefined) {
|
||||||
@ -437,7 +437,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability).
|
* Return the name that will be displayed when this Pokemon is sent out into battle.
|
||||||
|
* @param useIllusion - Whether to consider this Pokemon's illusion if present; default `true`
|
||||||
|
* @returns The name to render for this {@linkcode Pokemon}.
|
||||||
*/
|
*/
|
||||||
getNameToRender(useIllusion = true) {
|
getNameToRender(useIllusion = true) {
|
||||||
const name: string =
|
const name: string =
|
||||||
@ -446,7 +448,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
!useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.nickname : this.nickname;
|
!useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.nickname : this.nickname;
|
||||||
try {
|
try {
|
||||||
if (nickname) {
|
if (nickname) {
|
||||||
return decodeURIComponent(escape(atob(nickname)));
|
return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually...
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -455,11 +457,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPokeball(useIllusion = false) {
|
/**
|
||||||
if (useIllusion) {
|
* Return this Pokemon's {@linkcode PokeballType}.
|
||||||
return this.summonData.illusion?.pokeball ?? this.pokeball;
|
* @param useIllusion - Whether to consider this Pokemon's illusion if present; default `false`
|
||||||
}
|
* @returns The {@linkcode PokeballType} that will be shown when this Pokemon is sent out into battle.
|
||||||
return this.pokeball;
|
*/
|
||||||
|
getPokeball(useIllusion = false): PokeballType {
|
||||||
|
return useIllusion && this.summonData.illusion ? this.summonData.illusion.pokeball : this.pokeball;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
@ -516,17 +520,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a pokemon is fainted (ie: its `hp <= 0`).
|
* Checks if a pokemon is fainted (ie: its `hp <= 0`).
|
||||||
* It's usually better to call {@linkcode isAllowedInBattle()}
|
* Usually should not be called directly in favor of calling {@linkcode isAllowedInBattle()}.
|
||||||
* @param checkStatus `true` to also check that the pokemon's status is {@linkcode StatusEffect.FAINT}
|
* @param checkStatus - Whether to also check that the pokemon's status is {@linkcode StatusEffect.FAINT}; default `false`
|
||||||
* @returns `true` if the pokemon is fainted
|
* @returns Whether this Pokemon is fainted, as described above.
|
||||||
*/
|
*/
|
||||||
public isFainted(checkStatus = false): boolean {
|
public isFainted(checkStatus = false): boolean {
|
||||||
return this.hp <= 0 && (!checkStatus || this.status?.effect === StatusEffect.FAINT);
|
return this.hp <= 0 && (!checkStatus || this.status?.effect === StatusEffect.FAINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this pokemon is both not fainted and allowed to be in battle based on currently active challenges.
|
* Check if this pokemon is both not fainted and allowed to be used based on currently active challenges.
|
||||||
* @returns {boolean} `true` if pokemon is allowed in battle
|
* @returns Whether this Pokemon is allowed to partake in battle.
|
||||||
*/
|
*/
|
||||||
public isAllowedInBattle(): boolean {
|
public isAllowedInBattle(): boolean {
|
||||||
return !this.isFainted() && this.isAllowedInChallenge();
|
return !this.isFainted() && this.isAllowedInChallenge();
|
||||||
@ -534,8 +538,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this pokemon is allowed based on any active challenges.
|
* Check if this pokemon is allowed based on any active challenges.
|
||||||
* It's usually better to call {@linkcode isAllowedInBattle()}
|
* Usually should not be called directly in favor of consulting {@linkcode isAllowedInBattle()}.
|
||||||
* @returns {boolean} `true` if pokemon is allowed in battle
|
* @returns Whether this Pokemon is allowed under the current challenge conditions.
|
||||||
*/
|
*/
|
||||||
public isAllowedInChallenge(): boolean {
|
public isAllowedInChallenge(): boolean {
|
||||||
const challengeAllowed = new BooleanHolder(true);
|
const challengeAllowed = new BooleanHolder(true);
|
||||||
@ -545,8 +549,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if this {@linkcode Pokemon} is allowed in battle (ie: not fainted, and allowed under any active challenges).
|
* Checks if this {@linkcode Pokemon} is allowed in battle (ie: not fainted, and allowed under any active challenges).
|
||||||
* @param onField `true` to also check if the pokemon is currently on the field; default `false`
|
* @param onField - Whether to also check if the pokemon is currently on the field; default `false`
|
||||||
* @returns `true` if the pokemon is "active", as described above.
|
* @returns Whether this pokemon is considered "active", as described above.
|
||||||
* Returns `false` if there is no active {@linkcode BattleScene} or the pokemon is disallowed.
|
* Returns `false` if there is no active {@linkcode BattleScene} or the pokemon is disallowed.
|
||||||
*/
|
*/
|
||||||
public isActive(onField = false): boolean {
|
public isActive(onField = false): boolean {
|
||||||
@ -703,7 +707,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
abstract getBattlerIndex(): BattlerIndex;
|
abstract getBattlerIndex(): BattlerIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the illusion or not.
|
* Load all assets needed for this Pokemon's use in battle
|
||||||
|
* @param ignoreOverride - Whether to ignore overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `true`
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's active illusion; default `false`
|
||||||
|
* @returns A promise that resolves once all the corresponding assets have been loaded.
|
||||||
*/
|
*/
|
||||||
async loadAssets(ignoreOverride = true, useIllusion = false): Promise<void> {
|
async loadAssets(ignoreOverride = true, useIllusion = false): Promise<void> {
|
||||||
/** Promises that are loading assets and can be run concurrently. */
|
/** Promises that are loading assets and can be run concurrently. */
|
||||||
@ -832,11 +839,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to process variant sprite.
|
* Attempt to process variant sprite color caches.
|
||||||
*
|
* @param cacheKey - the cache key for the variant color sprite
|
||||||
* @param cacheKey the cache key for the variant color sprite
|
* @param useExpSprite - Whether experimental sprites should be used if present
|
||||||
* @param useExpSprite should the experimental sprite be used
|
* @param battleSpritePath - the filename of the sprite
|
||||||
* @param battleSpritePath the filename of the sprite
|
|
||||||
*/
|
*/
|
||||||
async populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) {
|
async populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) {
|
||||||
const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`;
|
const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`;
|
||||||
@ -883,8 +889,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return this.fusionSpecies.forms[this.fusionFormIndex].formKey;
|
return this.fusionSpecies.forms[this.fusionFormIndex].formKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSpriteAtlasPath(ignoreOverride?: boolean): string {
|
// TODO: Add more documentation for all these attributes.
|
||||||
|
// They may be all similar, but what each one actually _does_ is quite unclear at first glance
|
||||||
|
|
||||||
|
getSpriteAtlasPath(ignoreOverride = false): string {
|
||||||
const spriteId = this.getSpriteId(ignoreOverride).replace(/_{2}/g, "/");
|
const spriteId = this.getSpriteId(ignoreOverride).replace(/_{2}/g, "/");
|
||||||
|
|
||||||
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1027,10 +1037,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get this {@linkcode Pokemon}'s {@linkcode PokemonSpeciesForm}.
|
* Return this Pokemon's {@linkcode PokemonSpeciesForm | SpeciesForm}.
|
||||||
* @param ignoreOverride - Whether to ignore overridden species from {@linkcode MoveId.TRANSFORM}, default `false`.
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
* This overrides `useIllusion` if `true`.
|
* and overrides `useIllusion`.
|
||||||
* @param useIllusion - `true` to use the speciesForm of the illusion; default `false`.
|
* @param useIllusion - Whether to consider this Pokemon's illusion if present; default `false`.
|
||||||
|
* @returns This Pokemon's {@linkcode PokemonSpeciesForm}.
|
||||||
*/
|
*/
|
||||||
getSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm {
|
getSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm {
|
||||||
if (!ignoreOverride && this.summonData.speciesForm) {
|
if (!ignoreOverride && this.summonData.speciesForm) {
|
||||||
@ -1082,9 +1093,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not.
|
* Return the {@linkcode PokemonSpeciesForm | SpeciesForm} of this Pokemon's fusion counterpart.
|
||||||
|
* @param ignoreOverride - Whether to ignore species overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
|
* @param useIllusion - Whether to consider the species of this Pokemon's illusion; default `false`
|
||||||
|
* @returns The {@linkcode PokemonSpeciesForm} of this Pokemon's fusion counterpart.
|
||||||
*/
|
*/
|
||||||
getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion = false): PokemonSpeciesForm {
|
getFusionSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm {
|
||||||
const fusionSpecies: PokemonSpecies =
|
const fusionSpecies: PokemonSpecies =
|
||||||
useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!;
|
useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!;
|
||||||
const fusionFormIndex =
|
const fusionFormIndex =
|
||||||
@ -1406,17 +1420,19 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Calculates and retrieves the final value of a stat considering any held
|
* Calculates and retrieves the final value of a stat considering any held
|
||||||
* items, move effects, opponent abilities, and whether there was a critical
|
* items, move effects, opponent abilities, and whether there was a critical
|
||||||
* hit.
|
* hit.
|
||||||
* @param stat the desired {@linkcode EffectiveStat}
|
* @param stat - The desired {@linkcode EffectiveStat | Stat} to check.
|
||||||
* @param opponent the target {@linkcode Pokemon}
|
* @param opponent - The {@linkcode Pokemon} being targeted, if applicable.
|
||||||
* @param move the {@linkcode Move} being used
|
* @param move - The {@linkcode Move} being used, if any. Used to check ability ignoring effects and similar.
|
||||||
* @param ignoreAbility determines whether this Pokemon's abilities should be ignored during the stat calculation
|
* @param ignoreAbility - Whether to ignore ability effects of the user; default `false`.
|
||||||
* @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation.
|
* @param ignoreOppAbility - Whether to ignore ability effects of the target; default `false`.
|
||||||
* @param ignoreAllyAbility during an attack, determines whether the ally Pokemon's abilities should be ignored during the stat calculation.
|
* @param ignoreAllyAbility - Whether to ignore ability effects of the user's allies; default `false`.
|
||||||
* @param isCritical determines whether a critical hit has occurred or not (`false` by default)
|
* @param isCritical - Whether a critical hit has occurred or not; default `false`.
|
||||||
* @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering
|
* If `true`, will nullify offensive stat drops or defensive stat boosts.
|
||||||
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
|
* @param simulated - Whether to nullify any effects that produce changes to game state during calculations; default `true`
|
||||||
* @returns the final in-battle value of a stat
|
* @param ignoreHeldItems - Whether to ignore the user's held items during stat calculation; default `false`.
|
||||||
|
* @returns The final in-battle value for the given stat.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Replace the optional parameters with an object to make calling this method less cumbersome
|
||||||
getEffectiveStat(
|
getEffectiveStat(
|
||||||
stat: EffectiveStat,
|
stat: EffectiveStat,
|
||||||
opponent?: Pokemon,
|
opponent?: Pokemon,
|
||||||
@ -1436,6 +1452,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
||||||
const fieldApplied = new BooleanHolder(false);
|
const fieldApplied = new BooleanHolder(false);
|
||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
|
// TODO: remove `canStack` toggle from ability as breaking out renders it useless
|
||||||
applyAbAttrs("FieldMultiplyStatAbAttr", {
|
applyAbAttrs("FieldMultiplyStatAbAttr", {
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
@ -1448,6 +1465,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyAbAttrs("StatMultiplierAbAttr", {
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
pokemon: this,
|
pokemon: this,
|
||||||
@ -1647,9 +1665,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the fake or real gender (illusion ability).
|
* Return this Pokemon's {@linkcode Gender}.
|
||||||
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
|
* @returns the {@linkcode Gender} of this {@linkcode Pokemon}.
|
||||||
*/
|
*/
|
||||||
getGender(ignoreOverride?: boolean, useIllusion = false): Gender {
|
getGender(ignoreOverride = false, useIllusion = false): Gender {
|
||||||
if (useIllusion && this.summonData.illusion) {
|
if (useIllusion && this.summonData.illusion) {
|
||||||
return this.summonData.illusion.gender;
|
return this.summonData.illusion.gender;
|
||||||
}
|
}
|
||||||
@ -1660,9 +1681,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the fake or real gender (illusion ability).
|
* Return this Pokemon's fusion's {@linkcode Gender}.
|
||||||
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
|
* @returns The {@linkcode Gender} of this {@linkcode Pokemon}'s fusion.
|
||||||
*/
|
*/
|
||||||
getFusionGender(ignoreOverride?: boolean, useIllusion = false): Gender {
|
getFusionGender(ignoreOverride = false, useIllusion = false): Gender {
|
||||||
if (useIllusion && this.summonData.illusion?.fusionGender) {
|
if (useIllusion && this.summonData.illusion?.fusionGender) {
|
||||||
return this.summonData.illusion.fusionGender;
|
return this.summonData.illusion.fusionGender;
|
||||||
}
|
}
|
||||||
@ -1673,15 +1697,19 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the fake or real shininess (illusion ability).
|
* Check whether this Pokemon is shiny.
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
|
* @returns Whether this Pokemon is shiny
|
||||||
*/
|
*/
|
||||||
isShiny(useIllusion = false): boolean {
|
isShiny(useIllusion = false): boolean {
|
||||||
if (!useIllusion && this.summonData.illusion) {
|
if (!useIllusion && this.summonData.illusion) {
|
||||||
return !!(
|
return (
|
||||||
this.summonData.illusion.basePokemon?.shiny ||
|
this.summonData.illusion.basePokemon?.shiny ||
|
||||||
(this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny)
|
(this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) ||
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny);
|
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1700,9 +1728,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Check whether this Pokemon is doubly shiny (both normal and fusion are shiny).
|
||||||
* @param useIllusion - Whether we want the fake or real shininess (illusion ability).
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
* @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise
|
* @returns Whether this pokemon's base and fusion counterparts are both shiny.
|
||||||
*/
|
*/
|
||||||
isDoubleShiny(useIllusion = false): boolean {
|
isDoubleShiny(useIllusion = false): boolean {
|
||||||
if (!useIllusion && this.summonData.illusion?.basePokemon) {
|
if (!useIllusion && this.summonData.illusion?.basePokemon) {
|
||||||
@ -1712,11 +1740,15 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.summonData.illusion.basePokemon.fusionShiny
|
this.summonData.illusion.basePokemon.fusionShiny
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.isFusion(useIllusion) && this.shiny && this.fusionShiny;
|
return this.isFusion(useIllusion) && this.shiny && this.fusionShiny;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the fake or real variant (illusion ability).
|
* Return this Pokemon's {@linkcode Variant | shiny variant}.
|
||||||
|
* Only meaningful if this pokemon is actually shiny.
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
|
* @returns The shiny variant of this Pokemon.
|
||||||
*/
|
*/
|
||||||
getVariant(useIllusion = false): Variant {
|
getVariant(useIllusion = false): Variant {
|
||||||
if (!useIllusion && this.summonData.illusion) {
|
if (!useIllusion && this.summonData.illusion) {
|
||||||
@ -1724,9 +1756,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
? this.summonData.illusion.basePokemon!.variant
|
? this.summonData.illusion.basePokemon!.variant
|
||||||
: (Math.max(this.variant, this.fusionVariant) as Variant);
|
: (Math.max(this.variant, this.fusionVariant) as Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !this.isFusion(true) ? this.variant : (Math.max(this.variant, this.fusionVariant) as Variant);
|
return !this.isFusion(true) ? this.variant : (Math.max(this.variant, this.fusionVariant) as Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Clarify how this differs from `getVariant`
|
||||||
getBaseVariant(doubleShiny: boolean): Variant {
|
getBaseVariant(doubleShiny: boolean): Variant {
|
||||||
if (doubleShiny) {
|
if (doubleShiny) {
|
||||||
return this.summonData.illusion?.basePokemon?.variant ?? this.variant;
|
return this.summonData.illusion?.basePokemon?.variant ?? this.variant;
|
||||||
@ -1734,19 +1768,28 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return this.getVariant();
|
return this.getVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this pokemon's overall luck value, based on its shininess (1 pt per variant lvl).
|
||||||
|
* @returns The luck value of this Pokemon.
|
||||||
|
*/
|
||||||
getLuck(): number {
|
getLuck(): number {
|
||||||
return this.luck + (this.isFusion() ? this.fusionLuck : 0);
|
return this.luck + (this.isFusion() ? this.fusionLuck : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether this {@linkcode Pokemon} is currently fused with anything.
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
|
* @returns Whether this Pokemon is currently fused with another species.
|
||||||
|
*/
|
||||||
isFusion(useIllusion = false): boolean {
|
isFusion(useIllusion = false): boolean {
|
||||||
if (useIllusion && this.summonData.illusion) {
|
return useIllusion && this.summonData.illusion ? !!this.summonData.illusion.fusionSpecies : !!this.fusionSpecies;
|
||||||
return !!this.summonData.illusion.fusionSpecies;
|
|
||||||
}
|
|
||||||
return !!this.fusionSpecies;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability).
|
* Return this {@linkcode Pokemon}'s name.
|
||||||
|
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||||
|
* @returns This Pokemon's name.
|
||||||
|
* @see {@linkcode getNameToRender} - gets this Pokemon's display name.
|
||||||
*/
|
*/
|
||||||
getName(useIllusion = false): string {
|
getName(useIllusion = false): string {
|
||||||
return !useIllusion && this.summonData.illusion?.basePokemon
|
return !useIllusion && this.summonData.illusion?.basePokemon
|
||||||
@ -1755,19 +1798,19 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the {@linkcode Pokemon} has a fusion with the specified {@linkcode SpeciesId}.
|
* Check whether this {@linkcode Pokemon} has a fusion with the specified {@linkcode SpeciesId}.
|
||||||
* @param species the pokemon {@linkcode SpeciesId} to check
|
* @param species - The {@linkcode SpeciesId} to check against.
|
||||||
* @returns `true` if the {@linkcode Pokemon} has a fusion with the specified {@linkcode SpeciesId}, `false` otherwise
|
* @returns Whether this Pokemon is currently fused with the specified {@linkcode SpeciesId}.
|
||||||
*/
|
*/
|
||||||
hasFusionSpecies(species: SpeciesId): boolean {
|
hasFusionSpecies(species: SpeciesId): boolean {
|
||||||
return this.fusionSpecies?.speciesId === species;
|
return this.fusionSpecies?.speciesId === species;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the {@linkcode Pokemon} has is the specified {@linkcode SpeciesId} or is fused with it.
|
* Check whether this {@linkcode Pokemon} either is or is fused with the given {@linkcode SpeciesId}.
|
||||||
* @param species the pokemon {@linkcode SpeciesId} to check
|
* @param species - The {@linkcode SpeciesId} to check against.
|
||||||
* @param formKey If provided, requires the species to be in that form
|
* @param formKey - If provided, will require the species to be in the given form.
|
||||||
* @returns `true` if the pokemon is the species or is fused with it, `false` otherwise
|
* @returns Whether this Pokemon has this species as either its base or fusion counterpart.
|
||||||
*/
|
*/
|
||||||
hasSpecies(species: SpeciesId, formKey?: string): boolean {
|
hasSpecies(species: SpeciesId, formKey?: string): boolean {
|
||||||
if (isNullOrUndefined(formKey)) {
|
if (isNullOrUndefined(formKey)) {
|
||||||
@ -1782,7 +1825,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
abstract isBoss(): boolean;
|
abstract isBoss(): boolean;
|
||||||
|
|
||||||
getMoveset(ignoreOverride?: boolean): PokemonMove[] {
|
/**
|
||||||
|
* Return all the {@linkcode PokemonMove}s that make up this Pokemon's moveset.
|
||||||
|
* Takes into account player/enemy moveset overrides (which will also override PP count).
|
||||||
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
|
* @returns An array of {@linkcode PokemonMove}, as described above.
|
||||||
|
*/
|
||||||
|
getMoveset(ignoreOverride = false): PokemonMove[] {
|
||||||
const ret = !ignoreOverride && this.summonData.moveset ? this.summonData.moveset : this.moveset;
|
const ret = !ignoreOverride && this.summonData.moveset ? this.summonData.moveset : this.moveset;
|
||||||
|
|
||||||
// Overrides moveset based on arrays specified in overrides.ts
|
// Overrides moveset based on arrays specified in overrides.ts
|
||||||
@ -1804,11 +1853,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks which egg moves have been unlocked for the {@linkcode Pokemon} based
|
* Check which egg moves have been unlocked for this {@linkcode Pokemon}.
|
||||||
* on the species it was met at or by the first {@linkcode Pokemon} in its evolution
|
* Looks at either the species it was met at or the first {@linkcode Species} in its evolution
|
||||||
* line that can act as a starter and provides those egg moves.
|
* line that can act as a starter and provides those egg moves.
|
||||||
* @returns an array of {@linkcode MoveId}, the length of which is determined by how many
|
* @returns An array of all {@linkcode MoveId}s that are egg moves and unlocked for this Pokemon.
|
||||||
* egg moves are unlocked for that species.
|
|
||||||
*/
|
*/
|
||||||
getUnlockedEggMoves(): MoveId[] {
|
getUnlockedEggMoves(): MoveId[] {
|
||||||
const moves: MoveId[] = [];
|
const moves: MoveId[] = [];
|
||||||
@ -1825,13 +1873,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all possible learnable level moves for the {@linkcode Pokemon},
|
* Get all possible learnable level moves for the {@linkcode Pokemon},
|
||||||
* excluding any moves already known.
|
* excluding any moves already known.
|
||||||
*
|
*
|
||||||
* Available egg moves are only included if the {@linkcode Pokemon} was
|
* Available egg moves are only included if the {@linkcode Pokemon} was
|
||||||
* in the starting party of the run and if Fresh Start is not active.
|
* in the starting party of the run and if Fresh Start is not active.
|
||||||
* @returns an array of {@linkcode MoveId}, the length of which is determined
|
* @returns An array of {@linkcode MoveId}s, as described above.
|
||||||
* by how many learnable moves there are for the {@linkcode Pokemon}.
|
|
||||||
*/
|
*/
|
||||||
public getLearnableLevelMoves(): MoveId[] {
|
public getLearnableLevelMoves(): MoveId[] {
|
||||||
let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]);
|
let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]);
|
||||||
@ -1846,12 +1893,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the types of a pokemon
|
* Evaluate and return this Pokemon's typing.
|
||||||
* @param includeTeraType - `true` to include tera-formed type; Default: `false`
|
* @param includeTeraType - Whether to use this Pokemon's tera type if Terastallized; default `false`
|
||||||
* @param forDefend - `true` if the pokemon is defending from an attack; Default: `false`
|
* @param forDefend - Whether this Pokemon is currently receiving an attack; default `false`
|
||||||
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false`
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
* @param useIllusion - `true` to return the types of the illusion instead of the actual types; Default: `false`
|
* @param useIllusion - Whether to consider this Pokemon's illusion if present; default `false`
|
||||||
* @returns array of {@linkcode PokemonType}
|
* @returns An array of {@linkcode PokemonType}s corresponding to this Pokemon's typing (real or percieved).
|
||||||
*/
|
*/
|
||||||
public getTypes(
|
public getTypes(
|
||||||
includeTeraType = false,
|
includeTeraType = false,
|
||||||
@ -1947,7 +1994,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove UNKNOWN if other types are present
|
// remove UNKNOWN if other types are present
|
||||||
if (types.length > 1 && types.includes(PokemonType.UNKNOWN)) {
|
if (types.length > 1) {
|
||||||
const index = types.indexOf(PokemonType.UNKNOWN);
|
const index = types.indexOf(PokemonType.UNKNOWN);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
types.splice(index, 1);
|
types.splice(index, 1);
|
||||||
@ -1968,24 +2015,25 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the pokemon's typing includes the specified type
|
* Check if this Pokemon's typing includes the specified type.
|
||||||
* @param type - {@linkcode PokemonType} to check
|
* @param type - The {@linkcode PokemonType} to check
|
||||||
* @param includeTeraType - `true` to include tera-formed type; Default: `true`
|
* @param includeTeraType - Whether to use this Pokemon's tera type if Terastallized; default `true`
|
||||||
* @param forDefend - `true` if the pokemon is defending from an attack; Default: `false`
|
* @param forDefend - Whether this Pokemon is currently receiving an attack; default `false`
|
||||||
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false`
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
* @returns `true` if the Pokemon's type matches
|
* @returns Whether this Pokemon is of the specified type.
|
||||||
*/
|
*/
|
||||||
public isOfType(type: PokemonType, includeTeraType = true, forDefend = false, ignoreOverride = false): boolean {
|
public isOfType(type: PokemonType, includeTeraType = true, forDefend = false, ignoreOverride = false): boolean {
|
||||||
return this.getTypes(includeTeraType, forDefend, ignoreOverride).some(t => t === type);
|
return this.getTypes(includeTeraType, forDefend, ignoreOverride).includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the non-passive ability of the pokemon. This accounts for fusions and ability changing effects.
|
* Get this Pokemon's non-passive {@linkcode Ability}, factoring in fusions, overrides and ability-changing effects.
|
||||||
* This should rarely be called, most of the time {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr} are better used as
|
|
||||||
* those check both the passive and non-passive abilities and account for ability suppression.
|
* Should rarely be called directly in favor of {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr},
|
||||||
* @see {@linkcode hasAbility} {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases
|
* both of which check both ability slots and account for suppression.
|
||||||
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false`
|
* @see {@linkcode hasAbility} and {@linkcode hasAbilityWithAttr} are the intended ways to check abilities in most cases
|
||||||
* @returns The non-passive {@linkcode Ability} of the pokemon
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
|
* @returns The non-passive {@linkcode Ability} of this Pokemon.
|
||||||
*/
|
*/
|
||||||
public getAbility(ignoreOverride = false): Ability {
|
public getAbility(ignoreOverride = false): Ability {
|
||||||
if (!ignoreOverride && this.summonData.ability) {
|
if (!ignoreOverride && this.summonData.ability) {
|
||||||
@ -2131,7 +2179,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (passive && !this.hasPassive()) {
|
if (passive && !this.hasPassive()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const ability = !passive ? this.getAbility() : this.getPassiveAbility();
|
const ability = passive ? this.getPassiveAbility() : this.getAbility();
|
||||||
if (this.isFusion() && ability.hasAttr("NoFusionAbilityAbAttr")) {
|
if (this.isFusion() && ability.hasAttr("NoFusionAbilityAbAttr")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2144,7 +2192,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
|
const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
|
||||||
const suppressOffField = ability.hasAttr("PreSummonAbAttr");
|
const suppressOffField = ability.hasAttr("PreSummonAbAttr");
|
||||||
if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) {
|
if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.beingRemoved) {
|
||||||
const thisAbilitySuppressing = ability.hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr");
|
const thisAbilitySuppressing = ability.hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr");
|
||||||
const hasSuppressingAbility = this.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false);
|
const hasSuppressingAbility = this.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false);
|
||||||
// Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas
|
// Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas
|
||||||
@ -2162,13 +2210,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a pokemon has the specified ability and it's in effect. Accounts for all the various
|
* Check whether a pokemon has the specified ability in effect, either as a normal or passive ability.
|
||||||
* effects which can affect whether an ability will be present or in effect, and both passive and
|
* Accounts for all the various effects which can disable or modify abilities.
|
||||||
* non-passive. This is the primary way to check whether a pokemon has a particular ability.
|
* @param ability - The {@linkcode Abilities | Ability} to check for
|
||||||
* @param ability The ability to check for
|
|
||||||
* @param canApply - Whether to check if the ability is currently active; default `true`
|
* @param canApply - Whether to check if the ability is currently active; default `true`
|
||||||
* @param ignoreOverride Whether to ignore ability changing effects; default `false`
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
* @returns `true` if the ability is present and active
|
* @returns Whether this {@linkcode Pokemon} has the given ability
|
||||||
*/
|
*/
|
||||||
public hasAbility(ability: AbilityId, canApply = true, ignoreOverride = false): boolean {
|
public hasAbility(ability: AbilityId, canApply = true, ignoreOverride = false): boolean {
|
||||||
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
|
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
|
||||||
@ -2178,14 +2225,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a pokemon has an ability with the specified attribute and it's in effect.
|
* Check whether this pokemon has an ability with the specified attribute in effect, either as a normal or passive ability.
|
||||||
* Accounts for all the various effects which can affect whether an ability will be present or
|
* Accounts for all the various effects which can disable or modify abilities.
|
||||||
* in effect, and both passive and non-passive. This is one of the two primary ways to check
|
* @param attrType - The {@linkcode AbAttr | attribute} to check for
|
||||||
* whether a pokemon has a particular ability.
|
|
||||||
* @param attrType The {@link AbAttr | ability attribute} to check for
|
|
||||||
* @param canApply - Whether to check if the ability is currently active; default `true`
|
* @param canApply - Whether to check if the ability is currently active; default `true`
|
||||||
* @param ignoreOverride Whether to ignore ability changing effects; default `false`
|
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
|
||||||
* @returns `true` if an ability with the given {@linkcode AbAttr} is present and active
|
* @returns Whether this Pokemon has an ability with the given {@linkcode AbAttr}.
|
||||||
*/
|
*/
|
||||||
public hasAbilityWithAttr(attrType: AbAttrString, canApply = true, ignoreOverride = false): boolean {
|
public hasAbilityWithAttr(attrType: AbAttrString, canApply = true, ignoreOverride = false): boolean {
|
||||||
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
|
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
|
||||||
@ -2207,7 +2252,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const autotomizedTag = this.getTag(AutotomizedTag);
|
const autotomizedTag = this.getTag(AutotomizedTag);
|
||||||
let weightRemoved = 0;
|
let weightRemoved = 0;
|
||||||
if (!isNullOrUndefined(autotomizedTag)) {
|
if (!isNullOrUndefined(autotomizedTag)) {
|
||||||
weightRemoved = 100 * autotomizedTag!.autotomizeCount;
|
weightRemoved = 100 * autotomizedTag.autotomizeCount;
|
||||||
}
|
}
|
||||||
const minWeight = 0.1;
|
const minWeight = 0.1;
|
||||||
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
||||||
@ -3403,10 +3448,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
*
|
*
|
||||||
* Note that this does not apply to evasion or accuracy
|
* Note that this does not apply to evasion or accuracy
|
||||||
* @see {@linkcode getAccuracyMultiplier}
|
* @see {@linkcode getAccuracyMultiplier}
|
||||||
* @param stat the desired {@linkcode EffectiveStat}
|
* @param stat - The {@linkcode EffectiveStat} to calculate
|
||||||
* @param opponent the target {@linkcode Pokemon}
|
* @param opponent - The {@linkcode Pokemon} being targeted
|
||||||
* @param move the {@linkcode Move} being used
|
* @param move - The {@linkcode Move} being used
|
||||||
* @param ignoreOppAbility determines whether the effects of the opponent's abilities (i.e. Unaware) should be ignored (`false` by default)
|
* @param ignoreOppAbility determines whether the effects of the opponent's abilities (i.e. Unaware) should be ignored (`false` by default)
|
||||||
* @param isCritical determines whether a critical hit has occurred or not (`false` by default)
|
* @param isCritical determines whether a critical hit has occurred or not (`false` by default)
|
||||||
* @param simulated determines whether effects are applied without altering game state (`true` by default)
|
* @param simulated determines whether effects are applied without altering game state (`true` by default)
|
||||||
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
|
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
|
||||||
@ -4395,6 +4440,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this Pokemon's move history.
|
||||||
|
* Entries are sorted in order of OLDEST to NEWEST
|
||||||
|
* @returns An array of {@linkcode TurnMove}, as described above.
|
||||||
|
* @see {@linkcode getLastXMoves}
|
||||||
|
*/
|
||||||
public getMoveHistory(): TurnMove[] {
|
public getMoveHistory(): TurnMove[] {
|
||||||
return this.summonData.moveHistory;
|
return this.summonData.moveHistory;
|
||||||
}
|
}
|
||||||
@ -4408,19 +4459,20 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of the most recent move entries in this Pokemon's move history.
|
* Return a list of the most recent move entries in this {@linkcode Pokemon}'s move history.
|
||||||
* The retrieved move entries are sorted in order from NEWEST to OLDEST.
|
* The retrieved move entries are sorted in order from **NEWEST** to **OLDEST**.
|
||||||
* @param moveCount The number of move entries to retrieve.
|
* @param moveCount - The maximum number of move entries to retrieve.
|
||||||
* If negative, retrieve the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}).
|
* If negative, retrieves the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}).
|
||||||
* Default is `1`.
|
* Default is `1`.
|
||||||
* @returns A list of {@linkcode TurnMove}, as specified above.
|
* @returns An array of {@linkcode TurnMove}, as specified above.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Update documentation in dancer PR to mention "getLastNonVirtualMove"
|
||||||
getLastXMoves(moveCount = 1): TurnMove[] {
|
getLastXMoves(moveCount = 1): TurnMove[] {
|
||||||
const moveHistory = this.getMoveHistory();
|
const moveHistory = this.getMoveHistory();
|
||||||
if (moveCount >= 0) {
|
if (moveCount > 0) {
|
||||||
return moveHistory.slice(Math.max(moveHistory.length - moveCount, 0)).reverse();
|
return moveHistory.slice(Math.max(moveHistory.length - moveCount, 0)).reverse();
|
||||||
}
|
}
|
||||||
return moveHistory.slice(0).reverse();
|
return moveHistory.slice().reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -5576,13 +5628,15 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces one of this Pokemon's held item stacks by 1, and removes the item if applicable.
|
* Reduces one of this Pokemon's held item stacks by 1, removing it if applicable.
|
||||||
* Does nothing if this Pokemon is somehow not the owner of the held item.
|
* Does nothing if this Pokemon is somehow not the owner of the held item.
|
||||||
* @param heldItem The item stack to be reduced by 1.
|
* @param heldItem - The item stack to be reduced.
|
||||||
* @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`.
|
* @param forBattle - Whether to trigger in-battle effects (such as Unburden) after losing the item. Default: `true`
|
||||||
* @returns `true` if the item was removed successfully, `false` otherwise.
|
* Should be `false` for all item loss occurring outside of battle (MEs, etc.).
|
||||||
|
* @returns Whether the item was removed successfully.
|
||||||
*/
|
*/
|
||||||
public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean {
|
public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean {
|
||||||
|
// TODO: What does a -1 pokemon id mean?
|
||||||
if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) {
|
if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6254,22 +6308,23 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the pokemons boss status. If true initializes the boss segments either from the arguments
|
* Set this {@linkcode EnemyPokemon}'s boss status.
|
||||||
* or through the the Scene.getEncounterBossSegments function
|
|
||||||
*
|
*
|
||||||
* @param boss if the pokemon is a boss
|
* @param boss - Whether this pokemon should be a boss; default `true`
|
||||||
* @param bossSegments amount of boss segments (health-bar segments)
|
* @param bossSegments - Optional amount amount of health bar segments to give;
|
||||||
|
* will be generated by {@linkcode BattleScene.getEncounterBossSegments} if omitted
|
||||||
*/
|
*/
|
||||||
setBoss(boss = true, bossSegments = 0): void {
|
setBoss(boss = true, bossSegments?: number): void {
|
||||||
if (boss) {
|
if (!boss) {
|
||||||
this.bossSegments =
|
|
||||||
bossSegments ||
|
|
||||||
globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, this.level, this.species, true);
|
|
||||||
this.bossSegmentIndex = this.bossSegments - 1;
|
|
||||||
} else {
|
|
||||||
this.bossSegments = 0;
|
this.bossSegments = 0;
|
||||||
this.bossSegmentIndex = 0;
|
this.bossSegmentIndex = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.bossSegments =
|
||||||
|
bossSegments ??
|
||||||
|
globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, this.level, this.species, true);
|
||||||
|
this.bossSegmentIndex = this.bossSegments - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAndPopulateMoveset(formIndex?: number): void {
|
generateAndPopulateMoveset(formIndex?: number): void {
|
||||||
|
13
src/main.ts
13
src/main.ts
@ -45,8 +45,10 @@ Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative
|
|||||||
document.fonts.load("16px emerald").then(() => document.fonts.load("10px pkmnems"));
|
document.fonts.load("16px emerald").then(() => document.fonts.load("10px pkmnems"));
|
||||||
// biome-ignore lint/suspicious/noImplicitAnyLet: TODO
|
// biome-ignore lint/suspicious/noImplicitAnyLet: TODO
|
||||||
let game;
|
let game;
|
||||||
|
// biome-ignore lint/suspicious/noImplicitAnyLet: TODO
|
||||||
|
let manifest;
|
||||||
|
|
||||||
const startGame = async (manifest?: any) => {
|
const startGame = async () => {
|
||||||
await initI18n();
|
await initI18n();
|
||||||
const LoadingScene = (await import("./loading-scene")).LoadingScene;
|
const LoadingScene = (await import("./loading-scene")).LoadingScene;
|
||||||
const BattleScene = (await import("./battle-scene")).BattleScene;
|
const BattleScene = (await import("./battle-scene")).BattleScene;
|
||||||
@ -110,10 +112,13 @@ const startGame = async (manifest?: any) => {
|
|||||||
fetch("/manifest.json")
|
fetch("/manifest.json")
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(jsonResponse => {
|
.then(jsonResponse => {
|
||||||
startGame(jsonResponse.manifest);
|
manifest = jsonResponse.manifest;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
// Manifest not found (likely local build)
|
// Manifest not found (likely local build or path error on live)
|
||||||
|
console.log(`Manifest not found. ${err}`);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
startGame();
|
startGame();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -52,25 +52,11 @@ const iconOverflowIndex = 24;
|
|||||||
export const modifierSortFunc = (a: Modifier, b: Modifier): number => {
|
export const modifierSortFunc = (a: Modifier, b: Modifier): number => {
|
||||||
const itemNameMatch = a.type.name.localeCompare(b.type.name);
|
const itemNameMatch = a.type.name.localeCompare(b.type.name);
|
||||||
const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name);
|
const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name);
|
||||||
const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295;
|
const aId = a instanceof PokemonHeldItemModifier ? a.pokemonId : -1;
|
||||||
const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295;
|
const bId = b instanceof PokemonHeldItemModifier ? b.pokemonId : -1;
|
||||||
|
|
||||||
//First sort by pokemonID
|
// First sort by pokemon ID, then by item type and then name
|
||||||
if (aId < bId) {
|
return aId - bId || typeNameMatch || itemNameMatch;
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (aId > bId) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (aId === bId) {
|
|
||||||
//Then sort by item type
|
|
||||||
if (typeNameMatch === 0) {
|
|
||||||
return itemNameMatch;
|
|
||||||
//Finally sort by item name
|
|
||||||
}
|
|
||||||
return typeNameMatch;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ModifierBar extends Phaser.GameObjects.Container {
|
export class ModifierBar extends Phaser.GameObjects.Container {
|
||||||
@ -757,7 +743,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxStackCount(forThreshold?: boolean): number {
|
getMaxStackCount(forThreshold = false): number {
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (!pokemon) {
|
if (!pokemon) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -2814,6 +2800,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
|||||||
damageMultiplier.value *= 1 - 0.25 * this.getStackCount();
|
damageMultiplier.value *= 1 - 0.25 * this.getStackCount();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) {
|
if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) {
|
||||||
// Deal 25% damage for each remaining Multi Lens hit
|
// Deal 25% damage for each remaining Multi Lens hit
|
||||||
damageMultiplier.value *= 0.25;
|
damageMultiplier.value *= 0.25;
|
||||||
|
@ -562,14 +562,13 @@ export class PhaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues an ability bar flyout phase
|
* Queue a phase to show or hide the ability flyout bar.
|
||||||
* @param pokemon The pokemon who has the ability
|
* @param pokemon - The {@linkcode Pokemon} whose ability is being activated
|
||||||
* @param passive Whether the ability is a passive
|
* @param passive - Whether the ability is a passive
|
||||||
* @param show Whether to show or hide the bar
|
* @param show - Whether to show or hide the bar
|
||||||
*/
|
*/
|
||||||
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
|
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
|
||||||
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
|
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
|
||||||
this.clearPhaseQueueSplice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,7 +88,7 @@ export class CommandPhase extends FieldPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP.
|
// Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP.
|
||||||
const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag;
|
const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag | undefined;
|
||||||
if (encoreTag) {
|
if (encoreTag) {
|
||||||
this.getPokemon().lapseTag(BattlerTagType.ENCORE);
|
this.getPokemon().lapseTag(BattlerTagType.ENCORE);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ export class SelectStarterPhase extends Phase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize starters before starting the first battle
|
* Initialize starters before starting the first battle
|
||||||
* @param starters {@linkcode Pokemon} with which to start the first battle
|
* @param starters - Array of {@linkcode Starter}s with which to start the battle
|
||||||
*/
|
*/
|
||||||
initBattle(starters: Starter[]) {
|
initBattle(starters: Starter[]) {
|
||||||
const party = globalScene.getPlayerParty();
|
const party = globalScene.getPlayerParty();
|
||||||
|
@ -204,7 +204,7 @@ export class TitlePhase extends Phase {
|
|||||||
globalScene.eventManager.startEventChallenges();
|
globalScene.eventManager.startEventChallenges();
|
||||||
|
|
||||||
globalScene.setSeed(seed);
|
globalScene.setSeed(seed);
|
||||||
globalScene.resetSeed(0);
|
globalScene.resetSeed();
|
||||||
|
|
||||||
globalScene.money = globalScene.gameMode.getStartingMoney();
|
globalScene.money = globalScene.gameMode.getStartingMoney();
|
||||||
|
|
||||||
@ -283,6 +283,7 @@ export class TitlePhase extends Phase {
|
|||||||
console.error("Failed to load daily run:\n", err);
|
console.error("Failed to load daily run:\n", err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Grab first 10 chars of ISO date format (YYYY-MM-DD) and convert to base64
|
||||||
let seed: string = btoa(new Date().toISOString().substring(0, 10));
|
let seed: string = btoa(new Date().toISOString().substring(0, 10));
|
||||||
if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) {
|
if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) {
|
||||||
seed = Overrides.DAILY_RUN_SEED_OVERRIDE;
|
seed = Overrides.DAILY_RUN_SEED_OVERRIDE;
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import type { ArenaTag } from "#data/arena-tag";
|
import type { ArenaTag } from "#data/arena-tag";
|
||||||
import { loadArenaTag } from "#data/arena-tag";
|
import { loadArenaTag, SerializableArenaTag } from "#data/arena-tag";
|
||||||
import type { SerializedPositionalTag } from "#data/positional-tags/load-positional-tag";
|
import type { SerializedPositionalTag } from "#data/positional-tags/load-positional-tag";
|
||||||
import { Terrain } from "#data/terrain";
|
import { Terrain } from "#data/terrain";
|
||||||
import { Weather } from "#data/weather";
|
import { Weather } from "#data/weather";
|
||||||
import type { BiomeId } from "#enums/biome-id";
|
import type { BiomeId } from "#enums/biome-id";
|
||||||
import { Arena } from "#field/arena";
|
import { Arena } from "#field/arena";
|
||||||
|
import type { ArenaTagTypeData } from "#types/arena-tags";
|
||||||
|
import type { NonFunctionProperties } from "#types/type-helpers";
|
||||||
|
|
||||||
|
export interface SerializedArenaData {
|
||||||
|
biome: BiomeId;
|
||||||
|
weather: NonFunctionProperties<Weather> | null;
|
||||||
|
terrain: NonFunctionProperties<Terrain> | null;
|
||||||
|
tags?: ArenaTagTypeData[];
|
||||||
|
playerTerasUsed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class ArenaData {
|
export class ArenaData {
|
||||||
public biome: BiomeId;
|
public biome: BiomeId;
|
||||||
@ -14,26 +24,27 @@ export class ArenaData {
|
|||||||
public positionalTags: SerializedPositionalTag[] = [];
|
public positionalTags: SerializedPositionalTag[] = [];
|
||||||
public playerTerasUsed: number;
|
public playerTerasUsed: number;
|
||||||
|
|
||||||
constructor(source: Arena | any) {
|
constructor(source: Arena | SerializedArenaData) {
|
||||||
const sourceArena = source instanceof Arena ? (source as Arena) : null;
|
// Exclude any unserializable tags from the serialized data (such as ones only lasting 1 turn).
|
||||||
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
// NOTE: The filter has to be done _after_ map, data loaded from `ArenaTagTypeData`
|
||||||
this.weather = sourceArena
|
// is not yet an instance of `ArenaTag`
|
||||||
? sourceArena.weather
|
this.tags =
|
||||||
: source.weather
|
source.tags
|
||||||
? new Weather(source.weather.weatherType, source.weather.turnsLeft)
|
?.map((t: ArenaTag | ArenaTagTypeData) => loadArenaTag(t))
|
||||||
: null;
|
?.filter((tag): tag is SerializableArenaTag => tag instanceof SerializableArenaTag) ?? [];
|
||||||
this.terrain = sourceArena
|
|
||||||
? sourceArena.terrain
|
|
||||||
: source.terrain
|
|
||||||
? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft)
|
|
||||||
: null;
|
|
||||||
this.playerTerasUsed = (sourceArena ? sourceArena.playerTerasUsed : source.playerTerasUsed) ?? 0;
|
|
||||||
this.tags = [];
|
|
||||||
|
|
||||||
if (source.tags) {
|
this.playerTerasUsed = source.playerTerasUsed ?? 0;
|
||||||
this.tags = source.tags.map(t => loadArenaTag(t));
|
this.positionalTags = (sourceArena ? sourceArena.positionalTagManager.tags : source.positionalTags) ?? [];
|
||||||
|
|
||||||
|
if (source instanceof Arena) {
|
||||||
|
this.biome = source.biomeType;
|
||||||
|
this.weather = source.weather;
|
||||||
|
this.terrain = source.terrain;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.positionalTags = (sourceArena ? sourceArena.positionalTagManager.tags : source.positionalTags) ?? [];
|
this.biome = source.biome;
|
||||||
|
this.weather = source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
|
||||||
|
this.terrain = source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ import * as Modifier from "#modifiers/modifier";
|
|||||||
import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data";
|
import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data";
|
||||||
import type { Variant } from "#sprites/variant";
|
import type { Variant } from "#sprites/variant";
|
||||||
import { achvs } from "#system/achv";
|
import { achvs } from "#system/achv";
|
||||||
import { ArenaData } from "#system/arena-data";
|
import { ArenaData, type SerializedArenaData } from "#system/arena-data";
|
||||||
import { ChallengeData } from "#system/challenge-data";
|
import { ChallengeData } from "#system/challenge-data";
|
||||||
import { EggData } from "#system/egg-data";
|
import { EggData } from "#system/egg-data";
|
||||||
import { GameStats } from "#system/game-stats";
|
import { GameStats } from "#system/game-stats";
|
||||||
@ -1252,7 +1252,8 @@ export class GameData {
|
|||||||
// (or prevent them from being null)
|
// (or prevent them from being null)
|
||||||
// If the value is able to *not exist*, it should say so in the code
|
// If the value is able to *not exist*, it should say so in the code
|
||||||
const sessionData = JSON.parse(dataStr, (k: string, v: any) => {
|
const sessionData = JSON.parse(dataStr, (k: string, v: any) => {
|
||||||
// TODO: Add pre-parse migrate scripts
|
// TODO: Move this to occur _after_ migrate scripts (and refactor all non-assignment duties into migrate scripts)
|
||||||
|
// This should ideally be just a giant assign block
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case "party":
|
case "party":
|
||||||
case "enemyParty": {
|
case "enemyParty": {
|
||||||
@ -1290,7 +1291,7 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "arena":
|
case "arena":
|
||||||
return new ArenaData(v);
|
return new ArenaData(v as SerializedArenaData);
|
||||||
|
|
||||||
case "challenges": {
|
case "challenges": {
|
||||||
const ret: ChallengeData[] = [];
|
const ret: ChallengeData[] = [];
|
||||||
|
Loading…
Reference in New Issue
Block a user