Squashed changes into 1 commit, reverted unneeded stuff

This commit is contained in:
Bertie690 2025-05-20 12:30:21 -04:00
parent abb4ec2781
commit d3414b7126
16 changed files with 465 additions and 315 deletions

View File

@ -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. * Returns an array of Pokemon on both sides of the battle - player first, then enemy.
* Does not actually check if the pokemon are on the field or not, and always has length 4 regardless of battle type. * Does not actually check if the pokemon are on the field or not, and always has length 4 regardless of battle type.
* @param activeOnly - Whether to consider only active pokemon; default `false` * @param activeOnly - Whether to consider only active pokemon (as described by {@linkcode Pokemon.isActive()}); default `false`.
* If `true`, will also elide all `null` values from the array.
* @returns An array of {@linkcode Pokemon}, as described above. * @returns An array of {@linkcode Pokemon}, as described above.
*/ */
public getField(activeOnly = false): Pokemon[] { public getField(activeOnly = false): Pokemon[] {
@ -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 * Attempt to redirect a move in double battles from a fainted/removed Pokemon to its ally.
* @param removedPokemon {@linkcode Pokemon} the pokemon that is being removed from the field (flee, faint), moves to be redirected FROM * @param removedPokemon - The {@linkcode Pokemon} having been removed from the field.
* @param allyPokemon {@linkcode Pokemon} the pokemon that will have the moves be redirected TO * @param allyPokemon - The {@linkcode Pokemon} allied with the removed Pokemon; will have moves redirected to it
*/ */
redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void { redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void {
// failsafe: if not a double battle just return // failsafe: if not a double battle just return
@ -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 * Returns the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere
* @param isEnemy Whether to return the enemy's modifier bar * @param isEnemy - Whether to return the enemy modifier bar instead of the player bar; default `false`
* @returns {ModifierBar} * @returns The {@linkcode ModifierBar} for the given side of the field
*/ */
getModifierBar(isEnemy?: boolean): ModifierBar { getModifierBar(isEnemy = false): ModifierBar {
return isEnemy ? this.enemyModifierBar : this.modifierBar; return isEnemy ? this.enemyModifierBar : this.modifierBar;
} }
@ -933,8 +934,7 @@ export default class BattleScene extends SceneBase {
} }
getPokemonById(pokemonId: number): Pokemon | null { getPokemonById(pokemonId: number): Pokemon | null {
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); return (this.getPlayerParty() as Pokemon[]).concat(this.getEnemyParty()).find(p => p.id === pokemonId) ?? null;
return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null;
} }
addPlayerPokemon( addPlayerPokemon(
@ -1472,10 +1472,12 @@ export default class BattleScene extends SceneBase {
if (!waveIndex && lastBattle) { if (!waveIndex && lastBattle) {
const isNewBiome = this.isNewBiome(lastBattle); const isNewBiome = this.isNewBiome(lastBattle);
/** Whether to reset and recall pokemon */
const resetArenaState = const resetArenaState =
isNewBiome || isNewBiome ||
[BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) || [BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) ||
this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS; this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
for (const enemyPokemon of this.getEnemyParty()) { for (const enemyPokemon of this.getEnemyParty()) {
enemyPokemon.destroy(); enemyPokemon.destroy();
} }
@ -1847,7 +1849,7 @@ export default class BattleScene extends SceneBase {
} }
resetSeed(waveIndex?: number): void { resetSeed(waveIndex?: number): void {
const wave = waveIndex || this.currentBattle?.waveIndex || 0; const wave = waveIndex ?? this.currentBattle?.waveIndex ?? 0;
this.waveSeed = shiftCharCodes(this.seed, wave); this.waveSeed = shiftCharCodes(this.seed, wave);
Phaser.Math.RND.sow([this.waveSeed]); Phaser.Math.RND.sow([this.waveSeed]);
console.log("Wave Seed:", this.waveSeed, wave); console.log("Wave Seed:", this.waveSeed, wave);
@ -2913,6 +2915,7 @@ export default class BattleScene extends SceneBase {
return Math.floor(moneyValue / 10) * 10; return Math.floor(moneyValue / 10) * 10;
} }
// TODO: refactor this
addModifier( addModifier(
modifier: Modifier | null, modifier: Modifier | null,
ignoreUpdate?: boolean, ignoreUpdate?: boolean,
@ -3353,18 +3356,18 @@ export default class BattleScene extends SceneBase {
/** /**
* Get all of the modifiers that pass the `modifierFilter` function * Get all of the modifiers that pass the `modifierFilter` function
* @param modifierFilter The function used to filter a target's modifiers * @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` * @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 * @returns an array of {@linkcode PersistentModifier}s that passed the `modifierFilter` function
*/ */
findModifiers(modifierFilter: ModifierPredicate, isPlayer = true): PersistentModifier[] { findModifiers(modifierFilter: ModifierPredicate, isPlayer = true): PersistentModifier[] {
return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter); return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter);
} }
/** /**
* Find the first modifier that pass the `modifierFilter` function * Find the first modifier that passes the `modifierFilter` function
* @param modifierFilter The function used to filter a target's modifiers * @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` * @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 * @returns the first modifier that passed the `modifierFilter` function; `undefined` if none passed
*/ */
findModifier(modifierFilter: ModifierPredicate, player = true): PersistentModifier | undefined { findModifier(modifierFilter: ModifierPredicate, player = true): PersistentModifier | undefined {

View File

@ -117,19 +117,33 @@ export class Ability implements Localizable {
return this; 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 { partial(): this {
this.nameAppend += " (P)"; this.nameAppend += " (P)";
return this; return this;
} }
/**
* Mark an ability as unimplemented.
* Unimplemented abilities are ones which have _none_ of their basic functionality enabled.
* @returns `this`
*/
unimplemented(): this { unimplemented(): this {
this.nameAppend += " (N)"; this.nameAppend += " (N)";
return this; return this;
} }
/** /**
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case. * Mark an ability as having an edge case.
* @returns the ability * Does not show up in game and is solely for internal dev use.
* When using this, make sure to **document the edge case** (or else this becomes pointless).
* @returns `this`
*/ */
edgeCase(): this { edgeCase(): this {
return this; return this;

View File

@ -361,7 +361,6 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
* @param move {@linkcode Move} The attacking move. * @param move {@linkcode Move} The attacking move.
* @param cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. * @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 [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 { override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void {
(args[0] as NumberHolder).value = 0; (args[0] as NumberHolder).value = 0;
@ -1203,6 +1202,7 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
export class FieldMultiplyStatAbAttr extends AbAttr { export class FieldMultiplyStatAbAttr extends AbAttr {
private stat: Stat; private stat: Stat;
private multiplier: number; private multiplier: number;
/** Whether this ability can stack with others of the same type for this stat; default `false` */
private canStack: boolean; private canStack: boolean;
constructor(stat: Stat, multiplier: number, canStack = false) { constructor(stat: Stat, multiplier: number, canStack = false) {
@ -1213,13 +1213,13 @@ export class FieldMultiplyStatAbAttr extends AbAttr {
this.canStack = canStack; this.canStack = canStack;
} }
canApplyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, args: any[]): boolean { canApplyFieldStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: Stat, _statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, _args: any[]): boolean {
return this.canStack || !hasApplied.value return (this.canStack || !hasApplied.value)
&& this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat); && 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 pokemon {@linkcode Pokemon} the Pokemon using this ability
* @param passive {@linkcode boolean} unused * @param passive {@linkcode boolean} unused
* @param stat {@linkcode Stat} the type of the checked stat * @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: * Can be applied if:
* - The ability's condition is met, e.g. pixilate only boosts normal moves, * - The ability's condition is met, e.g. pixilate only boosts normal moves,
@ -1255,12 +1255,13 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
* @param pokemon - The pokemon that has the move type changing ability and is using the attacking move * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move
* @param _passive - Unused * @param _passive - Unused
* @param _simulated - Unused * @param _simulated - Unused
* @param _defender - The pokemon being attacked (unused) * @param _defender - Unused
* @param move - The move being used * @param move - The {@linkcode Move} being used
* @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier * @param _args - Unused
* @returns whether the move type change attribute can be applied * @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)) && return (!this.condition || this.condition(pokemon, _defender, move)) &&
!noAbilityTypeOverrideMoves.has(move.id) && !noAbilityTypeOverrideMoves.has(move.id) &&
(!pokemon.isTerastallized || (!pokemon.isTerastallized ||
@ -1269,13 +1270,17 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
} }
/** /**
* @param pokemon - The pokemon that has the move type changing ability and is using the attacking move * Apply the ability attribute to change the move's type and/or boost its power.
* @param passive - Unused * @param _pokemon - Unused
* @param simulated - Unused * @param _passive - Unused
* @param defender - The pokemon being attacked (unused) * @param _simulated - Unused
* @param move - The move being used * @param _defender - Unused
* @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier * @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 { override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: [NumberHolder?, NumberHolder?, ...any]): void {
if (args[0] && args[0] instanceof NumberHolder) { if (args[0] && args[0] instanceof NumberHolder) {
args[0].value = this.newType; args[0].value = this.newType;
@ -1334,12 +1339,15 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
} }
/** /**
* Class for abilities that convert single-strike moves to two-strike moves (i.e. Parental Bond). * Class for abilities that add additional strikes to single-target moves.
* @param damageMultiplier the damage multiplier for the second strike, relative to the first. * Used by {@linkcode Moves.PARENTAL_BOND | Parental Bond}.
*/ */
export class AddSecondStrikeAbAttr extends PreAttackAbAttr { export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
private damageMultiplier: number; private damageMultiplier: number;
/**
* @param damageMultiplier - The damage multiplier for the added strike, relative to the first.
*/
constructor(damageMultiplier: number) { constructor(damageMultiplier: number) {
super(false); super(false);
@ -1351,16 +1359,16 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
} }
/** /**
* If conditions are met, this doubles the move's hit count (via args[1]) * Applies the ability attribute by increasing hit count
* or multiplies the damage of secondary strikes (via args[2]) * @param pokemon - The {@linkcode Pokemon} using the move
* @param pokemon the {@linkcode Pokemon} using the move * @param passive - Unused
* @param passive n/a * @param defender - Unused
* @param defender n/a * @param move - The {@linkcode Move} being used
* @param move the {@linkcode Move} used by the ability source * @param args:
* @param args Additional arguments: * - `[0]` - A {@linkcode NumberHolder} holding the move's current strike count
* - `[0]` the number of strikes this move currently has ({@linkcode NumberHolder}) * - `[1]` - A {@linkcode NumberHolder} holding the current strike's damage multiplier.
* - `[1]` the damage multiplier for the current strike ({@linkcode NumberHolder})
*/ */
// TODO: Why can these be nullish?
override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void {
const hitCount = args[0] as NumberHolder; const hitCount = args[0] as NumberHolder;
const multiplier = args[1] 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 * 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} * applying the effect of any inherited class.
* for an example of an effect that does not require a damaging move. * This can be changed by providing a different {@linkcode attackCondition} to the constructor
* (see {@linkcode ConfusionOnStatusEffectAbAttr} for an example of this being used).
*/ */
canApplyPostAttack( canApplyPostAttack(
pokemon: Pokemon, pokemon: Pokemon,
@ -3138,36 +3147,47 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
} }
/** /**
* This attribute applies confusion to the target whenever the user * Attribute to apply confusion to the target whenever the user
* directly poisons them with a move, e.g. Poison Puppeteer. * directly statuses them with a move.
* Used by {@linkcode Abilities.POISON_PUPPETEER}
* Called in {@linkcode StatusEffectAttr}. * Called in {@linkcode StatusEffectAttr}.
* @extends PostAttackAbAttr * @extends PostAttackAbAttr
* @see {@linkcode applyPostAttack} * @see {@linkcode applyPostAttack}
*/ */
export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
/** List of effects to apply confusion after */ /** List of effects to apply confusion after */
private effects: StatusEffect[]; private effects: ReadonlySet<StatusEffect>;
constructor(...effects: StatusEffect[]) { constructor(...effects: StatusEffect[]) {
/** This effect does not require a damaging move */ /** This effect does not require a damaging move */
super((user, target, move) => true); 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 {
return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args)
&& this.effects.indexOf(args[0]) > -1 && !defender.isFainted() && defender.canAddTag(BattlerTagType.CONFUSED);
}
/** /**
* Applies confusion to the target pokemon. * Check that the target was inflicted by the correct status condition from this Pokemon
* @param pokemon {@link Pokemon} attacking * 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);
}
/**
* Apply confusion to the target pokemon.
* @param pokemon - {@linkcode Pokemon} with the ability
* @param passive N/A * @param passive N/A
* @param defender {@link Pokemon} defending * @param defender - {@linkcode Pokemon} being targeted (will be confused)
* @param move {@link Move} used to apply status effect and confusion * @param move - N/A
* @param hitResult N/A * @param hitResult - N/A
* @param args [0] {@linkcode StatusEffect} applied by move * @param args - N/A
*/ */
override applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { override applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): void {
if (!simulated) { if (!simulated) {
@ -3313,15 +3333,15 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA
/** /**
* Determine whether the {@linkcode ConditionalUserFieldProtectStatAbAttr} can be applied. * Determine whether the {@linkcode ConditionalUserFieldProtectStatAbAttr} can be applied.
* @param pokemon The pokemon with the ability * @param pokemon - The pokemon with the ability
* @param passive unused * @param passive - unused
* @param simulated Unused * @param simulated - Unused
* @param stat The stat being affected * @param stat - The {@linkcode Stat} being affected
* @param cancelled Holds whether the stat change was already prevented. * @param cancelled - {@linkcode BooleanHolder} containing whether the stat change was already prevented
* @param args Args[0] is the target pokemon of the stat change. * @param args `[0]` - The {@linkcode Pokemon} recieving the stat change
* @returns * @returns `true` if the ability can be applied
*/ */
override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon]): boolean {
const target = args[0]; const target = args[0];
if (!target) { if (!target) {
return false; return false;
@ -3330,12 +3350,12 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA
} }
/** /**
* Apply the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} to an interaction * Apply the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} to block status effect application
* @param _pokemon The pokemon the stat change is affecting (unused) * @param _pokemon - unused
* @param _passive unused * @param _passive - unused
* @param _simulated unused * @param _simulated - unused
* @param stat The stat being affected * @param stat - unused
* @param cancelled Will be set to true if the stat change is prevented * @param cancelled - A {@linkcode BooleanHolder} containing status effect immunity; will be set to `true`
* @param _args unused * @param _args unused
*/ */
override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, cancelled: BooleanHolder, _args: any[]): void { 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. * Apply this ability by enabling guaranteed critical hits.
* @param args [0] {@linkcode BooleanHolder} If true critical hit is guaranteed. * @param pokemon - The {@linkcode Pokemon} with the ability (unused).
* [1] {@linkcode Pokemon} Target. * @param passive - Unused
* [2] {@linkcode Move} used by ability user. * @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; (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 * 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 { export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
private allyTarget: boolean; private allyTarget: boolean;
@ -4298,30 +4323,33 @@ 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 { export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { 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); 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) * Damages all sleeping opponents on turn end equal to 1/8 of their respective max hp (min 1)
* @param pokemon {@linkcode Pokemon} with this ability * @param pokemon - The {@linkcode Pokemon} with this ability
* @param passive N/A * @param passive - Unused
* @param simulated `true` if applying in a simulated call. * @param simulated - Whether to suppress changes to game state during calculations.
* @param args N/A * @param args - Unused
*/ */
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
for (const opp of pokemon.getOpponents()) { if (simulated) {
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { return;
if (!simulated) { }
// 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 }); opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT });
globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }));
} }
} }
} }
} }
}
/** /**
@ -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 { export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr {
private statusEffect: StatusEffect[]; private statusEffect: StatusEffect[];

View File

@ -37,6 +37,8 @@ export enum ArenaTagSide {
ENEMY, 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 { export abstract class ArenaTag {
constructor( constructor(
public tagType: ArenaTagType, public tagType: ArenaTagType,
@ -65,8 +67,17 @@ export abstract class ArenaTag {
onOverlap(_arena: Arena, _source: Pokemon | null): void {} onOverlap(_arena: Arena, _source: Pokemon | null): void {}
/**
* Trigger this {@linkcode ArenaTag}'s effect, reducing its duration as applicable.
* Will ignore 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 { lapse(_arena: Arena): boolean {
return this.turnCount < 1 || !!--this.turnCount; // TODO: Rather than treating negative duration tags as being indefinite,
// make all duration based classes inherit from their own sub-class
return this.turnCount < 1 || --this.turnCount > 0;
} }
getMoveName(): string | null { getMoveName(): string | null {
@ -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}. * 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), * 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. * 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 { export class DelayedAttackTag extends ArenaTag {
public targetIndex: BattlerIndex; public targetIndex: BattlerIndex;

View File

@ -45,15 +45,17 @@ import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
// TODO: Explain what a battlerTagLapseType actually is
export enum BattlerTagLapseType { export enum BattlerTagLapseType {
FAINT, FAINT,
/** Tag lapses while using a (non-follow up) move, potentially halting its execution. */
MOVE, MOVE,
PRE_MOVE, PRE_MOVE,
AFTER_MOVE, AFTER_MOVE,
MOVE_EFFECT, MOVE_EFFECT,
TURN_END, TURN_END,
HIT, 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, AFTER_HIT,
CUSTOM, CUSTOM,
} }
@ -94,10 +96,13 @@ export class BattlerTag {
/** /**
* Tick down this {@linkcode BattlerTag}'s duration. * 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`) * @returns `true` if the tag should be kept (`turnCount` > 0`)
*/ */
lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
// TODO: Maybe flip this (return `true` if tag needs removal)
return --this.turnCount > 0; return --this.turnCount > 0;
} }
@ -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 * 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 * Descendants can override {@linkcode isMoveRestricted} to restrict moves that
* match a condition. A restricted move gets cancelled before it is used. * match a condition. A restricted move gets cancelled before it is used.

View File

@ -121,7 +121,13 @@ import { MoveTarget } from "#enums/MoveTarget";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MultiHitType } from "#enums/MultiHitType"; 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"; import { SelectBiomePhase } from "#app/phases/select-biome-phase";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; 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) * 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} * if the MoveAttr also comes with a condition, also adds that to the {@linkcode MoveCondition} array
* @param AttrType {@linkcode MoveAttr} the constructor of a MoveAttr class * @param attrType - The constructor of a {@linkcode MoveAttr} class to add
* @param args the args needed to instantiate a the given class * @param args - Any additional arguments needed to instantiate the given class
* @returns the called object {@linkcode Move} * @returns `this`
*/ */
attr<T extends Constructor<MoveAttr>>(AttrType: T, ...args: ConstructorParameters<T>): this { attr<T extends Constructor<MoveAttr>>(attrType: T, ...args: ConstructorParameters<T>): this {
const attr = new AttrType(...args); const attr = new attrType(...args);
this.attrs.push(attr); this.attrs.push(attr);
let attrCondition = attr.getCondition(); let attrCondition = attr.getCondition();
if (attrCondition) { if (attrCondition) {
@ -233,10 +239,11 @@ export default class Move implements Localizable {
/** /**
* Adds a new MoveAttr to the move (appends to the attr array) * 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} * if the MoveAttr also comes with a condition, also adds that to the {@linkcode MoveCondition} array
* Almost identical to {@link attr}, except you are passing in a MoveAttr object, instead of a constructor and it's arguments * Almost identical to {@linkcode attr}, except you are passing in an already instantized {@linkcode MoveAttr} object
* @param attrAdd {@linkcode MoveAttr} the attribute to add * as opposed to a constructor and its arguments
* @returns the called object {@linkcode Move} * @param attrAdd - The {@linkcode MoveAttr} to add
* @returns `this`
*/ */
addAttr(attrAdd: MoveAttr): this { addAttr(attrAdd: MoveAttr): this {
this.attrs.push(attrAdd); this.attrs.push(attrAdd);
@ -262,13 +269,13 @@ export default class Move implements Localizable {
} }
/** /**
* Getter function that returns if this Move has a MoveFlag * Getter function that returns if this Move has a given MoveFlag.
* @param flag {@linkcode MoveFlags} to check * @param flag - The {@linkcode MoveFlags} to check
* @returns boolean * @returns Whether this Move has the specified flag.
*/ */
hasFlag(flag: MoveFlags): boolean { hasFlag(flag: MoveFlags): boolean {
// internally it is taking the bitwise AND (MoveFlags are represented as bit-shifts) and returning False if result is 0 and true otherwise // Flags are internally represented as bitmasks, so we check by taking the bitwise AND.
return !!(this.flags & flag); return (this.flags & flag) !== MoveFlags.NONE;
} }
/** /**
@ -384,8 +391,10 @@ export default class Move implements Localizable {
} }
/** /**
* Marks the move as "partial": appends texts to the move name * Mark a move as partially implemented.
* @returns the called object {@linkcode Move} * 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 { partial(): this {
this.nameAppend += " (P)"; this.nameAppend += " (P)";
@ -393,8 +402,10 @@ export default class Move implements Localizable {
} }
/** /**
* Marks the move as "unimplemented": appends texts to the move name * Mark a move as unimplemented.
* @returns the called object {@linkcode Move} * 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 { unimplemented(): this {
this.nameAppend += " (N)"; this.nameAppend += " (N)";
@ -961,7 +972,7 @@ export class AttackMove extends Move {
/** /**
* {@link https://bulbapedia.bulbagarden.net/wiki/Freeze_(status_condition)} * {@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) { if (this.type === PokemonType.FIRE) {
this.addAttr(new HealStatusEffectAttr(false, StatusEffect.FREEZE)); 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 * Applies the effect of {@linkcode Moves.PSYCHO_SHIFT} to its target.
* Psycho Shift takes the user's status effect and passes it onto the target. The user is then healed after the move has been successfully executed. * Psycho Shift takes the user's status effect and passes it onto the target.
* @returns `true` if Psycho Shift's effect is able to be applied to the target * The user is then healed after the move has been successfully executed.
* @param user - The {@linkcode Pokemon} using the move
* @param target - The {@linkcode Pokemon} targeted by the move.
* @returns - Whether the effect was successfully applied to the target.
*/ */
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined); 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 { export class BypassSleepAttr extends MoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (user.status?.effect === StatusEffect.SLEEP) { if (user.status?.effect === StatusEffect.SLEEP) {
@ -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); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (cancelled.value) { 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. * 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 { export class RepeatMoveAttr extends MoveEffectAttr {
constructor() { constructor() {

View File

@ -409,7 +409,7 @@ export async function applyModifierTypeToPlayerPokemon(
m.type.id === modType.id && m.type.id === modType.id &&
m.pokemonId === pokemon.id && m.pokemonId === pokemon.id &&
m.matchType(modifier), m.matchType(modifier),
) as PokemonHeldItemModifier; ) as PokemonHeldItemModifier | undefined;
// At max stacks // At max stacks
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {

View File

@ -1,3 +1,7 @@
/**
* A list of possible flags that various moves may have.
* Represented internally as a bitmask.
*/
export enum MoveFlags { export enum MoveFlags {
NONE = 0, NONE = 0,
MAKES_CONTACT = 1 << 0, MAKES_CONTACT = 1 << 0,

View File

@ -691,6 +691,7 @@ export class Arena {
targetIndex?: BattlerIndex, targetIndex?: BattlerIndex,
): boolean { ): boolean {
const existingTag = this.getTagOnSide(tagType, side); const existingTag = this.getTagOnSide(tagType, side);
// TODO: Change this for future sight
if (existingTag) { if (existingTag) {
existingTag.onOverlap(this, globalScene.getPokemonById(sourceId)); existingTag.onOverlap(this, globalScene.getPokemonById(sourceId));
@ -703,6 +704,7 @@ export class Arena {
} }
// creates a new tag object // 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); const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
if (newTag) { if (newTag) {
newTag.onAdd(this, quiet); newTag.onAdd(this, quiet);

View File

@ -486,7 +486,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.ivs = ivs || getIvsFromId(this.id); this.ivs = ivs || getIvsFromId(this.id);
if (this.gender === undefined) { if (this.gender === undefined) {
this.generateGender(); this.gender = this.species.generateGender();
} }
if (this.formIndex === undefined) { if (this.formIndex === undefined) {
@ -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) { getNameToRender(useIllusion: boolean = true) {
const name: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.name : this.name; 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; const nickname: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.nickname : this.nickname;
try { try {
if (nickname) { if (nickname) {
return decodeURIComponent(escape(atob(nickname))); return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually...
} }
return name; return name;
} catch (err) { } catch (err) {
@ -578,12 +580,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
getPokeball(useIllusion = false){ /**
if(useIllusion){ * Return this Pokemon's {@linkcode PokeballType}.
return this.summonData.illusion?.pokeball ?? this.pokeball * @param useIllusion - Whether to consider this Pokemon's illusion if present; default `true`
} else { * @returns The {@linkcode PokeballType} that will be shown when this Pokemon is sent out into battle.
return this.pokeball */
} getPokeball(useIllusion = false): PokeballType {
return useIllusion && this.summonData?.illusion
? this.summonData.illusion.pokeball
: this.pokeball;
} }
init(): void { init(): void {
@ -640,9 +645,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Checks if a pokemon is fainted (ie: its `hp <= 0`). * Checks if a pokemon is fainted (ie: its `hp <= 0`).
* It's usually better to call {@linkcode isAllowedInBattle()} * Usually should not be called directly in favor of consulting {@linkcode isAllowedInBattle()}.
* @param checkStatus `true` to also check that the pokemon's status is {@linkcode StatusEffect.FAINT} * @param checkStatus - Whether to also check that the pokemon's status is {@linkcode StatusEffect.FAINT}; default `false`
* @returns `true` if the pokemon is fainted * @returns Whether this Pokemon is fainted, as described above.
*/ */
public isFainted(checkStatus = false): boolean { public isFainted(checkStatus = false): boolean {
return ( 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. * Check if this pokemon is both not fainted and allowed to be used based on currently active challenges.
* @returns {boolean} `true` if pokemon is allowed in battle * @returns Whether this Pokemon is allowed to partake in battle.
*/ */
public isAllowedInBattle(): boolean { public isAllowedInBattle(): boolean {
return !this.isFainted() && this.isAllowedInChallenge(); return !this.isFainted() && this.isAllowedInChallenge();
@ -661,8 +666,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Check if this pokemon is allowed based on any active challenges. * Check if this pokemon is allowed based on any active challenges.
* It's usually better to call {@linkcode isAllowedInBattle()} * Usually should not be called directly in favor of consulting {@linkcode isAllowedInBattle()}.
* @returns {boolean} `true` if pokemon is allowed in battle * @returns Whether this Pokemon is allowed under the current challenge conditions.
*/ */
public isAllowedInChallenge(): boolean { public isAllowedInChallenge(): boolean {
const challengeAllowed = new BooleanHolder(true); const challengeAllowed = new BooleanHolder(true);
@ -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). * Checks if this {@linkcode Pokemon} is allowed in battle (ie: not fainted, and allowed under any active challenges).
* @param onField `true` to also check if the pokemon is currently on the field; default `false` * @param onField - Whether to also check if the pokemon is currently on the field; default `false`
* @returns `true` if the pokemon is "active", as described above. * @returns Whether this pokemon is considered "active", as described above.
* Returns `false` if there is no active {@linkcode BattleScene} or the pokemon is disallowed. * Returns `false` if there is no active {@linkcode BattleScene} or the pokemon is disallowed.
*/ */
public isActive(onField = false): boolean { public isActive(onField = false): boolean {
@ -745,8 +750,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.species.ability2 !== this.species.ability1 ? randSeedInt(2) : 0; 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. * 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; 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<void> { async loadAssets(ignoreOverride = true, useIllusion: boolean = false): Promise<void> {
/** Promises that are loading assets and can be run concurrently. */ /** 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. * Attempt to process variant sprite color caches.
* * @param cacheKey - the cache key for the variant color sprite
* @param cacheKey the cache key for the variant color sprite * @param useExpSprite - Whether experimental sprites should be used if present
* @param useExpSprite should the experimental sprite be used * @param battleSpritePath - the filename of the sprite
* @param battleSpritePath the filename of the sprite
*/ */
async populateVariantColorCache( async populateVariantColorCache(
cacheKey: string, cacheKey: string,
@ -1027,7 +1032,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.fusionSpecies.forms[this.fusionFormIndex].formKey; return this.fusionSpecies.forms[this.fusionFormIndex].formKey;
} }
getSpriteAtlasPath(ignoreOverride?: boolean): string {
// TODO: Add more documentation for all these attributes.
// They may be all similar, but what each one actually _does_ is quite unclear at first glance
getSpriteAtlasPath(ignoreOverride = false): string {
const spriteId = this.getSpriteId(ignoreOverride).replace(/\_{2}/g, "/"); const spriteId = this.getSpriteId(ignoreOverride).replace(/\_{2}/g, "/");
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
} }
@ -1162,12 +1171,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* Get this {@linkcode Pokemon}'s {@linkcode PokemonSpeciesForm}. * Return this Pokemon's {@linkcode PokemonSpeciesForm | SpeciesForm}.
* @param ignoreOverride - Whether to ignore overridden species from {@linkcode Moves.TRANSFORM}, default `false`. * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* This overrides `useIllusion` if `true`. * @param useIllusion - Whether to consider this Pokemon's illusion if present; default `false`.
* @param useIllusion - `true` to use the speciesForm of the illusion; 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) { if (!ignoreOverride && this.summonData.speciesForm) {
return 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 fusionSpecies: PokemonSpecies = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!;
const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; 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 * Calculates and retrieves the final value of a stat considering any held
* items, move effects, opponent abilities, and whether there was a critical * items, move effects, opponent abilities, and whether there was a critical
* hit. * hit.
* @param stat the desired {@linkcode EffectiveStat} * @param stat - The desired {@linkcode EffectiveStat | Stat} to check.
* @param opponent the target {@linkcode Pokemon} * @param opponent - The {@linkcode Pokemon} being targeted, if applicable.
* @param move the {@linkcode Move} being used * @param move - The {@linkcode Move} being used, if any. Used to check ability ignoring effects and similar.
* @param ignoreAbility determines whether this Pokemon's abilities should be ignored during the stat calculation * @param ignoreAbility - Whether to ignore ability effects of the user; default `false`.
* @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation. * @param ignoreOppAbility - Whether to ignore ability effects of the target; default `false`.
* @param ignoreAllyAbility during an attack, determines whether the ally Pokemon's abilities should be ignored during the stat calculation. * @param ignoreAllyAbility - Whether to ignore ability effects of the user's allies; default `false`.
* @param isCritical determines whether a critical hit has occurred or not (`false` by default) * @param isCritical - Whether a critical hit has occurred or not; default `false`.
* @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering * If `true`, will nullify offensive stat drops or defensive stat boosts.
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false` * @param simulated - Whether to nullify any effects that produce changes to game state during calculations; default `true`
* @returns the final in-battle value of a stat * @param ignoreHeldItems - Whether to ignore the user's held items during stat calculation; default `false`.
* @returns The final in-battle value for the given stat.
*/ */
getEffectiveStat( getEffectiveStat(
stat: EffectiveStat, stat: EffectiveStat,
@ -1592,10 +1605,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fieldApplied, fieldApplied,
simulated, simulated,
); );
// TODO: us breaking early effectively makes the ability having a "can stack" toggle useless...
if (fieldApplied.value) { if (fieldApplied.value) {
break; break;
} }
} }
if (!ignoreAbility) { if (!ignoreAbility) {
applyStatMultiplierAbAttrs( applyStatMultiplierAbAttrs(
StatMultiplierAbAttr, StatMultiplierAbAttr,
@ -1608,7 +1624,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const ally = this.getAlly(); const ally = this.getAlly();
if (!isNullOrUndefined(ally)) { 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 = 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) { if (useIllusion && this.summonData.illusion) {
return this.summonData.illusion.gender; return this.summonData.illusion.gender;
} else if (!ignoreOverride && !isNullOrUndefined(this.summonData.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). * Return this Pokemon's fusion's {@linkcode Gender}.
* @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
* @returns The {@linkcode Gender} of this {@linkcode Pokemon}'s fusion.
*/ */
getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { getFusionGender(ignoreOverride = false, useIllusion = false): Gender {
if (useIllusion && this.summonData.illusion?.fusionGender) { if (useIllusion && this.summonData.illusion?.fusionGender) {
return this.summonData.illusion.fusionGender; return this.summonData.illusion.fusionGender;
} else if (!ignoreOverride && !isNullOrUndefined(this.summonData.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 { isShiny(useIllusion: boolean = false): boolean {
if (!useIllusion && this.summonData.illusion) { if (!useIllusion && this.summonData.illusion) {
return this.summonData.illusion.basePokemon?.shiny || (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; 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){ isBaseShiny(useIllusion: boolean = false){
@ -1911,33 +1942,37 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* * Check whether this Pokemon is doubly shiny (both normal and fusion are shiny).
* @param useIllusion - Whether we want the fake or real shininess (illusion ability). * @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
* @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise * @returns Whether this pokemon's base and fusion counterparts are both shiny.
*/ */
isDoubleShiny(useIllusion: boolean = false): boolean { isDoubleShiny(useIllusion = false): boolean {
if (!useIllusion && this.summonData.illusion?.basePokemon) { if (!useIllusion && this.summonData.illusion?.basePokemon) {
return this.isFusion(false) && this.summonData.illusion.basePokemon.shiny && this.summonData.illusion.basePokemon.fusionShiny; 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) { if (!useIllusion && this.summonData.illusion) {
return !this.isFusion(false) return !this.isFusion(false)
? this.summonData.illusion.basePokemon!.variant ? this.summonData.illusion.basePokemon!.variant
: Math.max(this.variant, this.fusionVariant) as Variant; : Math.max(this.variant, this.fusionVariant) as Variant;
} else { }
return !this.isFusion(true) return !this.isFusion(true)
? this.variant ? this.variant
: Math.max(this.variant, this.fusionVariant) as Variant; : Math.max(this.variant, this.fusionVariant) as Variant;
} }
}
// TODO: Clarify how this differs from {@linkcode getVariant}
getBaseVariant(doubleShiny: boolean): Variant { getBaseVariant(doubleShiny: boolean): Variant {
if (doubleShiny) { if (doubleShiny) {
return this.summonData.illusion?.basePokemon?.variant ?? this.variant; return this.summonData.illusion?.basePokemon?.variant ?? this.variant;
@ -1946,47 +1981,55 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getVariant(); return this.getVariant();
} }
/**
* Return this pokemon's overall luck value, based on its shininess (1 pt per variant lvl).
* @returns The luck value of this Pokemon.
*/
getLuck(): number { getLuck(): number {
return this.luck + (this.isFusion() ? this.fusionLuck : 0); return this.luck + (this.isFusion() ? this.fusionLuck : 0);
} }
isFusion(useIllusion: boolean = false): boolean { /**
if (useIllusion && this.summonData.illusion) { * Return whether this {@linkcode Pokemon} is currently fused with anything.
return !!this.summonData.illusion.fusionSpecies; * @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
} * @returns Whether this Pokemon is currently fused with another species.
return !!this.fusionSpecies; */
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) return (!useIllusion && this.summonData.illusion?.basePokemon)
? this.summonData.illusion.basePokemon.name ? this.summonData.illusion.basePokemon.name
: this.name; : this.name;
} }
/** /**
* Checks if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}. * Check whether this {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}.
* @param species the pokemon {@linkcode Species} to check * @param species - The {@linkcode Species} to check against
* @returns `true` if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}, `false` otherwise * @returns Whether this Pokemon is currently fused with the specified {@linkcode Species}.
*/ */
hasFusionSpecies(species: Species): boolean { hasFusionSpecies(species: Species): boolean {
return this.fusionSpecies?.speciesId === species; return this.fusionSpecies?.speciesId === species;
} }
/** /**
* Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it. * Check whether this {@linkcode Pokemon} either is or is fused with the given {@linkcode Species}.
* @param species the pokemon {@linkcode Species} to check * @param species - The {@linkcode Species} to check against
* @param formKey If provided, requires the species to be in that form * @param formKey - If provided, will require the species to be in that form
* @returns `true` if the pokemon is the species or is fused with it, `false` otherwise * @returns Whether this Pokemon has this species as either its base or fusion counterpart.
*/ */
hasSpecies(species: Species, formKey?: string): boolean { hasSpecies(species: Species, formKey?: string): boolean {
if (isNullOrUndefined(formKey)) { if (isNullOrUndefined(formKey)) {
return ( return this.species.speciesId === species || this.fusionSpecies?.speciesId === species;
this.species.speciesId === species ||
this.fusionSpecies?.speciesId === species
);
} }
return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey); 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; 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 = const ret =
!ignoreOverride && this.summonData.moveset !ignoreOverride && this.summonData.moveset
? 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 * Check which egg moves have been unlocked for this {@linkcode Pokemon}.
* on the species it was met at or by the first {@linkcode Pokemon} in its evolution * Looks at either the species it was met at or the first {@linkcode Species} in its evolution
* line that can act as a starter and provides those egg moves. * line that can act as a starter and provides those egg moves.
* @returns an array of {@linkcode Moves}, the length of which is determined by how many * @returns An array of all {@linkcode Moves} that are egg moves and unlocked for this Pokemon.
* egg moves are unlocked for that species.
*/ */
getUnlockedEggMoves(): Moves[] { getUnlockedEggMoves(): Moves[] {
const moves: 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. * excluding any moves already known.
* *
* Available egg moves are only included if the {@linkcode Pokemon} was * Available egg moves are only included if the {@linkcode Pokemon} was
* in the starting party of the run and if Fresh Start is not active. * in the starting party of the run and if Fresh Start is not active.
* @returns an array of {@linkcode Moves}, the length of which is determined * @returns An array of {@linkcode Moves}, as described above.
* by how many learnable moves there are for the {@linkcode Pokemon}.
*/ */
public getLearnableLevelMoves(): Moves[] { public getLearnableLevelMoves(): Moves[] {
let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); 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 * Evaluate and return this Pokemon's typing.
* @param includeTeraType - `true` to include tera-formed type; Default: `false` * @param includeTeraType - Whether to use this Pokemon's tera type if Terastallized; default `false`
* @param forDefend - `true` if the pokemon is defending from an attack; Default: `false` * @param forDefend - Whether this Pokemon is currently receiving an attack; default `false`
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* @param useIllusion - `true` to return the types of the illusion instead of the actual types; Default: `false` * @param useIllusion - Whether to consider this Pokemon's illusion if present; default `false`
* @returns array of {@linkcode PokemonType} * @returns An array of {@linkcode PokemonType}s corresponding to this Pokemon's typing (real or percieved).
*/ */
public getTypes( public getTypes(
includeTeraType = false, includeTeraType = false,
forDefend: boolean = false, forDefend = false,
ignoreOverride: boolean = false, ignoreOverride = false,
useIllusion: boolean = false useIllusion = false,
): PokemonType[] { ): PokemonType[] {
const types: PokemonType[] = []; const types: PokemonType[] = [];
@ -2179,7 +2226,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
// remove UNKNOWN if other types are present // remove UNKNOWN if other types are present
if (types.length > 1 && types.includes(PokemonType.UNKNOWN)) { if (types.length > 1) {
const index = types.indexOf(PokemonType.UNKNOWN); const index = types.indexOf(PokemonType.UNKNOWN);
if (index !== -1) { if (index !== -1) {
types.splice(index, 1); types.splice(index, 1);
@ -2204,12 +2251,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* Checks if the pokemon's typing includes the specified type * Check if this Pokemon's typing includes the specified type.
* @param type - {@linkcode PokemonType} to check * @param type - The {@linkcode PokemonType} to check
* @param includeTeraType - `true` to include tera-formed type; Default: `true` * @param includeTeraType - Whether to use this Pokemon's tera type if Terastallized; default `false`
* @param forDefend - `true` if the pokemon is defending from an attack; Default: `false` * @param forDefend - Whether this Pokemon is currently receiving an attack; default `false`
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* @returns `true` if the Pokemon's type matches * @returns Whether this Pokemon is of the specified type.
*/ */
public isOfType( public isOfType(
type: PokemonType, type: PokemonType,
@ -2217,18 +2264,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
forDefend = false, forDefend = false,
ignoreOverride = false, ignoreOverride = false,
): boolean { ): boolean {
return this.getTypes(includeTeraType, forDefend, ignoreOverride).some( return this.getTypes(includeTeraType, forDefend, ignoreOverride).includes(type);
t => t === type,
);
} }
/** /**
* Gets the non-passive ability of the pokemon. This accounts for fusions and ability changing effects. * Get this Pokemon's non-passive {@linkcode Ability}, factoring in fusions, overrides and ability-changing effects.
* This should rarely be called, most of the time {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr} are better used as
* those check both the passive and non-passive abilities and account for ability suppression. * Should rarely be called directky in favor of {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr},
* @see {@linkcode hasAbility} {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases * both of which check both ability slots and account for suppression.
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` * @see {@linkcode hasAbility}and {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases
* @returns The non-passive {@linkcode Ability} of the pokemon * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* @returns The non-passive {@linkcode Ability} of this Pokemon.
*/ */
public getAbility(ignoreOverride = false): Ability { public getAbility(ignoreOverride = false): Ability {
if (!ignoreOverride && this.summonData.ability) { if (!ignoreOverride && this.summonData.ability) {
@ -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 * Check whether a pokemon has the specified ability in effect, either as a normal or passive ability.
* effects which can affect whether an ability will be present or in effect, and both passive and * Accounts for all the various effects which can disable or modify abilities.
* non-passive. This is the primary way to check whether a pokemon has a particular ability. * @param ability - The {@linkcode Abilities | Ability} to check for
* @param ability The ability to check for
* @param canApply - Whether to check if the ability is currently active; default `true` * @param canApply - Whether to check if the ability is currently active; default `true`
* @param ignoreOverride Whether to ignore ability changing effects; default `false` * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* @returns `true` if the ability is present and active * @returns Whether this {@linkcode Pokemon} has the given ability
*/ */
public hasAbility( public hasAbility(
ability: Abilities, 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. * Check whether this pokemon has an ability with the specified attribute in effect, either as a normal or passive ability.
* Accounts for all the various effects which can affect whether an ability will be present or * Accounts for all the various effects which can disable or modify abilities.
* in effect, and both passive and non-passive. This is one of the two primary ways to check * @param attrType - The {@linkcode AbAttr | attribute} to check for
* whether a pokemon has a particular ability.
* @param attrType The {@link AbAttr | ability attribute} to check for
* @param canApply - Whether to check if the ability is currently active; default `true` * @param canApply - Whether to check if the ability is currently active; default `true`
* @param ignoreOverride Whether to ignore ability changing effects; default `false` * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode Moves.TRANSFORM | Transform}; default `false`
* @returns `true` if an ability with the given {@linkcode AbAttr} is present and active * @returns Whether this Pokemon has an ability with the given {@linkcode AbAttr}.
*/ */
public hasAbilityWithAttr( public hasAbilityWithAttr(
attrType: Constructor<AbAttr>, attrType: Constructor<AbAttr>,
@ -2518,7 +2561,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const autotomizedTag = this.getTag(AutotomizedTag); const autotomizedTag = this.getTag(AutotomizedTag);
let weightRemoved = 0; let weightRemoved = 0;
if (!isNullOrUndefined(autotomizedTag)) { if (!isNullOrUndefined(autotomizedTag)) {
weightRemoved = 100 * autotomizedTag!.autotomizeCount; weightRemoved = 100 * autotomizedTag.autotomizeCount;
} }
const minWeight = 0.1; const minWeight = 0.1;
const weight = new NumberHolder(this.species.weight - weightRemoved); const weight = new NumberHolder(this.species.weight - weightRemoved);
@ -3986,9 +4029,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* *
* Note that this does not apply to evasion or accuracy * Note that this does not apply to evasion or accuracy
* @see {@linkcode getAccuracyMultiplier} * @see {@linkcode getAccuracyMultiplier}
* @param stat the desired {@linkcode EffectiveStat} * @param stat - The {@linkcode EffectiveStat} to calculate
* @param opponent the target {@linkcode Pokemon} * @param opponent - The {@linkcode Pokemon} being targeted
* @param move the {@linkcode Move} being used * @param move - The {@linkcode Move} being used
* @param ignoreOppAbility determines whether the effects of the opponent's abilities (i.e. Unaware) should be ignored (`false` by default) * @param ignoreOppAbility determines whether the effects of the opponent's abilities (i.e. Unaware) should be ignored (`false` by default)
* @param isCritical determines whether a critical hit has occurred or not (`false` by default) * @param isCritical determines whether a critical hit has occurred or not (`false` by default)
* @param simulated determines whether effects are applied without altering game state (`true` by default) * @param simulated determines whether effects are applied without altering game state (`true` by default)
@ -5127,6 +5170,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return null; return null;
} }
/**
* Return this Pokemon's move history.
* Entries are sorted in order of OLDEST to NEWEST
* @returns An array of {@linkcode TurnMove}, as described above.
* @see {@linkcode getLastXMoves}
*/
public getMoveHistory(): TurnMove[] { public getMoveHistory(): TurnMove[] {
return this.summonData.moveHistory; return this.summonData.moveHistory;
} }
@ -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. * Return a list of the most recent move entries in this {@linkcode Pokemon}'s move history.
* The retrieved move entries are sorted in order from NEWEST to OLDEST. * The retrieved move entries are sorted in order from **NEWEST** to **OLDEST**.
* @param moveCount The number of move entries to retrieve. * @param moveCount - The maximum number of move entries to retrieve.
* If negative, retrieve the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}). * If negative, retrieves the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}).
* Default is `1`. * Default is `1`.
* @returns A list of {@linkcode TurnMove}, as specified above. * @returns An array of {@linkcode TurnMove}, as specified above.
*/ */
// TODO: Update documentation in dancer PR to mention "getLastNonVirtualMove"
getLastXMoves(moveCount = 1): TurnMove[] { getLastXMoves(moveCount = 1): TurnMove[] {
const moveHistory = this.getMoveHistory(); const moveHistory = this.getMoveHistory();
if (moveCount >= 0) { if (moveCount > 0) {
return moveHistory return moveHistory
.slice(Math.max(moveHistory.length - moveCount, 0)) .slice(Math.max(moveHistory.length - moveCount, 0))
.reverse(); .reverse();
} }
return moveHistory.slice(0).reverse(); return moveHistory.slice().reverse();
} }
getMoveQueue(): TurnMove[] { getMoveQueue(): TurnMove[] {
@ -6387,16 +6437,18 @@ 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. * Does nothing if this Pokemon is somehow not the owner of the held item.
* @param heldItem The item stack to be reduced by 1. * @param heldItem The item stack to be reduced.
* @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`. * @param forBattle - Whether to trigger in-battle effects (such as Unburden) after losing the item. Default: `true`
* @returns `true` if the item was removed successfully, `false` otherwise. * Should be `false` for all item loss occurring outside of battle (MEs, etc.).
* @returns Whether the item was removed successfully.
*/ */
public loseHeldItem( public loseHeldItem(
heldItem: PokemonHeldItemModifier, heldItem: PokemonHeldItemModifier,
forBattle = true, forBattle = true,
): boolean { ): boolean {
// TODO: What does a -1 pokemon id mean?
if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) { if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) {
return false; return false;
} }
@ -7166,16 +7218,21 @@ export class EnemyPokemon extends Pokemon {
} }
/** /**
* Sets the pokemons boss status. If true initializes the boss segments either from the arguments * Set this {@linkcode EnemyPokemon}'s boss status.
* or through the the Scene.getEncounterBossSegments function
* *
* @param boss if the pokemon is a boss * @param boss - Whether this pokemon should be a boss; default `true`
* @param bossSegments amount of boss segments (health-bar segments) * @param bossSegments - Optional amount amount of health bar segments to give;
* will be generated by {@linkcode BattleScene.getEncounterBossSegments} if omitted
*/ */
setBoss(boss = true, bossSegments = 0): void { setBoss(boss = true, bossSegments?: number): void {
if (boss) { if (!boss) {
this.bossSegments = 0;
this.bossSegmentIndex = 0;
return;
}
this.bossSegments = this.bossSegments =
bossSegments || bossSegments ??
globalScene.getEncounterBossSegments( globalScene.getEncounterBossSegments(
globalScene.currentBattle.waveIndex, globalScene.currentBattle.waveIndex,
this.level, this.level,
@ -7183,10 +7240,6 @@ export class EnemyPokemon extends Pokemon {
true, true,
); );
this.bossSegmentIndex = this.bossSegments - 1; this.bossSegmentIndex = this.bossSegments - 1;
} else {
this.bossSegments = 0;
this.bossSegmentIndex = 0;
}
} }
generateAndPopulateMoveset(formIndex?: number): void { generateAndPopulateMoveset(formIndex?: number): void {
@ -7958,8 +8011,9 @@ export class PokemonTurnData {
/** How many times the current move should hit the target(s) */ /** How many times the current move should hit the target(s) */
public hitCount = 0; public hitCount = 0;
/** /**
* - `-1` = Calculate how many hits are left * - `-1`: Calculate how many hits are left
* - `0` = Move is finished * - `0`: Move is finished
* - Anything `>0`: Number of hits left to check (barring early interrupts)
*/ */
public hitsLeft = -1; public hitsLeft = -1;
public totalDamageDealt = 0; 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. * 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 pokemon - The {@linkcode Pokemon} attempting to use this move
* @param {boolean} ignorePp If `true`, skips the PP check * @param ignorePp - Whether to ignore checking if the move is out of PP; default `false`
* @param {boolean} ignoreRestrictionTags If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) * @param ignoreRestrictionTags - Whether to skip checks for {@linkcode MoveRestrictionBattlerTag}s; default `false`
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. * @returns Whether this {@linkcode PokemonMove} can be selected by this Pokemon.
*/ */
isUsable( isUsable(
pokemon: Pokemon, pokemon: Pokemon,
@ -8090,7 +8144,7 @@ export class PokemonMove {
ignoreRestrictionTags = false, ignoreRestrictionTags = false,
): boolean { ): boolean {
if ( if (
this.moveId && this.moveId !== Moves.NONE &&
!ignoreRestrictionTags && !ignoreRestrictionTags &&
pokemon.isMoveRestricted(this.moveId, pokemon) pokemon.isMoveRestricted(this.moveId, pokemon)
) { ) {

View File

@ -745,7 +745,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return 1; return 1;
} }
getMaxStackCount(forThreshold?: boolean): number { getMaxStackCount(forThreshold = false): number {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (!pokemon) { if (!pokemon) {
return 0; return 0;
@ -2826,6 +2826,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
damageMultiplier.value *= 1 - 0.25 * this.getStackCount(); damageMultiplier.value *= 1 - 0.25 * this.getStackCount();
return true; return true;
} }
if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) { if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) {
// Deal 25% damage for each remaining Multi Lens hit // Deal 25% damage for each remaining Multi Lens hit
damageMultiplier.value *= 0.25; damageMultiplier.value *= 0.25;

View File

@ -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. // 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) { if (encoreTag) {
this.getPokemon().lapseTag(BattlerTagType.ENCORE); this.getPokemon().lapseTag(BattlerTagType.ENCORE);
} }

View File

@ -36,13 +36,14 @@ export class SelectStarterPhase extends Phase {
/** /**
* Initialize starters before starting the first battle * Initialize starters before starting the first battle
* @param starters {@linkcode Pokemon} with which to start the first battle * @param starters - Array of {@linkcode Starter}s with which to start the battle
*/ */
initBattle(starters: Starter[]) { initBattle(starters: Starter[]) {
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
const loadPokemonAssets: Promise<void>[] = []; const loadPokemonAssets: Promise<void>[] = [];
starters.forEach((starter: Starter, i: number) => { starters.forEach((starter: Starter, i: number) => {
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
// Override first starter species if applicable
starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species);
} }
const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);

View File

@ -210,7 +210,7 @@ export class TitlePhase extends Phase {
globalScene.eventManager.startEventChallenges(); globalScene.eventManager.startEventChallenges();
globalScene.setSeed(seed); globalScene.setSeed(seed);
globalScene.resetSeed(0); globalScene.resetSeed();
globalScene.money = globalScene.gameMode.getStartingMoney(); globalScene.money = globalScene.gameMode.getStartingMoney();
@ -289,6 +289,7 @@ export class TitlePhase extends Phase {
console.error("Failed to load daily run:\n", err); console.error("Failed to load daily run:\n", err);
}); });
} else { } else {
// Grab first 10 chars of ISO date format (YYYY-MM-DD) and convert to base64
let seed: string = btoa(new Date().toISOString().substring(0, 10)); let seed: string = btoa(new Date().toISOString().substring(0, 10));
if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) { if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) {
seed = Overrides.DAILY_RUN_SEED_OVERRIDE; seed = Overrides.DAILY_RUN_SEED_OVERRIDE;

View File

@ -1351,7 +1351,8 @@ export class GameData {
// (or prevent them from being null) // (or prevent them from being null)
// If the value is able to *not exist*, it should say so in the code // If the value is able to *not exist*, it should say so in the code
const sessionData = JSON.parse(dataStr, (k: string, v: any) => { const sessionData = JSON.parse(dataStr, (k: string, v: any) => {
// TODO: Add pre-parse migrate scripts // TODO: Move this to occur _after_ migrate scripts (and refactor all non-assignment duties into migrate scripts)
// This should ideally be just a giant assign block
switch (k) { switch (k) {
case "party": case "party":
case "enemyParty": { case "enemyParty": {

View File

@ -263,7 +263,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
const ppPercentLeft = pp / maxPP; 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; let ppColorStyle = TextStyle.MOVE_PP_FULL;
if (ppPercentLeft > 0.25 && ppPercentLeft <= 0.5) { if (ppPercentLeft > 0.25 && ppPercentLeft <= 0.5) {
ppColorStyle = TextStyle.MOVE_PP_HALF_FULL; ppColorStyle = TextStyle.MOVE_PP_HALF_FULL;
@ -273,7 +273,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
ppColorStyle = TextStyle.MOVE_PP_EMPTY; 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.setColor(this.getTextColor(ppColorStyle, false));
this.ppText.setShadowColor(this.getTextColor(ppColorStyle, true)); this.ppText.setShadowColor(this.getTextColor(ppColorStyle, true));
this.moveInfoOverlay.show(pokemonMove.getMove()); this.moveInfoOverlay.show(pokemonMove.getMove());