[Misc/Docs] Assorted code cleanups + doc updates (#5745)

* Squashed changes into 1 commit, reverted unneeded stuff

* Update ability-class.ts comments

* Update move.ts comments

* Fixed flaky test

* Applied PR reviews

* Update move.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Fixed ab code

* Added comment for BattlerIndex

* ddd

* ren biome

* Update battler-tags.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update battler-tags.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update pokemon.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update pokemon.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update pokemon.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Fixed the things

* Fixed up a few `default` stuff and random enum stuff

* Update move.ts comments

* Revert change to pokemon.ts

* Update battle-scene.ts

* fixed import oopsie

* Update src/field/pokemon.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update src/data/abilities/ability.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update src/data/abilities/ability.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Fix incorrect TSDoc

* Update ability.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Bertie690 2025-07-22 13:28:10 -04:00 committed by GitHub
parent 56e3402c81
commit 6937effa16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 527 additions and 360 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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();
} }
} }

View File

@ -22,6 +22,8 @@ import type { Pokemon } from "#field/pokemon";
import { BooleanHolder, NumberHolder, toDmgValue } from "#utils/common"; import { BooleanHolder, NumberHolder, toDmgValue } from "#utils/common";
import i18next from "i18next"; import i18next from "i18next";
// TODO: Add a class for tags that explicitly REQUIRE a source move (as currently we have a lot of bangs)
export abstract class ArenaTag { export abstract class ArenaTag {
constructor( constructor(
public tagType: ArenaTagType, public tagType: ArenaTagType,
@ -50,8 +52,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 {

View File

@ -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.

View File

@ -166,9 +166,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];
@ -179,9 +179,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];
@ -193,23 +193,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) {
@ -223,11 +225,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);
@ -244,8 +248,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;
@ -253,13 +257,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;
} }
/** /**
@ -304,13 +308,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) {
@ -324,7 +328,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;
@ -334,9 +338,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)
@ -354,13 +358,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);
@ -368,16 +373,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)";
@ -385,8 +396,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)";
@ -961,10 +974,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));
} }
@ -1220,7 +1231,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}
*/ */
@ -1238,8 +1250,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;
@ -1248,7 +1259,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;
@ -1257,7 +1268,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;
@ -1266,7 +1277,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;
@ -2570,9 +2581,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);
@ -2905,6 +2919,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) {
@ -2922,7 +2942,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;
} }
} }
@ -3253,7 +3273,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;
@ -5416,7 +5436,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) {
@ -7055,7 +7078,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;

View File

@ -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

View File

@ -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()) {

View File

@ -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,

View File

@ -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 {
/** Display option to switch active pokemon at battle start. */
SWITCH, SWITCH,
/** Hide option to switch active pokemon at battle start. */
SET SET
} }

View File

@ -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,

View File

@ -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,

View File

@ -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 {
/** Display amount flyout for all off-field party members upon gaining any amount of EXP. */
DEFAULT, DEFAULT,
/** Display smaller flyout showing level gained on gaining a new level. */
ONLY_LEVEL_UP, ONLY_LEVEL_UP,
/** Do not show any flyouts for EXP gains or levelups. */
SKIP SKIP
} }

View File

@ -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;
} }
@ -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,9 +3448,9 @@ 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)
@ -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 {

View File

@ -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;

View File

@ -560,14 +560,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();
} }
/** /**

View File

@ -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);
} }

View File

@ -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();

View File

@ -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;

View File

@ -1247,7 +1247,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": {