From d3414b7126ff04a5cc5f5b0f11717a556b5cad86 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Tue, 20 May 2025 12:30:21 -0400 Subject: [PATCH 01/10] Squashed changes into 1 commit, reverted unneeded stuff --- src/battle-scene.ts | 37 +- src/data/abilities/ability-class.ts | 18 +- src/data/abilities/ability.ts | 187 ++++---- src/data/arena-tag.ts | 14 +- src/data/battler-tags.ts | 11 +- src/data/moves/move.ts | 74 ++-- .../utils/encounter-pokemon-utils.ts | 2 +- src/enums/MoveFlags.ts | 4 + src/field/arena.ts | 2 + src/field/pokemon.ts | 410 ++++++++++-------- src/modifier/modifier.ts | 5 +- src/phases/command-phase.ts | 3 +- src/phases/select-starter-phase.ts | 3 +- src/phases/title-phase.ts | 3 +- src/system/game-data.ts | 3 +- src/ui/fight-ui-handler.ts | 4 +- 16 files changed, 465 insertions(+), 315 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5835ee08af5..67bd47d3b5b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -874,7 +874,8 @@ export default class BattleScene extends SceneBase { /** * 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. - * @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 elide all `null` values from the array. * @returns An array of {@linkcode Pokemon}, as described above. */ public getField(activeOnly = false): Pokemon[] { @@ -887,9 +888,9 @@ export default 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 - * @param removedPokemon {@linkcode Pokemon} the pokemon that is being removed from the field (flee, faint), moves to be redirected FROM - * @param allyPokemon {@linkcode Pokemon} the pokemon that will have the moves be redirected TO + * Attempt to redirect a move in double battles from a fainted/removed Pokemon to its ally. + * @param removedPokemon - The {@linkcode Pokemon} having been removed from the field. + * @param allyPokemon - The {@linkcode Pokemon} allied with the removed Pokemon; will have moves redirected to it */ redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void { // failsafe: if not a double battle just return @@ -915,10 +916,10 @@ export default class BattleScene extends SceneBase { /** * 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 - * @returns {ModifierBar} + * @param isEnemy - Whether to return the enemy modifier bar instead of the player bar; default `false` + * @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; } @@ -933,8 +934,7 @@ export default class BattleScene extends SceneBase { } getPokemonById(pokemonId: number): Pokemon | null { - const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); - return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null; + return (this.getPlayerParty() as Pokemon[]).concat(this.getEnemyParty()).find(p => p.id === pokemonId) ?? null; } addPlayerPokemon( @@ -1472,10 +1472,12 @@ export default class BattleScene extends SceneBase { if (!waveIndex && lastBattle) { const isNewBiome = this.isNewBiome(lastBattle); + /** Whether to reset and recall pokemon */ const resetArenaState = isNewBiome || [BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS; + for (const enemyPokemon of this.getEnemyParty()) { enemyPokemon.destroy(); } @@ -1847,7 +1849,7 @@ export default class BattleScene extends SceneBase { } resetSeed(waveIndex?: number): void { - const wave = waveIndex || this.currentBattle?.waveIndex || 0; + const wave = waveIndex ?? this.currentBattle?.waveIndex ?? 0; this.waveSeed = shiftCharCodes(this.seed, wave); Phaser.Math.RND.sow([this.waveSeed]); console.log("Wave Seed:", this.waveSeed, wave); @@ -2861,7 +2863,7 @@ export default class BattleScene extends SceneBase { // adds to the end of PhaseQueuePrepend this.unshiftPhase(phase); } else { - //remember that pushPhase adds it to nextCommandPhaseQueue + // remember that pushPhase adds it to nextCommandPhaseQueue this.pushPhase(phase); } } @@ -2913,6 +2915,7 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } + // TODO: refactor this addModifier( modifier: Modifier | null, ignoreUpdate?: boolean, @@ -3353,18 +3356,18 @@ export default class BattleScene extends SceneBase { /** * Get all of the modifiers that pass the `modifierFilter` function - * @param modifierFilter The function used to filter a target's modifiers - * @param isPlayer Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` - * @returns the list of all modifiers that passed the `modifierFilter` function + * @param modifierFilter - The function used to filter a target's modifiers + * @param isPlayer - Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @returns an array of {@linkcode PersistentModifier}s that passed the `modifierFilter` function */ findModifiers(modifierFilter: ModifierPredicate, isPlayer = true): PersistentModifier[] { return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter); } /** - * Find the first modifier that pass the `modifierFilter` function - * @param modifierFilter The function used to filter a target's modifiers - * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * Find the first modifier that passes the `modifierFilter` function + * @param modifierFilter - The function used to filter a target's modifiers + * @param player - Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @returns the first modifier that passed the `modifierFilter` function; `undefined` if none passed */ findModifier(modifierFilter: ModifierPredicate, player = true): PersistentModifier | undefined { diff --git a/src/data/abilities/ability-class.ts b/src/data/abilities/ability-class.ts index 387c5fb328b..8a284bac264 100644 --- a/src/data/abilities/ability-class.ts +++ b/src/data/abilities/ability-class.ts @@ -117,19 +117,33 @@ export class Ability implements Localizable { return this; } + /** + * Mark an ability as partially implemented. + * Partial abilities are expected to have their core functionality implemented, but may lack + * certain notable features or interactions with other moves or abilities. + * @returns `this` + */ partial(): this { this.nameAppend += " (P)"; return this; } + /** + * Mark an ability as unimplemented. + * Unimplemented abilities are ones which have _none_ of their basic functionality enabled. + * @returns `this` + */ unimplemented(): this { this.nameAppend += " (N)"; return this; } /** - * Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case. - * @returns the ability + * Mark an ability as having an edge case. + * 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 { return this; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index ff86937622b..c3418f9e8bc 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -361,7 +361,6 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { * @param move {@linkcode Move} The attacking move. * @param cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. * @param args [0] {@linkcode NumberHolder} gets set to 0 if move is immuned by an ability. - * @param args [1] - Whether the move is simulated. */ override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { (args[0] as NumberHolder).value = 0; @@ -1203,6 +1202,7 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr { export class FieldMultiplyStatAbAttr extends AbAttr { private stat: Stat; private multiplier: number; + /** Whether this ability can stack with others of the same type for this stat; default `false` */ private canStack: boolean; constructor(stat: Stat, multiplier: number, canStack = false) { @@ -1213,13 +1213,13 @@ export class FieldMultiplyStatAbAttr extends AbAttr { this.canStack = canStack; } - canApplyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, args: any[]): boolean { - return this.canStack || !hasApplied.value + canApplyFieldStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: Stat, _statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, _args: any[]): boolean { + return (this.canStack || !hasApplied.value) && this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat); } /** - * applyFieldStat: Tries to multiply a Pokemon's Stat + * Tries to multiply a Pokemon's Stat. * @param pokemon {@linkcode Pokemon} the Pokemon using this ability * @param passive {@linkcode boolean} unused * @param stat {@linkcode Stat} the type of the checked stat @@ -1245,7 +1245,7 @@ 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: * - The ability's condition is met, e.g. pixilate only boosts normal moves, @@ -1255,27 +1255,32 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move * @param _passive - Unused * @param _simulated - Unused - * @param _defender - The pokemon being attacked (unused) - * @param move - The move being used - * @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier + * @param _defender - Unused + * @param move - The {@linkcode Move} being used + * @param _args - Unused * @returns whether the move type change attribute can be applied */ - override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean { + // TODO: Why can these be nullish? + override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?]): boolean { return (!this.condition || this.condition(pokemon, _defender, move)) && - !noAbilityTypeOverrideMoves.has(move.id) && + !noAbilityTypeOverrideMoves.has(move.id) && (!pokemon.isTerastallized || (move.id !== Moves.TERA_BLAST && (move.id !== Moves.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(Species.TERAPAGOS)))); } /** - * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move - * @param passive - Unused - * @param simulated - Unused - * @param defender - The pokemon being attacked (unused) - * @param move - The move being used - * @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier + * Apply the ability attribute to change the move's type and/or boost its power. + * @param _pokemon - Unused + * @param _passive - Unused + * @param _simulated - Unused + * @param _defender - Unused + * @param _move - Unused + * @param args + * `[0]` - A {@linkcode NumberHolder} holding the move's type + * `[1]` - A {@linkcode NumberHolder} containing the move's power */ + // TODO: Why can these be nullish? override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: [NumberHolder?, NumberHolder?, ...any]): void { if (args[0] && args[0] instanceof NumberHolder) { args[0].value = this.newType; @@ -1334,13 +1339,16 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { } /** - * Class for abilities that convert single-strike moves to two-strike moves (i.e. Parental Bond). - * @param damageMultiplier the damage multiplier for the second strike, relative to the first. - */ + * Class for abilities that add additional strikes to single-target moves. + * Used by {@linkcode Moves.PARENTAL_BOND | Parental Bond}. +*/ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { private damageMultiplier: number; - constructor(damageMultiplier: number) { + /** + * @param damageMultiplier - The damage multiplier for the added strike, relative to the first. + */ + constructor(damageMultiplier: number) { super(false); this.damageMultiplier = damageMultiplier; @@ -1351,16 +1359,16 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { } /** - * If conditions are met, this doubles the move's hit count (via args[1]) - * or multiplies the damage of secondary strikes (via args[2]) - * @param pokemon the {@linkcode Pokemon} using the move - * @param passive n/a - * @param defender n/a - * @param move the {@linkcode Move} used by the ability source - * @param args Additional arguments: - * - `[0]` the number of strikes this move currently has ({@linkcode NumberHolder}) - * - `[1]` the damage multiplier for the current strike ({@linkcode NumberHolder}) + * Applies the ability attribute by increasing hit count + * @param pokemon - The {@linkcode Pokemon} using the move + * @param passive - Unused + * @param defender - Unused + * @param move - The {@linkcode Move} being used + * @param args: + * - `[0]` - A {@linkcode NumberHolder} holding the move's current strike count + * - `[1]` - A {@linkcode NumberHolder} holding the current strike's damage multiplier. */ + // TODO: Why can these be nullish? override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { const hitCount = args[0] as NumberHolder; const multiplier = args[1] as NumberHolder; @@ -1587,8 +1595,9 @@ export class PostAttackAbAttr extends AbAttr { /** * By default, this method checks that the move used is a damaging attack before - * applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} - * for an example of an effect that does not require a damaging move. + * applying the effect of any inherited class. + * This can be changed by providing a different {@linkcode attackCondition} to the constructor + * (see {@linkcode ConfusionOnStatusEffectAbAttr} for an example of this being used). */ canApplyPostAttack( pokemon: Pokemon, @@ -3138,36 +3147,47 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { } /** - * This attribute applies confusion to the target whenever the user - * directly poisons them with a move, e.g. Poison Puppeteer. + * Attribute to apply confusion to the target whenever the user + * directly statuses them with a move. + * Used by {@linkcode Abilities.POISON_PUPPETEER} * Called in {@linkcode StatusEffectAttr}. * @extends PostAttackAbAttr * @see {@linkcode applyPostAttack} */ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { /** List of effects to apply confusion after */ - private effects: StatusEffect[]; + private effects: ReadonlySet; constructor(...effects: StatusEffect[]) { /** This effect does not require a damaging move */ super((user, target, move) => true); - this.effects = effects; + this.effects = new Set(effects); } - override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + /** + * Check that the target was inflicted by the correct status condition from this Pokemon + * and can be confused. + * @param pokemon - {The @linkcode Pokemon} with this ability + * @param passive - N/A + * @param defender - The {@linkcode Pokemon} being targeted (will be confused) + * @param move - The {@linkcode Move} that applied the status effect + * @param hitResult - N/A + * @param args `[0]` - The {@linkcode StatusEffect} applied by the move + * @returns `true` if the target can be confused after being statused. + */ + override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: [StatusEffect]): boolean { return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) - && this.effects.indexOf(args[0]) > -1 && !defender.isFainted() && defender.canAddTag(BattlerTagType.CONFUSED); + && this.effects.has(args[0]) && !defender.isFainted() && defender.canAddTag(BattlerTagType.CONFUSED); } - /** - * Applies confusion to the target pokemon. - * @param pokemon {@link Pokemon} attacking + * Apply confusion to the target pokemon. + * @param pokemon - {@linkcode Pokemon} with the ability * @param passive N/A - * @param defender {@link Pokemon} defending - * @param move {@link Move} used to apply status effect and confusion - * @param hitResult N/A - * @param args [0] {@linkcode StatusEffect} applied by move + * @param defender - {@linkcode Pokemon} being targeted (will be confused) + * @param move - N/A + * @param hitResult - N/A + * @param args - N/A */ override applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { if (!simulated) { @@ -3313,15 +3333,15 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA /** * Determine whether the {@linkcode ConditionalUserFieldProtectStatAbAttr} can be applied. - * @param pokemon The pokemon with the ability - * @param passive unused - * @param simulated Unused - * @param stat The stat being affected - * @param cancelled Holds whether the stat change was already prevented. - * @param args Args[0] is the target pokemon of the stat change. - * @returns - */ - override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { + * @param pokemon - The pokemon with the ability + * @param passive - unused + * @param simulated - Unused + * @param stat - The {@linkcode Stat} being affected + * @param cancelled - {@linkcode BooleanHolder} containing whether the stat change was already prevented + * @param args `[0]` - The {@linkcode Pokemon} recieving the stat change + * @returns `true` if the ability can be applied + */ + override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon]): boolean { const target = args[0]; if (!target) { return false; @@ -3330,12 +3350,12 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA } /** - * Apply the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} to an interaction - * @param _pokemon The pokemon the stat change is affecting (unused) - * @param _passive unused - * @param _simulated unused - * @param stat The stat being affected - * @param cancelled Will be set to true if the stat change is prevented + * Apply the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} to block status effect application + * @param _pokemon - unused + * @param _passive - unused + * @param _simulated - unused + * @param stat - unused + * @param cancelled - A {@linkcode BooleanHolder} containing status effect immunity; will be set to `true` * @param _args unused */ override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, cancelled: BooleanHolder, _args: any[]): void { @@ -3508,12 +3528,17 @@ export class ConditionalCritAbAttr extends AbAttr { } /** - * @param pokemon {@linkcode Pokemon} user. - * @param args [0] {@linkcode BooleanHolder} If true critical hit is guaranteed. - * [1] {@linkcode Pokemon} Target. - * [2] {@linkcode Move} used by ability user. + * Apply this ability by enabling guaranteed critical hits. + * @param pokemon - The {@linkcode Pokemon} with the ability (unused). + * @param passive - Unused + * @param simulated - Unused + * @param cancelled - Unused + * @param args - + * - [0] A {@linkcode BooleanHolder} containing whether critical hits are guaranteed. + * - [1] The {@linkcode Pokemon} being targeted (unused) + * - [2] The {@linkcode Move} used by the ability holder (unused) */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: [BooleanHolder, Pokemon, Move, ...any]): void { (args[0] as BooleanHolder).value = true; } } @@ -4034,7 +4059,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { /** * After the turn ends, resets the status of either the ability holder or their ally - * @param allyTarget Whether to target ally, defaults to false (self-target) + * @param allyTarget - Whether to target the ability holder's ally; default `false` (self-target) */ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { private allyTarget: boolean; @@ -4298,26 +4323,29 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { /** - * Attribute used for abilities (Bad Dreams) that damages the opponents for being asleep + * Attribute used for abilities (Bad Dreams) that damages sleeping opponents during turn end. */ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return pokemon.getOpponents().some(opp => (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus); } /** - * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) - * @param pokemon {@linkcode Pokemon} with this ability - * @param passive N/A - * @param simulated `true` if applying in a simulated call. - * @param args N/A + * Damages all sleeping opponents on turn end equal to 1/8 of their respective max hp (min 1) + * @param pokemon - The {@linkcode Pokemon} with this ability + * @param passive - Unused + * @param simulated - Whether to suppress changes to game state during calculations. + * @param args - Unused */ override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - for (const opp of pokemon.getOpponents()) { - if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { - if (!simulated) { - opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); - globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); - } + if (simulated) { + return; + } + + // grab all on-field, sleeping opponents and hurt them if they aren't immune to passive damage + for (const opp of pokemon.getOpponents(true).filter(opp => opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE))) { + if (!opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); + globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); } } } @@ -4398,7 +4426,7 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { * @extends AbAttr */ export class PostMoveUsedAbAttr extends AbAttr { - canApplyPostMoveUsed( + canApplyPostMoveUsed( pokemon: Pokemon, move: PokemonMove, source: Pokemon, @@ -5086,7 +5114,8 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { } /** - * Ignores the type immunity to Status Effects of the defender if the defender is of a certain type + * Attribute to ignore type-based immunities to Status Effect if the defender is of a certain type. + * Used by {@linkcode Abilities.CORROSION}. */ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { private statusEffect: StatusEffect[]; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 19c94a8a045..718c5c715fe 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -37,6 +37,8 @@ export enum ArenaTagSide { ENEMY, } +// TODO: Add a class for tags that explicitly REQUIRE a source move (as currently we have a lot of bangs) + export abstract class ArenaTag { constructor( public tagType: ArenaTagType, @@ -65,8 +67,17 @@ export abstract class ArenaTag { onOverlap(_arena: Arena, _source: Pokemon | null): void {} + /** + * Trigger this {@linkcode ArenaTag}'s effect, reducing its duration as applicable. + * Will ignore duration of all tags with durations alwrea + * @param _arena - The {@linkcode Arena} at the moment the tag is being lapsed. + * Unused by default but can be used by super classes. + * @returns `true` if this tag should be kept; `false` if it should be removed. + */ 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 { @@ -870,6 +881,7 @@ class ToxicSpikesTag extends ArenaTrapTag { * Arena Tag class for delayed attacks, such as {@linkcode Moves.FUTURE_SIGHT} or {@linkcode Moves.DOOM_DESIRE}. * Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used), * and deals damage after the turn count is reached. + // TODO: Add class for tags that can have multiple instances up and edit `arena.addTag` appropriately */ export class DelayedAttackTag extends ArenaTag { public targetIndex: BattlerIndex; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 34fdd5409c8..40061feac3c 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -45,15 +45,17 @@ import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; import { isNullOrUndefined } from "#app/utils/common"; +// TODO: Explain what a battlerTagLapseType actually is export enum BattlerTagLapseType { FAINT, + /** Tag lapses while using a (non-follow up) move, potentially halting its execution. */ MOVE, PRE_MOVE, AFTER_MOVE, MOVE_EFFECT, TURN_END, HIT, - /** Tag lapses AFTER_HIT, applying its effects even if the user faints */ + /** Tag lapses after being hit, applying its effects even if the user faints */ AFTER_HIT, CUSTOM, } @@ -94,10 +96,13 @@ export class BattlerTag { /** * Tick down this {@linkcode BattlerTag}'s duration. + * @param _pokemon - The {@linkcode Pokemon} whom this tag belongs to. + * Unused by default but can be used by super classes. + * @param _lapseType - The {@linkcode BattlerTagLapseType} being lapsed. + * Unused by default but can be used by super classes. * @returns `true` if the tag should be kept (`turnCount` > 0`) */ lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - // TODO: Maybe flip this (return `true` if tag needs removal) return --this.turnCount > 0; } @@ -143,7 +148,7 @@ export interface TerrainBattlerTag { /** * 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 Moves.DISABLE}. + * in-game (not to be confused with {@linkcode Moves.DISABLE}). * * Descendants can override {@linkcode isMoveRestricted} to restrict moves that * match a condition. A restricted move gets cancelled before it is used. diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 235cb954ea5..8270fac22c5 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -121,7 +121,13 @@ import { MoveTarget } from "#enums/MoveTarget"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; -import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; +import { + invalidAssistMoves, + invalidCopycatMoves, + invalidMetronomeMoves, + invalidMirrorMoveMoves, + invalidSleepTalkMoves, +} from "./invalid-moves"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; @@ -212,13 +218,13 @@ export default class Move implements Localizable { /** * Adds a new MoveAttr to the move (appends to the attr array) - * if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition} - * @param AttrType {@linkcode MoveAttr} the constructor of a MoveAttr class - * @param args the args needed to instantiate a the given class - * @returns the called object {@linkcode Move} + * if the MoveAttr also comes with a condition, also adds that to the {@linkcode MoveCondition} array + * @param attrType - The constructor of a {@linkcode MoveAttr} class to add + * @param args - Any additional arguments needed to instantiate the given class + * @returns `this` */ - attr>(AttrType: T, ...args: ConstructorParameters): this { - const attr = new AttrType(...args); + attr>(attrType: T, ...args: ConstructorParameters): this { + const attr = new attrType(...args); this.attrs.push(attr); let attrCondition = attr.getCondition(); if (attrCondition) { @@ -233,10 +239,11 @@ export default class Move implements Localizable { /** * Adds a new MoveAttr to the move (appends to the attr array) - * if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition} - * 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 - * @returns the called object {@linkcode Move} + * if the MoveAttr also comes with a condition, also adds that to the {@linkcode MoveCondition} array + * Almost identical to {@linkcode attr}, except you are passing in an already instantized {@linkcode MoveAttr} object + * as opposed to a constructor and its arguments + * @param attrAdd - The {@linkcode MoveAttr} to add + * @returns `this` */ addAttr(attrAdd: MoveAttr): this { this.attrs.push(attrAdd); @@ -262,13 +269,13 @@ export default class Move implements Localizable { } /** - * Getter function that returns if this Move has a MoveFlag - * @param flag {@linkcode MoveFlags} to check - * @returns boolean + * Getter function that returns if this Move has a given MoveFlag. + * @param flag - The {@linkcode MoveFlags} to check + * @returns Whether this Move has the specified flag. */ 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 - return !!(this.flags & flag); + // Flags are internally represented as bitmasks, so we check by taking the bitwise AND. + return (this.flags & flag) !== MoveFlags.NONE; } /** @@ -384,8 +391,10 @@ export default class Move implements Localizable { } /** - * Marks the move as "partial": appends texts to the move name - * @returns the called object {@linkcode Move} + * Mark a move as partially implemented. + * Partial moves are expected to have their core functionality implemented, but may lack + * certain notable features or interactions with other moves or abilities. + * @returns `this` */ partial(): this { this.nameAppend += " (P)"; @@ -393,8 +402,10 @@ export default class Move implements Localizable { } /** - * Marks the move as "unimplemented": appends texts to the move name - * @returns the called object {@linkcode Move} + * Mark a move as unimplemented. + * Unimplemented moves are ones which have _none_ of their basic functionality enabled, + * and are effectively barred from use by the AI. + * @returns `this` */ unimplemented(): this { this.nameAppend += " (N)"; @@ -961,7 +972,7 @@ export class AttackMove extends Move { /** * {@link 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; + * All damaging Fire-type moves can thaw a frozen target, regardless of whether or not they have a chance to burn */ if (this.type === PokemonType.FIRE) { this.addAttr(new HealStatusEffectAttr(false, StatusEffect.FREEZE)); @@ -2507,9 +2518,12 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { } /** - * Applies the effect of 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. - * @returns `true` if Psycho Shift's effect is able to be applied to the 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. + * @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 { const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined); @@ -2841,6 +2855,10 @@ 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}. + */ export class BypassSleepAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.status?.effect === StatusEffect.SLEEP) { @@ -5360,7 +5378,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); applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); if (cancelled.value) { @@ -6998,7 +7019,8 @@ export class CopyMoveAttr extends CallMoveAttr { /** * Attribute used for moves that causes 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 { constructor() { diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index a6a87b4ab9a..243eab58b27 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -409,7 +409,7 @@ export async function applyModifierTypeToPlayerPokemon( m.type.id === modType.id && m.pokemonId === pokemon.id && m.matchType(modifier), - ) as PokemonHeldItemModifier; + ) as PokemonHeldItemModifier | undefined; // At max stacks if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { diff --git a/src/enums/MoveFlags.ts b/src/enums/MoveFlags.ts index 0fc85fddec6..796d2a3468e 100644 --- a/src/enums/MoveFlags.ts +++ b/src/enums/MoveFlags.ts @@ -1,3 +1,7 @@ +/** + * A list of possible flags that various moves may have. + * Represented internally as a bitmask. + */ export enum MoveFlags { NONE = 0, MAKES_CONTACT = 1 << 0, diff --git a/src/field/arena.ts b/src/field/arena.ts index f083180490b..bcd1d32586f 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -691,6 +691,7 @@ export class Arena { targetIndex?: BattlerIndex, ): boolean { const existingTag = this.getTagOnSide(tagType, side); + // TODO: Change this for future sight if (existingTag) { existingTag.onOverlap(this, globalScene.getPokemonById(sourceId)); @@ -703,6 +704,7 @@ export class Arena { } // creates a new tag object + // TODO: check if we can remove the `|| 0` since turn count should never be nullish const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); if (newTag) { newTag.onAdd(this, quiet); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8abe3a303ca..3317eef5b4c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -486,7 +486,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.ivs = ivs || getIvsFromId(this.id); if (this.gender === undefined) { - this.generateGender(); + this.gender = this.species.generateGender(); } if (this.formIndex === undefined) { @@ -562,14 +562,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} 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: boolean = true) { const name: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.name : this.name; const nickname: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.nickname : this.nickname; try { if (nickname) { - return decodeURIComponent(escape(atob(nickname))); + return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually... } return name; } catch (err) { @@ -578,13 +580,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getPokeball(useIllusion = false){ - if(useIllusion){ - return this.summonData.illusion?.pokeball ?? this.pokeball - } else { - return this.pokeball + /** + * Return this Pokemon's {@linkcode PokeballType}. + * @param useIllusion - Whether to consider this Pokemon's illusion if present; default `true` + * @returns The {@linkcode PokeballType} that will be shown when this Pokemon is sent out into battle. + */ + getPokeball(useIllusion = false): PokeballType { + return useIllusion && this.summonData?.illusion + ? this.summonData.illusion.pokeball + : this.pokeball; } - } init(): void { this.fieldPosition = FieldPosition.CENTER; @@ -640,9 +645,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Checks if a pokemon is fainted (ie: its `hp <= 0`). - * It's usually better to call {@linkcode isAllowedInBattle()} - * @param checkStatus `true` to also check that the pokemon's status is {@linkcode StatusEffect.FAINT} - * @returns `true` if the pokemon is fainted + * Usually should not be called directly in favor of consulting {@linkcode isAllowedInBattle()}. + * @param checkStatus - Whether to also check that the pokemon's status is {@linkcode StatusEffect.FAINT}; default `false` + * @returns Whether this Pokemon is fainted, as described above. */ public isFainted(checkStatus = false): boolean { return ( @@ -652,8 +657,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Check if this pokemon is both not fainted and allowed to be in battle based on currently active challenges. - * @returns {boolean} `true` if pokemon is allowed in battle + * Check if this pokemon is both not fainted and allowed to be used based on currently active challenges. + * @returns Whether this Pokemon is allowed to partake in battle. */ public isAllowedInBattle(): boolean { return !this.isFainted() && this.isAllowedInChallenge(); @@ -661,8 +666,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Check if this pokemon is allowed based on any active challenges. - * It's usually better to call {@linkcode isAllowedInBattle()} - * @returns {boolean} `true` if pokemon is allowed in battle + * Usually should not be called directly in favor of consulting {@linkcode isAllowedInBattle()}. + * @returns Whether this Pokemon is allowed under the current challenge conditions. */ public isAllowedInChallenge(): boolean { const challengeAllowed = new BooleanHolder(true); @@ -676,8 +681,8 @@ export default 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). - * @param onField `true` to also check if the pokemon is currently on the field; default `false` - * @returns `true` if the pokemon is "active", as described above. + * @param onField - Whether to also check if the pokemon is currently on the field; default `false` + * @returns Whether this pokemon is considered "active", as described above. * Returns `false` if there is no active {@linkcode BattleScene} or the pokemon is disallowed. */ public isActive(onField = false): boolean { @@ -745,8 +750,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.species.ability2 !== this.species.ability1 ? randSeedInt(2) : 0; } - - /** * Generate an illusion of the last pokemon in the party, as other wild pokemon in the area. */ @@ -842,7 +845,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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: boolean = false): Promise { /** Promises that are loading assets and can be run concurrently. */ @@ -960,11 +966,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Attempt to process variant sprite. - * - * @param cacheKey the cache key for the variant color sprite - * @param useExpSprite should the experimental sprite be used - * @param battleSpritePath the filename of the sprite + * Attempt to process variant sprite color caches. + * @param cacheKey - the cache key for the variant color sprite + * @param useExpSprite - Whether experimental sprites should be used if present + * @param battleSpritePath - the filename of the sprite */ async populateVariantColorCache( cacheKey: string, @@ -1027,7 +1032,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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, "/"); return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; } @@ -1162,12 +1171,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Get this {@linkcode Pokemon}'s {@linkcode PokemonSpeciesForm}. - * @param ignoreOverride - Whether to ignore overridden species from {@linkcode Moves.TRANSFORM}, default `false`. - * This overrides `useIllusion` if `true`. - * @param useIllusion - `true` to use the speciesForm of the illusion; default `false`. + * Return this Pokemon's {@linkcode PokemonSpeciesForm | SpeciesForm}. + * @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 This Pokemon's {@linkcode PokemonSpeciesForm}. */ - getSpeciesForm(ignoreOverride: boolean = false, useIllusion: boolean = false): PokemonSpeciesForm { + getSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm { if (!ignoreOverride && this.summonData.speciesForm) { return this.summonData.speciesForm; } @@ -1183,9 +1192,12 @@ export default 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: boolean = false): PokemonSpeciesForm { + getFusionSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm { const fusionSpecies: PokemonSpecies = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; @@ -1547,16 +1559,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Calculates and retrieves the final value of a stat considering any held * items, move effects, opponent abilities, and whether there was a critical * hit. - * @param stat the desired {@linkcode EffectiveStat} - * @param opponent the target {@linkcode Pokemon} - * @param move the {@linkcode Move} being used - * @param ignoreAbility determines whether this Pokemon's abilities should be ignored during the stat calculation - * @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation. - * @param ignoreAllyAbility during an attack, determines whether the ally Pokemon's abilities should be ignored during the stat calculation. - * @param isCritical determines whether a critical hit has occurred or not (`false` by default) - * @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering - * @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false` - * @returns the final in-battle value of a stat + * @param stat - The desired {@linkcode EffectiveStat | Stat} to check. + * @param opponent - The {@linkcode Pokemon} being targeted, if applicable. + * @param move - The {@linkcode Move} being used, if any. Used to check ability ignoring effects and similar. + * @param ignoreAbility - Whether to ignore ability effects of the user; default `false`. + * @param ignoreOppAbility - Whether to ignore ability effects of the target; default `false`. + * @param ignoreAllyAbility - Whether to ignore ability effects of the user's allies; default `false`. + * @param isCritical - Whether a critical hit has occurred or not; default `false`. + * If `true`, will nullify offensive stat drops or defensive stat boosts. + * @param simulated - Whether to nullify any effects that produce changes to game state during calculations; default `true` + * @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. */ getEffectiveStat( stat: EffectiveStat, @@ -1592,10 +1605,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { fieldApplied, simulated, ); + + // TODO: us breaking early effectively makes the ability having a "can stack" toggle useless... if (fieldApplied.value) { break; } } + if (!ignoreAbility) { applyStatMultiplierAbAttrs( StatMultiplierAbAttr, @@ -1608,7 +1624,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const ally = this.getAlly(); if (!isNullOrUndefined(ally)) { - applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility); + applyAllyStatMultiplierAbAttrs( + AllyStatMultiplierAbAttr, + ally, + stat, + statValue, + simulated, + this, + move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility); } let ret = @@ -1860,9 +1883,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} 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: boolean = false): Gender { + getGender(ignoreOverride = false, useIllusion = false): Gender { if (useIllusion && this.summonData.illusion) { return this.summonData.illusion.gender; } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { @@ -1872,9 +1898,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). - */ - getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + * 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 = false, useIllusion = false): Gender { if (useIllusion && this.summonData.illusion?.fusionGender) { return this.summonData.illusion.fusionGender; } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { @@ -1884,14 +1913,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} 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 at least once. */ isShiny(useIllusion: boolean = false): boolean { if (!useIllusion && this.summonData.illusion) { return this.summonData.illusion.basePokemon?.shiny || (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; - } else { - return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); } + + return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); } isBaseShiny(useIllusion: boolean = false){ @@ -1911,33 +1942,37 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * - * @param useIllusion - Whether we want the fake or real shininess (illusion ability). - * @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise + * Check whether this Pokemon is doubly shiny (both normal and fusion are shiny). + * @param useIllusion - Whether to consider this pokemon's illusion if present; default `false` + * @returns Whether this pokemon's base and fusion counterparts are both shiny. */ - isDoubleShiny(useIllusion: boolean = false): boolean { + isDoubleShiny(useIllusion = false): boolean { if (!useIllusion && this.summonData.illusion?.basePokemon) { return this.isFusion(false) && this.summonData.illusion.basePokemon.shiny && this.summonData.illusion.basePokemon.fusionShiny; - } else { - return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; } + + return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; } /** - * @param {boolean} 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: boolean = false): Variant { + getVariant(useIllusion = false): Variant { if (!useIllusion && this.summonData.illusion) { return !this.isFusion(false) ? this.summonData.illusion.basePokemon!.variant : Math.max(this.variant, this.fusionVariant) as Variant; - } else { - return !this.isFusion(true) + } + + return !this.isFusion(true) ? this.variant : Math.max(this.variant, this.fusionVariant) as Variant; - } } + // TODO: Clarify how this differs from {@linkcode getVariant} getBaseVariant(doubleShiny: boolean): Variant { if (doubleShiny) { return this.summonData.illusion?.basePokemon?.variant ?? this.variant; @@ -1946,47 +1981,55 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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 { return this.luck + (this.isFusion() ? this.fusionLuck : 0); } - isFusion(useIllusion: boolean = false): boolean { - if (useIllusion && this.summonData.illusion) { - return !!this.summonData.illusion.fusionSpecies; - } - return !!this.fusionSpecies; + /** + * 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 { + return useIllusion && this.summonData.illusion + ? !!this.summonData.illusion.fusionSpecies + : !!this.fusionSpecies; } /** - * @param {boolean} 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: boolean = false): string { + getName(useIllusion = false): string { return (!useIllusion && this.summonData.illusion?.basePokemon) ? this.summonData.illusion.basePokemon.name : this.name; } /** - * Checks if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}. - * @param species the pokemon {@linkcode Species} to check - * @returns `true` if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}, `false` otherwise + * Check whether this {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}. + * @param species - The {@linkcode Species} to check against + * @returns Whether this Pokemon is currently fused with the specified {@linkcode Species}. */ hasFusionSpecies(species: Species): boolean { return this.fusionSpecies?.speciesId === species; } /** - * Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it. - * @param species the pokemon {@linkcode Species} to check - * @param formKey If provided, requires the species to be in that form - * @returns `true` if the pokemon is the species or is fused with it, `false` otherwise + * Check whether this {@linkcode Pokemon} either is or is fused with the given {@linkcode Species}. + * @param species - The {@linkcode Species} to check against + * @param formKey - If provided, will require the species to be in that form + * @returns Whether this Pokemon has this species as either its base or fusion counterpart. */ hasSpecies(species: Species, formKey?: string): boolean { if (isNullOrUndefined(formKey)) { - return ( - this.species.speciesId === species || - this.fusionSpecies?.speciesId === species - ); + return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; } return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey); @@ -1994,7 +2037,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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 @@ -2024,11 +2073,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Checks which egg moves have been unlocked for the {@linkcode Pokemon} based - * on the species it was met at or by the first {@linkcode Pokemon} in its evolution + * Check which egg moves have been unlocked for this {@linkcode Pokemon}. + * 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. - * @returns an array of {@linkcode Moves}, the length of which is determined by how many - * egg moves are unlocked for that species. + * @returns An array of all {@linkcode Moves} that are egg moves and unlocked for this Pokemon. */ getUnlockedEggMoves(): Moves[] { const moves: Moves[] = []; @@ -2047,13 +2095,12 @@ export default 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. * * 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. - * @returns an array of {@linkcode Moves}, the length of which is determined - * by how many learnable moves there are for the {@linkcode Pokemon}. + * @returns An array of {@linkcode Moves}, as described above. */ public getLearnableLevelMoves(): Moves[] { let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); @@ -2074,18 +2121,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Gets the types of a pokemon - * @param includeTeraType - `true` to include tera-formed type; Default: `false` - * @param forDefend - `true` if the pokemon is defending from an attack; Default: `false` - * @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` - * @param useIllusion - `true` to return the types of the illusion instead of the actual types; Default: `false` - * @returns array of {@linkcode PokemonType} + * Evaluate and return this Pokemon's typing. + * @param includeTeraType - Whether to use this Pokemon's tera type if Terastallized; default `false` + * @param forDefend - Whether this Pokemon is currently receiving an attack; default `false` + * @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 An array of {@linkcode PokemonType}s corresponding to this Pokemon's typing (real or percieved). */ public getTypes( includeTeraType = false, - forDefend: boolean = false, - ignoreOverride: boolean = false, - useIllusion: boolean = false + forDefend = false, + ignoreOverride = false, + useIllusion = false, ): PokemonType[] { const types: PokemonType[] = []; @@ -2179,7 +2226,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // 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); if (index !== -1) { types.splice(index, 1); @@ -2204,12 +2251,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Checks if the pokemon's typing includes the specified type - * @param type - {@linkcode PokemonType} to check - * @param includeTeraType - `true` to include tera-formed type; Default: `true` - * @param forDefend - `true` if the pokemon is defending from an attack; Default: `false` - * @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` - * @returns `true` if the Pokemon's type matches + * Check if this Pokemon's typing includes the specified type. + * @param type - The {@linkcode PokemonType} to check + * @param includeTeraType - Whether to use this Pokemon's tera type if Terastallized; default `false` + * @param forDefend - Whether this Pokemon is currently receiving an attack; default `false` + * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false` + * @returns Whether this Pokemon is of the specified type. */ public isOfType( type: PokemonType, @@ -2217,18 +2264,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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. - * 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. - * @see {@linkcode hasAbility} {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases - * @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` - * @returns The non-passive {@linkcode Ability} of the pokemon + * Get this Pokemon's non-passive {@linkcode Ability}, factoring in fusions, overrides and ability-changing effects. + + * Should rarely be called directky in favor of {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr}, + * both of which check both ability slots and account for suppression. + * @see {@linkcode hasAbility}and {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases + * @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 { if (!ignoreOverride && this.summonData.ability) { @@ -2449,13 +2495,12 @@ export default 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 - * effects which can affect whether an ability will be present or in effect, and both passive and - * non-passive. This is the primary way to check whether a pokemon has a particular ability. - * @param ability The ability to check for + * Check whether a pokemon has the specified ability in effect, either as a normal or passive ability. + * Accounts for all the various effects which can disable or modify abilities. + * @param ability - The {@linkcode Abilities | Ability} to check for * @param canApply - Whether to check if the ability is currently active; default `true` - * @param ignoreOverride Whether to ignore ability changing effects; default `false` - * @returns `true` if the ability is present and active + * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false` + * @returns Whether this {@linkcode Pokemon} has the given ability */ public hasAbility( ability: Abilities, @@ -2479,14 +2524,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Checks whether a pokemon has an ability with the specified attribute and it's in effect. - * Accounts for all the various effects which can affect whether an ability will be present or - * in effect, and both passive and non-passive. This is one of the two primary ways to check - * whether a pokemon has a particular ability. - * @param attrType The {@link AbAttr | ability attribute} to check for + * 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 disable or modify abilities. + * @param attrType - The {@linkcode AbAttr | attribute} to check for * @param canApply - Whether to check if the ability is currently active; default `true` - * @param ignoreOverride Whether to ignore ability changing effects; default `false` - * @returns `true` if an ability with the given {@linkcode AbAttr} is present and active + * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false` + * @returns Whether this Pokemon has an ability with the given {@linkcode AbAttr}. */ public hasAbilityWithAttr( attrType: Constructor, @@ -2518,7 +2561,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const autotomizedTag = this.getTag(AutotomizedTag); let weightRemoved = 0; if (!isNullOrUndefined(autotomizedTag)) { - weightRemoved = 100 * autotomizedTag!.autotomizeCount; + weightRemoved = 100 * autotomizedTag.autotomizeCount; } const minWeight = 0.1; const weight = new NumberHolder(this.species.weight - weightRemoved); @@ -3986,10 +4029,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * Note that this does not apply to evasion or accuracy * @see {@linkcode getAccuracyMultiplier} - * @param stat the desired {@linkcode EffectiveStat} - * @param opponent the target {@linkcode Pokemon} - * @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 stat - The {@linkcode EffectiveStat} to calculate + * @param opponent - The {@linkcode Pokemon} being targeted + * @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 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 ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false` @@ -5127,6 +5170,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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[] { return this.summonData.moveHistory; } @@ -5140,21 +5189,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Returns a list of the most recent move entries in this Pokemon's move history. - * The retrieved move entries are sorted in order from NEWEST to OLDEST. - * @param moveCount The number of move entries to retrieve. - * If negative, retrieve the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}). - * Default is `1`. - * @returns A list of {@linkcode TurnMove}, as specified above. + * 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**. + * @param moveCount - The maximum number of move entries to retrieve. + * If negative, retrieves the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}). + * Default is `1`. + * @returns An array of {@linkcode TurnMove}, as specified above. */ + // TODO: Update documentation in dancer PR to mention "getLastNonVirtualMove" getLastXMoves(moveCount = 1): TurnMove[] { const moveHistory = this.getMoveHistory(); - if (moveCount >= 0) { + if (moveCount > 0) { return moveHistory .slice(Math.max(moveHistory.length - moveCount, 0)) .reverse(); } - return moveHistory.slice(0).reverse(); + return moveHistory.slice().reverse(); } getMoveQueue(): TurnMove[] { @@ -5678,7 +5728,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Performs the action of clearing a Pokemon's status - * + * * This is a helper to {@linkcode resetStatus}, which should be called directly instead of this method */ public clearStatus(confusion: boolean, reloadAssets: boolean) { @@ -6387,29 +6437,31 @@ export default 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. - * @param heldItem The item stack to be reduced by 1. - * @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`. - * @returns `true` if the item was removed successfully, `false` otherwise. + * @param heldItem The item stack to be reduced. + * @param forBattle - Whether to trigger in-battle effects (such as Unburden) after losing the item. Default: `true` + * 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 { + // TODO: What does a -1 pokemon id mean? if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) { return false; } - heldItem.stackCount--; - if (heldItem.stackCount <= 0) { - globalScene.removeModifier(heldItem, !this.isPlayer()); - } - if (forBattle) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); - } + heldItem.stackCount--; + if (heldItem.stackCount <= 0) { + globalScene.removeModifier(heldItem, !this.isPlayer()); + } + if (forBattle) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); + } - return true; + return true; } /** @@ -7166,27 +7218,28 @@ export class EnemyPokemon extends Pokemon { } /** - * Sets the pokemons boss status. If true initializes the boss segments either from the arguments - * or through the the Scene.getEncounterBossSegments function + * Set this {@linkcode EnemyPokemon}'s boss status. * - * @param boss if the pokemon is a boss - * @param bossSegments amount of boss segments (health-bar segments) + * @param boss - Whether this pokemon should be a boss; default `true` + * @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 { - if (boss) { - this.bossSegments = - bossSegments || - globalScene.getEncounterBossSegments( - globalScene.currentBattle.waveIndex, - this.level, - this.species, - true, - ); - this.bossSegmentIndex = this.bossSegments - 1; - } else { + setBoss(boss = true, bossSegments?: number): void { + if (!boss) { this.bossSegments = 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 { @@ -7958,8 +8011,9 @@ export class PokemonTurnData { /** How many times the current move should hit the target(s) */ public hitCount = 0; /** - * - `-1` = Calculate how many hits are left - * - `0` = Move is finished + * - `-1`: Calculate how many hits are left + * - `0`: Move is finished + * - Anything `>0`: Number of hits left to check (barring early interrupts) */ public hitsLeft = -1; public totalDamageDealt = 0; @@ -8076,13 +8130,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. * - * @param {Pokemon} pokemon {@linkcode Pokemon} that would be using this move - * @param {boolean} ignorePp If `true`, skips the PP check - * @param {boolean} ignoreRestrictionTags If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) - * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. + * @param pokemon - The {@linkcode Pokemon} attempting to use this move + * @param ignorePp - Whether to ignore checking if the move is out of PP; default `false` + * @param ignoreRestrictionTags - Whether to skip checks for {@linkcode MoveRestrictionBattlerTag}s; default `false` + * @returns Whether this {@linkcode PokemonMove} can be selected by this Pokemon. */ isUsable( pokemon: Pokemon, @@ -8090,7 +8144,7 @@ export class PokemonMove { ignoreRestrictionTags = false, ): boolean { if ( - this.moveId && + this.moveId !== Moves.NONE && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon) ) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 94bb0e3419a..4c1f4002cd5 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -65,7 +65,7 @@ export const modifierSortFunc = (a: Modifier, b: Modifier): number => { const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; - //First sort by pokemonID + // First sort by pokemonID if (aId < bId) { return 1; } @@ -745,7 +745,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return 1; } - getMaxStackCount(forThreshold?: boolean): number { + getMaxStackCount(forThreshold = false): number { const pokemon = this.getPokemon(); if (!pokemon) { return 0; @@ -2826,6 +2826,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { damageMultiplier.value *= 1 - 0.25 * this.getStackCount(); return true; } + if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) { // Deal 25% damage for each remaining Multi Lens hit damageMultiplier.value *= 0.25; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index c3e558e1d86..4863c643216 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -86,7 +86,8 @@ 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. - const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag; + // TODO: Why do we handle this here of all places + const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag | undefined; if (encoreTag) { this.getPokemon().lapseTag(BattlerTagType.ENCORE); } diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 0a76df31a2c..219335be96e 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -36,13 +36,14 @@ export class SelectStarterPhase extends Phase { /** * 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[]) { const party = globalScene.getPlayerParty(); const loadPokemonAssets: Promise[] = []; starters.forEach((starter: Starter, i: number) => { if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + // Override first starter species if applicable starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); } const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 56057c23372..8eca92a1811 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -210,7 +210,7 @@ export class TitlePhase extends Phase { globalScene.eventManager.startEventChallenges(); globalScene.setSeed(seed); - globalScene.resetSeed(0); + globalScene.resetSeed(); globalScene.money = globalScene.gameMode.getStartingMoney(); @@ -289,6 +289,7 @@ export class TitlePhase extends Phase { console.error("Failed to load daily run:\n", err); }); } 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)); if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) { seed = Overrides.DAILY_RUN_SEED_OVERRIDE; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 0c5e0b349ed..1760826ebc4 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1351,7 +1351,8 @@ export class GameData { // (or prevent them from being null) // If the value is able to *not exist*, it should say so in the code 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) { case "party": case "enemyParty": { diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 5a0978a934d..ae38d7c7249 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -263,7 +263,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { const ppPercentLeft = pp / maxPP; - //** Determines TextStyle according to percentage of PP remaining */ + /** Determines TextStyle according to percentage of PP remaining */ let ppColorStyle = TextStyle.MOVE_PP_FULL; if (ppPercentLeft > 0.25 && ppPercentLeft <= 0.5) { ppColorStyle = TextStyle.MOVE_PP_HALF_FULL; @@ -273,7 +273,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { ppColorStyle = TextStyle.MOVE_PP_EMPTY; } - //** Changes the text color and shadow according to the determined TextStyle */ + /** Changes the text color and shadow according to the determined TextStyle */ this.ppText.setColor(this.getTextColor(ppColorStyle, false)); this.ppText.setShadowColor(this.getTextColor(ppColorStyle, true)); this.moveInfoOverlay.show(pokemonMove.getMove()); From 0c12f2a79cfb1f024c8c971085b8960c758de750 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 27 May 2025 09:37:20 -0400 Subject: [PATCH 02/10] Update ability-class.ts comments --- src/data/abilities/ability-class.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/data/abilities/ability-class.ts b/src/data/abilities/ability-class.ts index 8a284bac264..282276b7906 100644 --- a/src/data/abilities/ability-class.ts +++ b/src/data/abilities/ability-class.ts @@ -119,7 +119,7 @@ export class Ability implements Localizable { /** * Mark an ability as partially implemented. - * Partial abilities are expected to have their core functionality implemented, but may lack + * 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` */ @@ -139,9 +139,11 @@ export class Ability implements Localizable { } /** - * Mark an ability as having an edge case. + * Mark an ability as having one or more edge cases. + * 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` */ From 4e1af68c0fd9e43a69b5e2087a0810b583553a5a Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 27 May 2025 09:51:59 -0400 Subject: [PATCH 03/10] Update move.ts comments --- src/data/moves/move.ts | 66 +++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index af535c2d6ae..ecb56fbfab4 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -191,8 +191,8 @@ export default class Move implements Localizable { /** * Get all move attributes that match `attrType` - * @param attrType any attribute that extends {@linkcode MoveAttr} - * @returns Array of attributes that match `attrType`, Empty Array if none match. + * @param attrType - The constructor of a {@linkcode MoveAttr} to check. + * @returns An array containing all attributes matching `attrType`, or an empty array if none match. */ getAttrs(attrType: Constructor): T[] { return this.attrs.filter((a): a is T => a instanceof attrType); @@ -200,27 +200,27 @@ export default class Move implements Localizable { /** * Check if a move has an attribute that matches `attrType` - * @param attrType any attribute that extends {@linkcode MoveAttr} - * @returns true if the move has attribute `attrType` + * @param attrType - The constructor of a {@linkcode MoveAttr} + * @returns Whether if the move has an attribute that is/extends `attrType` */ hasAttr(attrType: Constructor): boolean { return this.attrs.some((attr) => attr instanceof attrType); } /** - * Takes as input a boolean function and returns the first MoveAttr in attrs that matches true - * @param attrPredicate - * @returns the first {@linkcode MoveAttr} element in attrs that makes the input function return true + * Find the first attribute that matches a given predicate function. + * @param attrPredicate - The predicate function to search `MoveAttr`s by. + * @returns The first {@linkcode MoveAttr} for which `attrPredicate` returns a value coercible to the boolean value `true`. */ findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr { return this.attrs.find(attrPredicate)!; // TODO: is the bang correct? } /** - * Adds a new MoveAttr to the move (appends to the attr array) - * if the MoveAttr also comes with a condition, also adds that to the {@linkcode MoveCondition} array - * @param attrType - The constructor of a {@linkcode MoveAttr} class to add - * @param args - Any additional arguments needed to instantiate the given class + * Adds a new MoveAttr to this move (appends to the attr array). + * If the MoveAttr also comes with a condition, it is added to its {@linkcode MoveCondition} array. + * @param attrType - The {@linkcode MoveAttr} to add + * @param args - The arguments needed to instantiate the given class * @returns `this` */ attr>(attrType: T, ...args: ConstructorParameters): this { @@ -238,10 +238,11 @@ export default class Move implements Localizable { } /** - * Adds a new MoveAttr to the move (appends to the attr array) - * if the MoveAttr also comes with a condition, also adds that to the {@linkcode MoveCondition} array - * Almost identical to {@linkcode attr}, except you are passing in an already instantized {@linkcode MoveAttr} object - * as opposed to a constructor and its arguments + * Adds a new MoveAttr to this move (appends to the attr array). + * If the MoveAttr also comes with a condition, it is added to its {@linkcode MoveCondition} array. + * + * Almost identical to {@linkcode attr}, except taking already instantized {@linkcode MoveAttr} object + * as opposed to a constructor and its arguments. * @param attrAdd - The {@linkcode MoveAttr} to add * @returns `this` */ @@ -260,8 +261,8 @@ export default class Move implements Localizable { /** * Sets the move target of this move - * @param moveTarget {@linkcode MoveTarget} the move target to set - * @returns the called object {@linkcode Move} + * @param moveTarget - The {@linkcode MoveTarget} to set + * @returns `this` */ target(moveTarget: MoveTarget): this { this.moveTarget = moveTarget; @@ -320,12 +321,12 @@ export default 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. - * @param {Pokemon} user the source of this move - * @param {Pokemon} target the target of this move - * @param {PokemonType} type the type of the move's target - * @returns boolean + * @param user - The {@linkcode Pokemon} using the move + * @param target - The {@linkcode Pokemon} targeted by the move + * @param type - The {@linkcode PokemonType} of the move + * @returns Whether the move is blocked by the target's type */ isTypeImmune(user: Pokemon, target: Pokemon, type: PokemonType): boolean { if (this.moveTarget === MoveTarget.USER) { @@ -349,8 +350,8 @@ export default class Move implements Localizable { /** * Checks if the move would hit its target's Substitute instead of the target itself. - * @param user The {@linkcode Pokemon} using this move - * @param target The {@linkcode Pokemon} targeted by this move + * @param user - The {@linkcode Pokemon} using this move + * @param target - The {@linkcode Pokemon} targeted by this move * @returns `true` if the move can bypass the target's Substitute; `false` otherwise. */ hitsSubstitute(user: Pokemon, target?: Pokemon): boolean { @@ -369,13 +370,14 @@ export default class Move implements Localizable { } /** - * Adds a move condition to the move - * @param condition {@linkcode MoveCondition} or {@linkcode MoveConditionFunc}, appends to conditions array a new MoveCondition object - * @returns the called object {@linkcode Move} + * Adds a condition to this move (in addition to any provided by its prior {@linkcode MoveAttr}s). + * The move will fail upon use if at least 1 of its conditions is not met. + * @param condition - The {@linkcode MoveCondition} or {@linkcode MoveConditionFunc} to add to the conditions array. + * @returns `this` */ condition(condition: MoveCondition | MoveConditionFunc): this { if (typeof condition === "function") { - condition = new MoveCondition(condition as MoveConditionFunc); + condition = new MoveCondition(condition); } this.conditions.push(condition); @@ -383,8 +385,12 @@ export default class Move implements Localizable { } /** - * Internal dev flag for documenting edge cases. When using this, please document the known edge case. - * @returns the called object {@linkcode Move} + * Mark a move as having one or more edge cases. + * 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 { return this; From 791f0557be460f4e1628ef084f32fbd12cb2ee5f Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Tue, 27 May 2025 09:59:34 -0400 Subject: [PATCH 04/10] Fixed flaky test --- test/moves/gastro_acid.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/moves/gastro_acid.test.ts b/test/moves/gastro_acid.test.ts index 8247d29c0a0..37a79f83fa3 100644 --- a/test/moves/gastro_acid.test.ts +++ b/test/moves/gastro_acid.test.ts @@ -25,7 +25,7 @@ describe("Moves - Gastro Acid", () => { game.override.battleStyle("double"); game.override.startingLevel(1); game.override.enemyLevel(100); - game.override.ability(Abilities.NONE); + game.override.ability(Abilities.BALL_FETCH); game.override.moveset([Moves.GASTRO_ACID, Moves.WATER_GUN, Moves.SPLASH, Moves.CORE_ENFORCER]); game.override.enemySpecies(Species.BIDOOF); game.override.enemyMoveset(Moves.SPLASH); From 72f834c9463704ed76b83c313b8d2e2a6b4f3ad9 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Tue, 27 May 2025 12:20:54 -0400 Subject: [PATCH 05/10] Applied PR reviews --- src/battle-scene.ts | 4 +- src/data/abilities/ability.ts | 52 +++++++++++-------- src/data/arena-tag.ts | 5 +- src/data/moves/move.ts | 36 +++++++------ .../utils/encounter-pokemon-utils.ts | 2 +- src/field/arena.ts | 2 - src/field/pokemon.ts | 10 ++-- src/modifier/modifier.ts | 22 ++------ 8 files changed, 63 insertions(+), 70 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 9def79c333d..d0379c5bf3c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -875,7 +875,7 @@ export default class BattleScene extends SceneBase { * 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. * @param activeOnly - Whether to consider only active pokemon (as described by {@linkcode Pokemon.isActive()}); default `false`. - * If `true`, will also elide all `null` values from the array. + * If `true`, will also remove all `null` values from the array. * @returns An array of {@linkcode Pokemon}, as described above. */ public getField(activeOnly = false): Pokemon[] { @@ -3354,6 +3354,8 @@ export default class BattleScene extends SceneBase { return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); } + // TODO: Add overload signature to `findModifier` and `findModifiers` for functions of the form `(x): X is T` + /** * Get all of the modifiers that pass the `modifierFilter` function * @param modifierFilter - The function used to filter a target's modifiers diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 60f3f194c21..3e3fbde7793 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1202,7 +1202,10 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr { export class FieldMultiplyStatAbAttr extends AbAttr { private stat: Stat; private multiplier: number; - /** Whether this ability can stack with others of the same type for this stat; default `false` */ + /** + * Whether this ability can stack with others of the same type for this stat. + * @defaultValue `false` + */ private canStack: boolean; constructor(stat: Stat, multiplier: number, canStack = false) { @@ -1214,8 +1217,12 @@ export class FieldMultiplyStatAbAttr extends AbAttr { } canApplyFieldStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: Stat, _statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, _args: any[]): boolean { - return (this.canStack || !hasApplied.value) - && this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat); + return ( + (this.canStack || !hasApplied.value) + && this.stat === stat + // targets with the same stat-changing ability as this are unaffected + && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => attr.stat !== stat) + ); } /** @@ -1246,7 +1253,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { /** * Determine if the move type change attribute can be applied. - * + * * Can be applied if: * - 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 Moves.MULTI_ATTACK} @@ -1341,37 +1348,35 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { /** * Class for abilities that add additional strikes to single-target moves. * Used by {@linkcode Moves.PARENTAL_BOND | Parental Bond}. + * + * @param damageMultiplier - The damage multiplier for the added strike, relative to the first. */ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { private damageMultiplier: number; - - /** - * @param damageMultiplier - The damage multiplier for the added strike, relative to the first. - */ constructor(damageMultiplier: number) { super(false); this.damageMultiplier = damageMultiplier; } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?]): boolean { return move.canBeMultiStrikeEnhanced(pokemon, true); } /** - * Applies the ability attribute by increasing hit count + * Increases the move's hit count or modifies the move's damage. * @param pokemon - The {@linkcode Pokemon} using the move * @param passive - Unused * @param defender - Unused * @param move - The {@linkcode Move} being used - * @param args: + * @param args - * - `[0]` - A {@linkcode NumberHolder} holding the move's current strike count * - `[1]` - A {@linkcode NumberHolder} holding the current strike's damage multiplier. */ - // TODO: Why can these be nullish? - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { - const hitCount = args[0] as NumberHolder; - const multiplier = args[1] as NumberHolder; + // TODO: Review if these args can be undefined when called (and remove the ? if not) + override applyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon, _move: Move, args: [NumberHolder?, NumberHolder?]): void { + const hitCount = args[0]; + const multiplier = args[1]; if (hitCount?.value) { hitCount.value += 1; } @@ -3147,10 +3152,11 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { } /** - * Attribute to apply confusion to the target whenever the user - * directly statuses them with a move. + * Attribute to apply confusion to the target whenever the user directly statuses them with a move. + * * Used by {@linkcode Abilities.POISON_PUPPETEER} - * Called in {@linkcode StatusEffectAttr}. + * + * Called in {@linkcode StatusEffectAttr} * @extends PostAttackAbAttr * @see {@linkcode applyPostAttack} */ @@ -3338,8 +3344,8 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param simulated - Unused * @param stat - The {@linkcode Stat} being affected * @param cancelled - {@linkcode BooleanHolder} containing whether the stat change was already prevented - * @param args `[0]` - The {@linkcode Pokemon} recieving the stat change - * @returns `true` if the ability can be applied + * @param args - `[0]`: The {@linkcode Pokemon} receiving the stat change + * @returns `true` if the ability can be applied */ override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon]): boolean { const target = args[0]; @@ -3534,9 +3540,9 @@ export class ConditionalCritAbAttr extends AbAttr { * @param simulated - Unused * @param cancelled - Unused * @param args - - * - [0] A {@linkcode BooleanHolder} containing whether critical hits are guaranteed. - * - [1] The {@linkcode Pokemon} being targeted (unused) - * - [2] The {@linkcode Move} used by the ability holder (unused) + * - `[0]` A {@linkcode BooleanHolder} containing whether critical hits are guaranteed. + * - `[1]` The {@linkcode Pokemon} being targeted (unused) + * - `[2]` The {@linkcode Move} used by the ability holder (unused) */ override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: [BooleanHolder, Pokemon, Move, ...any]): void { (args[0] as BooleanHolder).value = true; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 4ddac7e63a8..fa74ba60bbf 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -69,9 +69,9 @@ export abstract class ArenaTag { /** * Trigger this {@linkcode ArenaTag}'s effect, reducing its duration as applicable. - * Will ignore duration of all tags with durations alwrea + * 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 super classes. + * 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 { @@ -881,7 +881,6 @@ class ToxicSpikesTag extends ArenaTrapTag { * Arena Tag class for delayed attacks, such as {@linkcode Moves.FUTURE_SIGHT} or {@linkcode Moves.DOOM_DESIRE}. * Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used), * and deals damage after the turn count is reached. - // TODO: Add class for tags that can have multiple instances up and edit `arena.addTag` appropriately */ export class DelayedAttackTag extends ArenaTag { public targetIndex: BattlerIndex; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f06397d0596..281ad564ea4 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -201,7 +201,7 @@ export default class Move implements Localizable { /** * Check if a move has an attribute that matches `attrType` * @param attrType - The constructor of a {@linkcode MoveAttr} - * @returns Whether if the move has an attribute that is/extends `attrType` + * @returns Whether this move has an attribute matching `attrType` */ hasAttr(attrType: Constructor): boolean { return this.attrs.some((attr) => attr instanceof attrType); @@ -209,11 +209,13 @@ export default class Move implements Localizable { /** * Find the first attribute that matches a given predicate function. - * @param attrPredicate - The predicate function to search `MoveAttr`s by. - * @returns The first {@linkcode MoveAttr} for which `attrPredicate` returns a value coercible to the boolean value `true`. + * @param attrPredicate - The predicate function to search `MoveAttr`s by. + * @returns The first {@linkcode MoveAttr} for which `attrPredicate` returns `true`. */ 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)!; } /** @@ -241,7 +243,7 @@ export default class Move implements Localizable { * Adds a new MoveAttr to this move (appends to the attr array). * If the MoveAttr also comes with a condition, it is added to its {@linkcode MoveCondition} array. * - * Almost identical to {@linkcode attr}, except taking already instantized {@linkcode MoveAttr} object + * Similar to {@linkcode attr}, except this takes an already instantiated {@linkcode MoveAttr} object * as opposed to a constructor and its arguments. * @param attrAdd - The {@linkcode MoveAttr} to add * @returns `this` @@ -371,7 +373,7 @@ export default class Move implements Localizable { /** * Adds a condition to this move (in addition to any provided by its prior {@linkcode MoveAttr}s). - * The move will fail upon use if at least 1 of its conditions is not met. + * The move will fail upon use if at least 1 of its conditions is not met. * @param condition - The {@linkcode MoveCondition} or {@linkcode MoveConditionFunc} to add to the conditions array. * @returns `this` */ @@ -386,8 +388,8 @@ export default class Move implements Localizable { /** * Mark a move as having one or more edge cases. - * The move may lack certain niche interactions with other moves/abilities, but still functions - * as intended in most cases. + * 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` @@ -397,8 +399,8 @@ export default class Move implements Localizable { } /** - * Mark a move as partially implemented. - * Partial moves are expected to have their core functionality implemented, but may lack + * Mark this move as partially implemented. + * 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` */ @@ -408,9 +410,9 @@ export default class Move implements Localizable { } /** - * Mark a move as unimplemented. + * Mark this move as unimplemented. * Unimplemented moves are ones which have _none_ of their basic functionality enabled, - * and are effectively barred from use by the AI. + * and will fail upon use. * @returns `this` */ unimplemented(): this { @@ -976,10 +978,8 @@ export class AttackMove extends Move { constructor(id: Moves, 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); - /** - * {@link https://bulbapedia.bulbagarden.net/wiki/Freeze_(status_condition)} - * All damaging Fire-type moves can thaw a frozen target, regardless of whether or not they have a chance to burn - */ + // > All damaging Fire-type moves can... thaw a frozen target, regardless of whether or not they have a chance to burn. + // - https://bulbapedia.bulbagarden.net/wiki/Freeze_(status_condition) if (this.type === PokemonType.FIRE) { this.addAttr(new HealStatusEffectAttr(false, StatusEffect.FREEZE)); } @@ -2865,6 +2865,8 @@ 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` export class BypassSleepAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.status?.effect === StatusEffect.SLEEP) { @@ -2882,7 +2884,7 @@ export class BypassSleepAttr extends MoveAttr { * @param move */ 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; } } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 243eab58b27..39d753bd816 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -404,7 +404,7 @@ export async function applyModifierTypeToPlayerPokemon( // Check if the Pokemon has max stacks of that item already const modifier = modType.newModifier(pokemon); const existing = globalScene.findModifier( - m => + (m): m is PokemonHeldItemModifier => m instanceof PokemonHeldItemModifier && m.type.id === modType.id && m.pokemonId === pokemon.id && diff --git a/src/field/arena.ts b/src/field/arena.ts index bcd1d32586f..f083180490b 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -691,7 +691,6 @@ export class Arena { targetIndex?: BattlerIndex, ): boolean { const existingTag = this.getTagOnSide(tagType, side); - // TODO: Change this for future sight if (existingTag) { existingTag.onOverlap(this, globalScene.getPokemonById(sourceId)); @@ -704,7 +703,6 @@ export class Arena { } // creates a new tag object - // TODO: check if we can remove the `|| 0` since turn count should never be nullish const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); if (newTag) { newTag.onAdd(this, quiet); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 46b6a6771c2..46ebd355d88 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -586,7 +586,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns The {@linkcode PokeballType} that will be shown when this Pokemon is sent out into battle. */ getPokeball(useIllusion = false): PokeballType { - return useIllusion && this.summonData?.illusion + return useIllusion && this.summonData.illusion ? this.summonData.illusion.pokeball : this.pokeball; } @@ -645,7 +645,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Checks if a pokemon is fainted (ie: its `hp <= 0`). - * Usually should not be called directly in favor of consulting {@linkcode isAllowedInBattle()}. + * Usually should not be called directly in favor of calling {@linkcode isAllowedInBattle()}. * @param checkStatus - Whether to also check that the pokemon's status is {@linkcode StatusEffect.FAINT}; default `false` * @returns Whether this Pokemon is fainted, as described above. */ @@ -2270,9 +2270,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Get this Pokemon's non-passive {@linkcode Ability}, factoring in fusions, overrides and ability-changing effects. - * Should rarely be called directky in favor of {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr}, + * Should rarely be called directly in favor of {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr}, * both of which check both ability slots and account for suppression. - * @see {@linkcode hasAbility}and {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases + * @see {@linkcode hasAbility} and {@linkcode hasAbilityWithAttr} are the intended ways to check abilities in most cases * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false` * @returns The non-passive {@linkcode Ability} of this Pokemon. */ @@ -6424,7 +6424,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * 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. - * @param heldItem The item stack to be reduced. + * @param heldItem - The item stack to be reduced. * @param forBattle - Whether to trigger in-battle effects (such as Unburden) after losing the item. Default: `true` * Should be `false` for all item loss occurring outside of battle (MEs, etc.). * @returns Whether the item was removed successfully. diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index fa8a2d400d3..c653cd70cbe 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -62,25 +62,11 @@ const iconOverflowIndex = 24; export const modifierSortFunc = (a: Modifier, b: Modifier): number => { const itemNameMatch = a.type.name.localeCompare(b.type.name); const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); - const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; - const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; + const aId = a instanceof PokemonHeldItemModifier ? a.pokemonId : -1; + const bId = b instanceof PokemonHeldItemModifier ? b.pokemonId : -1; - // First sort by pokemonID - if (aId < bId) { - 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; + // First sort by pokemon ID, then by item type and then name + return aId - bId || typeNameMatch || itemNameMatch; }; export class ModifierBar extends Phaser.GameObjects.Container { From d744e832b2cc01b2aa8a0367079720dcef85e965 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 27 May 2025 21:15:56 -0400 Subject: [PATCH 06/10] Update move.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/moves/move.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 281ad564ea4..2ce6dbb254b 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -412,7 +412,7 @@ export default class Move implements Localizable { /** * Mark this move as unimplemented. * Unimplemented moves are ones which have _none_ of their basic functionality enabled, - * and will fail upon use. + * and cannot be used. * @returns `this` */ unimplemented(): this { From 42481c819a8ff0f509b20bec718d4147c67afc09 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 9 Jun 2025 22:56:49 -0400 Subject: [PATCH 07/10] Fixed ab code --- src/data/abilities/ability.ts | 95 ++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 923494b6910..a8293f2b783 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1924,10 +1924,12 @@ export class FieldMultiplyStatAbAttr extends AbAttr { _args: any[], ): boolean { return ( - (this.canStack || !hasApplied.value) - && this.stat === stat + (this.canStack || !hasApplied.value) && + this.stat === stat && // targets with the same stat-changing ability as this are unaffected - && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => attr.stat !== stat) + checkedPokemon + .getAbilityAttrs(FieldMultiplyStatAbAttr) + .every(attr => attr.stat !== stat) ); } @@ -2095,16 +2097,23 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { * Used by {@linkcode Moves.PARENTAL_BOND | Parental Bond}. * * @param damageMultiplier - The damage multiplier for the added strike, relative to the first. -*/ + */ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { private damageMultiplier: number; - constructor(damageMultiplier: number) { + constructor(damageMultiplier: number) { super(false); this.damageMultiplier = damageMultiplier; } - override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?]): boolean { + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon | null, + move: Move, + _args: [NumberHolder?, NumberHolder?], + ): boolean { return move.canBeMultiStrikeEnhanced(pokemon, true); } @@ -2119,7 +2128,14 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { * - `[1]` - A {@linkcode NumberHolder} holding the current strike's damage multiplier. */ // TODO: Review if these args can be undefined when called (and remove the ? if not) - override applyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon, _move: Move, args: [NumberHolder?, NumberHolder?]): void { + override applyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + args: [NumberHolder?, NumberHolder?], + ): void { const hitCount = args[0]; const multiplier = args[1]; if (hitCount?.value) { @@ -4188,20 +4204,32 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { this.effects = new Set(effects); } - /** - * Check that the target was inflicted by the correct status condition from this Pokemon - * and can be confused. - * @param pokemon - {The @linkcode Pokemon} with this ability - * @param passive - N/A - * @param defender - The {@linkcode Pokemon} being targeted (will be confused) - * @param move - The {@linkcode Move} that applied the status effect - * @param hitResult - N/A - * @param args `[0]` - The {@linkcode StatusEffect} applied by the move - * @returns `true` if the target can be confused after being statused. + /** + * Check that the target was inflicted by the correct status condition from this Pokemon + * and can be confused. + * @param pokemon - {The @linkcode Pokemon} with this ability + * @param passive - N/A + * @param defender - The {@linkcode Pokemon} being targeted (will be confused) + * @param move - The {@linkcode Move} that applied the status effect + * @param hitResult - N/A + * @param args `[0]` - The {@linkcode StatusEffect} applied by the move + * @returns `true` if the target can be confused after being statused. */ - override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: [StatusEffect]): boolean { - return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) - && this.effects.has(args[0]) && !defender.isFainted() && defender.canAddTag(BattlerTagType.CONFUSED); + override canApplyPostAttack( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + args: [StatusEffect], + ): boolean { + return ( + super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && + this.effects.has(args[0]) && + !defender.isFainted() && + defender.canAddTag(BattlerTagType.CONFUSED) + ); } /** @@ -4398,8 +4426,15 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param cancelled - {@linkcode BooleanHolder} containing whether the stat change was already prevented * @param args - `[0]`: The {@linkcode Pokemon} receiving the stat change * @returns `true` if the ability can be applied - */ - override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon]): boolean { + */ + override canApplyPreStatStageChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + stat: BattleStat, + cancelled: BooleanHolder, + args: [Pokemon], + ): boolean { const target = args[0]; if (!target) { return false; @@ -4647,7 +4682,13 @@ export class ConditionalCritAbAttr extends AbAttr { * - `[1]` The {@linkcode Pokemon} being targeted (unused) * - `[2]` The {@linkcode Move} used by the ability holder (unused) */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: [BooleanHolder, Pokemon, Move, ...any]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: [BooleanHolder, Pokemon, Move, ...any], + ): void { (args[0] as BooleanHolder).value = true; } } @@ -5676,9 +5717,13 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { continue; } - if (!opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + const cancelled = new BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, opp, cancelled, false); + if (!cancelled) { opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); - globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }), + ); } } } From 673f174b3f8258fcf6c43d5b529c118ea5febff8 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 9 Jun 2025 23:11:14 -0400 Subject: [PATCH 08/10] Added comment for BattlerIndex --- src/enums/battler-index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/enums/battler-index.ts b/src/enums/battler-index.ts index 32b1684c86c..253e5bfc3ed 100644 --- a/src/enums/battler-index.ts +++ b/src/enums/battler-index.ts @@ -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 { ATTACKER = -1, PLAYER, From de200542c0460643aeb74112ebc4744ed8bd5a1b Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 16 Jun 2025 16:44:42 -0400 Subject: [PATCH 09/10] ddd --- src/battle-scene.ts | 18 ++++++++---------- src/data/moves/move.ts | 6 +++--- src/phases/command-phase.ts | 1 - 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 2a2308307eb..68029fe3d90 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -895,7 +895,8 @@ export default class BattleScene extends SceneBase { } getPokemonById(pokemonId: number): Pokemon | null { - return (this.getPlayerParty() as Pokemon[]).concat(this.getEnemyParty()).find(p => p.id === pokemonId) ?? null; + const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); + return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null; } addPlayerPokemon( @@ -2599,7 +2600,6 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } - // TODO: refactor this addModifier( modifier: Modifier | null, ignoreUpdate?: boolean, @@ -3038,22 +3038,20 @@ export default class BattleScene extends SceneBase { return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); } - // TODO: Add overload signature to `findModifier` and `findModifiers` for functions of the form `(x): X is T` - /** * Get all of the modifiers that pass the `modifierFilter` function - * @param modifierFilter - The function used to filter a target's modifiers - * @param isPlayer - Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` - * @returns an array of {@linkcode PersistentModifier}s that passed the `modifierFilter` function + * @param modifierFilter The function used to filter a target's modifiers + * @param isPlayer Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @returns the list of all modifiers that passed the `modifierFilter` function */ findModifiers(modifierFilter: ModifierPredicate, isPlayer = true): PersistentModifier[] { return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter); } /** - * Find the first modifier that passes the `modifierFilter` function - * @param modifierFilter - The function used to filter a target's modifiers - * @param player - Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * Find the first modifier that pass the `modifierFilter` function + * @param modifierFilter The function used to filter a target's modifiers + * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @returns the first modifier that passed the `modifierFilter` function; `undefined` if none passed */ findModifier(modifierFilter: ModifierPredicate, player = true): PersistentModifier | undefined { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 1c76a4cbdc5..08e38dddbef 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -132,10 +132,10 @@ export default abstract class Move implements Localizable { /** * Check if the move is of the given subclass without requiring `instanceof`. - * + * * ⚠️ Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those, * use {@linkcode isChargingMove} instead. - * + * * @param moveKind - The string name of the move to check against * @returns Whether this move is of the provided type. */ @@ -2916,7 +2916,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { * Used by {@linkcode Moves.SNORE} and {@linkcode Moves.SLEEP_TALK}. */ // TODO: Should this use a battler tag? -// TODO: Give this `userSleptOrComatoseCondition` +// TODO: Give this `userSleptOrComatoseCondition` by default export class BypassSleepAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.status?.effect === StatusEffect.SLEEP) { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 5ab857ad67b..edacea338dd 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -86,7 +86,6 @@ 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. - // TODO: Why do we handle this here of all places const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag | undefined; if (encoreTag) { this.getPokemon().lapseTag(BattlerTagType.ENCORE); From ca57a1b7e379d7fb81931fe40b0a8b8e59f520f5 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 16 Jun 2025 16:47:32 -0400 Subject: [PATCH 10/10] ren biome --- src/data/abilities/ability.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8bc123ebfad..2d8167bef5e 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2157,7 +2157,9 @@ export class FieldMultiplyStatAbAttr extends AbAttr { (!hasApplied.value && this.stat === stat && // targets with the same stat-changing ability as this are unaffected - checkedPokemon.getAbilityAttrs("FieldMultiplyStatAbAttr").every(attr => attr.stat !== stat)) + checkedPokemon + .getAbilityAttrs("FieldMultiplyStatAbAttr") + .every(attr => attr.stat !== stat)) ); }