Reverted a couple doc changes

This commit is contained in:
Bertie690 2025-05-10 16:28:22 -04:00
parent c2e7c95620
commit da6443562b
7 changed files with 332 additions and 373 deletions

View File

@ -5557,7 +5557,7 @@ class ForceSwitchOutHelper {
* - If the Pokémon is still alive (hp > 0), and if so, it leaves the field and a new SwitchPhase is initiated. * - If the Pokémon is still alive (hp > 0), and if so, it leaves the field and a new SwitchPhase is initiated.
*/ */
if (switchOutTarget instanceof PlayerPokemon) { if (switchOutTarget instanceof PlayerPokemon) {
if (globalScene.getPlayerParty().every(p => !p.isActive(true))) { if (globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
return false; return false;
} }
@ -6901,8 +6901,8 @@ export function initAbilities() {
new Ability(Abilities.ANALYTIC, 5) new Ability(Abilities.ANALYTIC, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => { .attr(MovePowerBoostAbAttr, (user, target, move) => {
// Boost power if all other Pokemon have already moved (no other moves are slated to execute) // Boost power if all other Pokemon have already moved (no other moves are slated to execute)
const laterMovePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user?.id); const movePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user?.id);
return isNullOrUndefined(laterMovePhase); return isNullOrUndefined(movePhase);
}, 1.3), }, 1.3),
new Ability(Abilities.ILLUSION, 5) new Ability(Abilities.ILLUSION, 5)
// The Pokemon generate an illusion if it's available // The Pokemon generate an illusion if it's available

View File

@ -51,10 +51,6 @@ export enum BattlerTagLapseType {
MOVE, MOVE,
PRE_MOVE, PRE_MOVE,
AFTER_MOVE, AFTER_MOVE,
/**
* TODO: Stop treating this like a catch-all "semi invulnerability" tag;
* we may want to use this for other stuff later
*/
MOVE_EFFECT, MOVE_EFFECT,
TURN_END, TURN_END,
HIT, HIT,
@ -65,7 +61,7 @@ export enum BattlerTagLapseType {
export class BattlerTag { export class BattlerTag {
public tagType: BattlerTagType; public tagType: BattlerTagType;
public lapseTypes: BattlerTagLapseType[]; // TODO: Make this a set public lapseTypes: BattlerTagLapseType[];
public turnCount: number; public turnCount: number;
public sourceMove: Moves; public sourceMove: Moves;
public sourceId?: number; public sourceId?: number;
@ -98,7 +94,7 @@ export class BattlerTag {
onOverlap(_pokemon: Pokemon): void {} onOverlap(_pokemon: Pokemon): void {}
/** /**
* Trigger and tick down this {@linkcode BattlerTag}'s duration. * Tick down this {@linkcode BattlerTag}'s duration.
* @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 {
@ -186,21 +182,21 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
} }
/** /**
* Check if this tag is currently restricting a move's use. * Gets whether this tag is restricting a move.
* *
* @param move - The {@linkcode Moves | move ID} whose usability is being checked. * @param move - {@linkcode Moves} ID to check restriction for.
* @param user - The {@linkcode Pokemon} using the move. * @param user - The {@linkcode Pokemon} involved
* @returns Whether the given move is restricted by this tag. * @returns `true` if the move is restricted by this tag, otherwise `false`.
*/ */
public abstract isMoveRestricted(move: Moves, user?: Pokemon): boolean; public abstract isMoveRestricted(move: Moves, user?: Pokemon): boolean;
/** /**
* Check if this tag is restricting a move during target selection. * Checks if this tag is restricting a move based on a user's decisions during the target selection phase
* Returns `false` by default unless overridden by a child class. *
* @param _move - The {@linkcode Moves | move ID} whose selectability is being checked * @param {Moves} _move {@linkcode Moves} move ID to check restriction for
* @param _user - The {@linkcode Pokemon} using the move. * @param {Pokemon} _user {@linkcode Pokemon} the user of the above move
* @param _target - The {@linkcode Pokemon} being targeted * @param {Pokemon} _target {@linkcode Pokemon} the target of the above move
* @returns Whether the given move should be unselectable when choosing targets. * @returns {boolean} `false` unless overridden by the child tag
*/ */
isMoveTargetRestricted(_move: Moves, _user: Pokemon, _target: Pokemon): boolean { isMoveTargetRestricted(_move: Moves, _user: Pokemon, _target: Pokemon): boolean {
return false; return false;
@ -346,10 +342,9 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
/** /**
* @override * @override
* Display the text that occurs when a move is interrupted via Disable. * @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
* @param pokemon - The {@linkcode Pokemon} attempting to use the restricted move * @param {Moves} move {@linkcode Moves} ID of the move being interrupted
* @param move - The {@linkcode Moves | move ID} of the move being interrupted * @returns {string} text to display when the move is interrupted
* @returns The text to display when the given move is interrupted
*/ */
override interruptedText(pokemon: Pokemon, move: Moves): string { override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:disableInterruptedMove", { return i18next.t("battle:disableInterruptedMove", {
@ -367,6 +362,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
/** /**
* Tag used by Gorilla Tactics to restrict the user to using only one move. * Tag used by Gorilla Tactics to restrict the user to using only one move.
* @extends MoveRestrictionBattlerTag
*/ */
export class GorillaTacticsTag extends MoveRestrictionBattlerTag { export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
private moveId = Moves.NONE; private moveId = Moves.NONE;
@ -416,10 +412,11 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
} }
/** /**
*
* @override * @override
* @param pokemon - The {@linkcode Pokemon} attempting to select a move * @param {Pokemon} pokemon n/a
* @param _move - Unused * @param {Moves} _move {@linkcode Moves} ID of the move being denied
* @returns The text to display when the move is rendered unselectable * @returns {string} text to display when the move is denied
*/ */
override selectionDeniedText(pokemon: Pokemon, _move: Moves): string { override selectionDeniedText(pokemon: Pokemon, _move: Moves): string {
return i18next.t("battle:canOnlyUseMove", { return i18next.t("battle:canOnlyUseMove", {
@ -640,7 +637,7 @@ class NoRetreatTag extends TrappedTag {
*/ */
export class FlinchedTag extends BattlerTag { export class FlinchedTag extends BattlerTag {
constructor(sourceMove: Moves) { constructor(sourceMove: Moves) {
super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 1, sourceMove); super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 0, sourceMove);
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
@ -1481,6 +1478,10 @@ export class WrapTag extends DamagingTrapTag {
} }
export abstract class VortexTrapTag extends DamagingTrapTag { export abstract class VortexTrapTag extends DamagingTrapTag {
constructor(tagType: BattlerTagType, commonAnim: CommonAnim, turnCount: number, sourceMove: Moves, sourceId: number) {
super(tagType, commonAnim, turnCount, sourceMove, sourceId);
}
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battlerTags:vortexOnTrap", { return i18next.t("battlerTags:vortexOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
@ -2748,7 +2749,10 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
* @returns `true` if the move has a TRIAGE_MOVE flag and is a status move * @returns `true` if the move has a TRIAGE_MOVE flag and is a status move
*/ */
override isMoveRestricted(move: Moves): boolean { override isMoveRestricted(move: Moves): boolean {
return allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS; if (allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS) {
return true;
}
return false;
} }
/** /**
@ -3422,8 +3426,7 @@ export class PsychoShiftTag extends BattlerTag {
} }
/** /**
* Tag associated with {@linkcode Moves.MAGIC_COAT | Magic Coat} that reflects certain status moves directed at the user. * Tag associated with the move Magic Coat.
* TODO: Move Reflection code out of `move-effect-phase` and into here
*/ */
export class MagicCoatTag extends BattlerTag { export class MagicCoatTag extends BattlerTag {
constructor() { constructor() {

View File

@ -702,10 +702,10 @@ export default class Move implements Localizable {
/** /**
* Sees if a move has a custom failure text (by looking at each {@linkcode MoveAttr} of this move) * Sees if a move has a custom failure text (by looking at each {@linkcode MoveAttr} of this move)
* @param user - The {@linkcode Pokemon} using this move * @param user {@linkcode Pokemon} using the move
* @param target - The {@linkcode Pokemon} targeted by this move * @param target {@linkcode Pokemon} target of the move
* @param move - The {@linkcode Move} being used * @param move {@linkcode Move} with this attribute
* @returns A string containing the custom failure text, or `undefined` if no custom text exists. * @returns string of the custom failure text, or `null` if it uses the default text ("But it failed!")
*/ */
getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined { getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined {
for (const attr of this.attrs) { for (const attr of this.attrs) {
@ -1373,8 +1373,8 @@ export class PreMoveMessageAttr extends MoveAttr {
/** /**
* Attribute for moves that can be conditionally interrupted to be considered to * Attribute for moves that can be conditionally interrupted to be considered to
* have failed before their "useMove" message is displayed. * have failed before their "useMove" message is displayed. Currently used by
* Currently used by {@linkcode Moves.FOCUS_PUNCH}. * Focus Punch.
* @extends MoveAttr * @extends MoveAttr
*/ */
export class PreUseInterruptAttr extends MoveAttr { export class PreUseInterruptAttr extends MoveAttr {
@ -1383,40 +1383,38 @@ export class PreUseInterruptAttr extends MoveAttr {
protected conditionFunc: MoveConditionFunc; protected conditionFunc: MoveConditionFunc;
/** /**
* Create a new PreUseInterruptAttr. * Create a new MoveInterruptedMessageAttr.
* @param message - Custom failure text to display when the move is interrupted, either as a string or a function producing one. * @param message The message to display when the move is interrupted, or a function that formats the message based on the user, target, and move.
* If ommitted, will display the default failure text upon cancellation.
* @param conditionFunc - A {@linkcode MoveConditionFunc} that returns `true` if the move should be canceled.
*/ */
constructor(message: string | undefined | ((user: Pokemon, target: Pokemon, move: Move) => string), conditionFunc: MoveConditionFunc) { constructor(message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string), conditionFunc?: MoveConditionFunc) {
super(); super();
this.message = message; this.message = message;
this.conditionFunc = conditionFunc; this.conditionFunc = conditionFunc ?? (() => true);
} }
/** /**
* Conditionally cancel this pokemon's current move. * Message to display when a move is interrupted.
* @param user - The {@linkcode Pokemon} using this move * @param user {@linkcode Pokemon} using the move
* @param target - The {@linkcode Pokemon} targeted by this move * @param target {@linkcode Pokemon} target of the move
* @param move - The {@linkcode Move} being used * @param move {@linkcode Move} with this attribute
* @returns `true` if the move should be cancelled.
*/ */
override apply(user: Pokemon, target: Pokemon, move: Move): boolean { override apply(user: Pokemon, target: Pokemon, move: Move): boolean {
return this.conditionFunc(user, target, move); return this.conditionFunc(user, target, move);
} }
/** /**
* Obtain the text displayed upon this move's interruption. * Message to display when a move is interrupted.
* @param user - The {@linkcode Pokemon} using this move * @param user {@linkcode Pokemon} using the move
* @param target - The {@linkcode Pokemon} targeted by this move * @param target {@linkcode Pokemon} target of the move
* @param move - The {@linkcode Move} being used * @param move {@linkcode Move} with this attribute
* @returns A string containing the custom failure text, or `undefined` if no custom text exists.
*/ */
override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined { override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined {
if (this.conditionFunc(user, target, move)) { if (this.message && this.conditionFunc(user, target, move)) {
return typeof this.message !== "function" const message =
? this.message typeof this.message === "string"
? (this.message as string)
: this.message(user, target, move); : this.message(user, target, move);
return message;
} }
} }
} }
@ -3059,17 +3057,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
this.chargeText = chargeText; this.chargeText = chargeText;
} }
/** apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
* Apply the delayed attack, either setting it up or triggering the attack.
* @param user - The {@linkcode Pokemon} using the move
* @param target - The {@linkcode Pokemon} being targeted
* @param move - The {@linkcode Move} being used
* @param args -
* `[0]` - {@linkcode BooleanHolder} containing whether the move was overriden
* `[1]` - Whether the move is supposed to set up a delayed attack (`true`) or activate (`false`)
* @returns always `true`
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: [BooleanHolder, boolean]): boolean {
// Edge case for the move applied on a pokemon that has fainted // Edge case for the move applied on a pokemon that has fainted
if (!target) { if (!target) {
return true; return true;
@ -3105,9 +3093,9 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
/** /**
* If the user's ally is set to use a different move with this attribute, * If the user's ally is set to use a different move with this attribute,
* defer this move's effects for a combined move on the ally's turn. * defer this move's effects for a combined move on the ally's turn.
* @param user - The {@linkcode Pokemon} using the move * @param user the {@linkcode Pokemon} using this move
* @param target - Unused * @param target n/a
* @param move - The {@linkcode Move} being used * @param move the {@linkcode Move} being used
* @param args * @param args
* - [0] a {@linkcode BooleanHolder} indicating whether the move's base * - [0] a {@linkcode BooleanHolder} indicating whether the move's base
* effects should be overridden this turn. * effects should be overridden this turn.
@ -3814,31 +3802,27 @@ export class DoublePowerChanceAttr extends VariablePowerAttr {
export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultiplierAttr { export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultiplierAttr {
constructor(limit: number, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: Moves[]) { constructor(limit: number, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: Moves[]) {
super((user: Pokemon, _target: Pokemon, move: Move): number => { super((user: Pokemon, target: Pokemon, move: Move): number => {
const moveHistory = user.getLastXMoves(-1).slice(1, limit+1); // don't count the first history entry (ie the current move) const moveHistory = user.getLastXMoves(limit + 1).slice(1);
let count = 1; let count = 0;
let turnMove: TurnMove | undefined;
// TODO: Confirm whether mirror moving an echoed voice counts for and/or resets a boost while (
for (const tm of moveHistory) { (
if ( (turnMove = moveHistory.shift())?.move === move.id
!(tm.move === move.id || comboMoves.includes(tm.move)) || (comboMoves.length && comboMoves.includes(turnMove?.move ?? Moves.NONE))
|| (resetOnFail && tm.result !== MoveResult.SUCCESS) )
&& (!resetOnFail || turnMove?.result === MoveResult.SUCCESS)
) { ) {
break; if (count < (limit - 1)) {
}
if (count < limit - 1) {
count++; count++;
continue; } else if (resetOnLimit) {
}
if (resetOnLimit) {
count = 0; count = 0;
} } else {
break; break;
} }
}
return this.getMultiplier(count); return this.getMultiplier(count);
}); });
@ -4017,7 +4001,7 @@ export class HpPowerAttr extends VariablePowerAttr {
/** /**
* Attribute used for moves whose base power scales with the opponent's HP * Attribute used for moves whose base power scales with the opponent's HP
* Used for {@linkcode Moves.CRUSH_GRIP}, {@linkcode Moves.WRING_OUT}, and {@linkcode Moves.HARD_PRESS} * Used for Crush Grip, Wring Out, and Hard Press
* maxBasePower 100 for Hard Press, 120 for others * maxBasePower 100 for Hard Press, 120 for others
*/ */
export class OpponentHighHpPowerAttr extends VariablePowerAttr { export class OpponentHighHpPowerAttr extends VariablePowerAttr {
@ -4333,7 +4317,6 @@ const hasStockpileStacksCondition: MoveConditionFunc = (user) => {
return !!hasStockpilingTag && hasStockpilingTag.stockpiledCount > 0; return !!hasStockpilingTag && hasStockpilingTag.stockpiledCount > 0;
}; };
/** /**
* Attribute used for multi-hit moves that increase power in increments of the * Attribute used for multi-hit moves that increase power in increments of the
* move's base power for each hit, namely Triple Kick and Triple Axel. * move's base power for each hit, namely Triple Kick and Triple Axel.
@ -5434,17 +5417,11 @@ export class FrenzyAttr extends MoveEffectAttr {
} }
// TODO: Disable if used via dancer // TODO: Disable if used via dancer
// TODO: Add support for moves that don't add the frenzy tag (Uproar, Rollout, etc.) // TODO: Add support for moves that don't add the frenzy tag (Uproar, Rollout, etc.)
// If frenzy is not in effect and we don't have anything queued up, if (!user.getTag(BattlerTagType.FRENZY) && !user.getMoveQueue().length) {
// add 1-2 extra instances of the move to the move queue. const turnCount = user.randSeedIntRange(1, 2);
// If frenzy is already in effect, tick down the tag. new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
if (!user.getTag(BattlerTagType.FRENZY) && user.getMoveQueue().length === 0) {
const turnCount = user.randSeedIntRange(1, 2); // excludes initial use
for (let i = 0; i < turnCount; i++) {
user.pushMoveQueue({ move: move.id, targets: [ target.getBattlerIndex() ], useType: MoveUseType.IGNORE_PP });
}
user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id); user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id);
} else { } else {
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args); applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
@ -5817,24 +5794,23 @@ export class ProtectAttr extends AddBattlerTagAttr {
super(tagType, true); super(tagType, true);
} }
/**
* Condition to fail a protect usage based on random chance.
* Chance starts at 100% and is thirded for each prior successful proctect usage.
* @returns a function that fails the function if its proc chance roll fails
*/
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return ((user, target, move): boolean => { return ((user, target, move): boolean => {
const lastMoves = user.getLastXMoves(-1) let timesUsed = 0;
const moveHistory = user.getLastXMoves();
let turnMove: TurnMove | undefined;
let threshold = 1; while (moveHistory.length) {
for (const tm of lastMoves) { turnMove = moveHistory.shift();
if (!allMoves[tm.move].hasAttr(ProtectAttr) || tm.result !== MoveResult.SUCCESS) { if (!allMoves[turnMove?.move ?? Moves.NONE].hasAttr(ProtectAttr) || turnMove?.result !== MoveResult.SUCCESS) {
break; break;
} }
threshold *= 3; timesUsed++;
} }
if (timesUsed) {
return threshold === 1 || user.randSeedInt(threshold) === 0; return !user.randSeedInt(Math.pow(3, timesUsed));
}
return true;
}); });
} }
} }
@ -7334,14 +7310,13 @@ export class SketchAttr extends MoveEffectAttr {
constructor() { constructor() {
super(true); super(true);
} }
/** /**
* User copies the opponent's last used move, if possible. * User copies the opponent's last used move, if possible
* @param user - The {@linkcode Pokemon} using the move * @param {Pokemon} user Pokemon that used the move and will replace Sketch with the copied move
* @param target - The {@linkcode Pokemon} being targeted by the move * @param {Pokemon} target Pokemon that the user wants to copy a move from
* @param move - The {@linkcoed Move} being used * @param {Move} move Move being used
* @param args - Unused * @param {any[]} args Unused
* @returns Whether a move was successfully learnt * @returns {boolean} true if the function succeeds, otherwise false
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -7881,6 +7856,7 @@ export class AfterYouAttr extends MoveEffectAttr {
/** /**
* Move effect to force the target to move last, ignoring priority. * Move effect to force the target to move last, ignoring priority.
* If applied to multiple targets, they move in speed order after all other moves. * If applied to multiple targets, they move in speed order after all other moves.
* @extends MoveEffectAttr
*/ */
export class ForceLastAttr extends MoveEffectAttr { export class ForceLastAttr extends MoveEffectAttr {
/** /**
@ -7927,7 +7903,7 @@ const phaseForcedSlower = (phase: MovePhase, target: Pokemon, trickRoom: boolean
let slower: boolean; let slower: boolean;
// quashed pokemon still have speed ties // quashed pokemon still have speed ties
if (phase.pokemon.getEffectiveStat(Stat.SPD) === target.getEffectiveStat(Stat.SPD)) { if (phase.pokemon.getEffectiveStat(Stat.SPD) === target.getEffectiveStat(Stat.SPD)) {
slower = !target.randSeedInt(2); slower = target.randSeedInt(2) === 0;
} else { } else {
slower = !trickRoom ? phase.pokemon.getEffectiveStat(Stat.SPD) < target.getEffectiveStat(Stat.SPD) : phase.pokemon.getEffectiveStat(Stat.SPD) > target.getEffectiveStat(Stat.SPD); slower = !trickRoom ? phase.pokemon.getEffectiveStat(Stat.SPD) < target.getEffectiveStat(Stat.SPD) : phase.pokemon.getEffectiveStat(Stat.SPD) > target.getEffectiveStat(Stat.SPD);
} }

View File

@ -642,7 +642,7 @@ 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()} * It's usually better to call {@linkcode isAllowedInBattle()}
* @param checkStatus - Whether to also check the pokemon's status for {@linkcode StatusEffect.FAINT}; default `false` * @param checkStatus `true` to also check that the pokemon's status is {@linkcode StatusEffect.FAINT}
* @returns `true` if the pokemon is fainted * @returns `true` if the pokemon is fainted
*/ */
public isFainted(checkStatus = false): boolean { public isFainted(checkStatus = false): boolean {
@ -3256,9 +3256,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* If it rolls shiny, or if it's already shiny, also sets a random variant and give the Pokemon the associated luck. * If it rolls shiny, or if it's already shiny, also sets a random variant and give the Pokemon the associated luck.
* *
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536` * The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
* @param thresholdOverride - number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) * @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
* @param applyModifiersToOverride - Whether to apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}. * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
* Does nothing if {@linkcode thresholdOverride} is not set.
* @returns `true` if the Pokemon has been set as a shiny, `false` otherwise * @returns `true` if the Pokemon has been set as a shiny, `false` otherwise
*/ */
public trySetShinySeed( public trySetShinySeed(
@ -3812,7 +3811,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ui => ui =>
ui instanceof BattleInfo && ui instanceof BattleInfo &&
(ui as BattleInfo) instanceof PlayerBattleInfo === this.isPlayer(), (ui as BattleInfo) instanceof PlayerBattleInfo === this.isPlayer(),
)[0] as Phaser.GameObjects.GameObject | undefined; )
.find(() => true);
if (!otherBattleInfo || !this.getFieldIndex()) { if (!otherBattleInfo || !this.getFieldIndex()) {
globalScene.fieldUI.sendToBack(this.battleInfo); globalScene.fieldUI.sendToBack(this.battleInfo);
globalScene.sendTextToBack(); // Push the top right text objects behind everything else globalScene.sendTextToBack(); // Push the top right text objects behind everything else
@ -5140,8 +5140,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Returns a list of the most recent move entries in this Pokemon's move history. * Returns a list of the most recent move entries in this Pokemon's move history.
* The retrieved move entries are sorted in order from NEWEST to OLDEST. * The retrieved move entries are sorted in order from NEWEST to OLDEST.
* @param moveCount The number of move entries to retrieve.\ * @param moveCount The number of move entries to retrieve.
* If negative, retrieves the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}). * If negative, retrieve 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 A list of {@linkcode TurnMove}, as specified above.
*/ */
@ -5660,19 +5660,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (effect === StatusEffect.SLEEP) { if (effect === StatusEffect.SLEEP) {
sleepTurnsRemaining = new NumberHolder(this.randSeedIntRange(2, 4)); sleepTurnsRemaining = new NumberHolder(this.randSeedIntRange(2, 4));
this.setFrameRate(4); this.setFrameRate(4);
// If the user is invulnerable, remove their invulnerability when they fall asleep // If the user is invulnerable, lets remove their invulnerability when they fall asleep
// and remove the upcoming attack from the move queue. const invulnerableTags = [
const tag = [
BattlerTagType.UNDERGROUND, BattlerTagType.UNDERGROUND,
BattlerTagType.UNDERWATER, BattlerTagType.UNDERWATER,
BattlerTagType.HIDDEN, BattlerTagType.HIDDEN,
BattlerTagType.FLYING, BattlerTagType.FLYING,
].find(t => this.getTag(t)); ];
const tag = invulnerableTags.find(t => this.getTag(t));
if (tag) { if (tag) {
this.removeTag(tag); this.removeTag(tag);
this.getMoveQueue().shift(); this.getMoveQueue().pop();
} }
} }
@ -8137,10 +8140,10 @@ export class PokemonMove {
} }
/** /**
* Increments this move's {@linkcode ppUsed} variable (up to a maximum of {@link getMovePp}). * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp}
* @param count - Amount of PP to consume; default `1` * @param count Amount of PP to use
*/ */
usePp(count = 1) { usePp(count: number = 1) {
this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp());
} }

View File

@ -135,11 +135,11 @@ export class MoveEffectPhase extends PokemonPhase {
* Compute targets and the results of hit checks of the invoked move against all targets, * Compute targets and the results of hit checks of the invoked move against all targets,
* organized by battler index. * organized by battler index.
* *
* **This is *not* a pure function** and has the following side effects: * **This is *not* a pure function**; it has the following side effects
* - Sets `this.hitChecks` to the results of the hit checks against each target * - `this.hitChecks` - The results of the hit checks against each target
* - Sets success/failure of `this.moveHistoryEntry` based on the hit check results * - `this.moveHistoryEntry` - Sets success or failure based on the hit check results
* - Sets `user.turnData.hitCount` and `user.turnData.hitsLeft` to 1 if the move * - user.turnData.hitCount and user.turnData.hitsLeft - Both set to 1 if the
* was unsuccessful against all targets (effectively canceling it) * move was unsuccessful against all targets
* *
* @returns The targets of the invoked move * @returns The targets of the invoked move
* @see {@linkcode hitCheck} * @see {@linkcode hitCheck}
@ -205,9 +205,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Apply the move to each of its resolved targets. * Apply the move to each of the resolved targets.
* @param targets - The resolved set of targets of the move * @param targets - The resolved set of targets of the move
* @throws - Error if there was an unexpected hit check result * @throws Error if there was an unexpected hit check result
*/ */
private applyToTargets(user: Pokemon, targets: Pokemon[]): void { private applyToTargets(user: Pokemon, targets: Pokemon[]): void {
for (const [i, target] of targets.entries()) { for (const [i, target] of targets.entries()) {
@ -229,7 +229,6 @@ export class MoveEffectPhase extends PokemonPhase {
case HitCheckResult.NO_EFFECT_NO_MESSAGE: case HitCheckResult.NO_EFFECT_NO_MESSAGE:
case HitCheckResult.PROTECTED: case HitCheckResult.PROTECTED:
case HitCheckResult.TARGET_NOT_ON_FIELD: case HitCheckResult.TARGET_NOT_ON_FIELD:
// Apply effects for ineffective moves (e.g. High Jump Kick crash dmg)
applyMoveAttrs(NoEffectAttr, user, target, this.move); applyMoveAttrs(NoEffectAttr, user, target, this.move);
break; break;
case HitCheckResult.MISS: case HitCheckResult.MISS:
@ -643,19 +642,22 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user) { if (!user) {
return false; return false;
} }
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
switch (true) { return true;
// No Guard }
case user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr): if (this.move.hasAttr(ToxicAccuracyAttr) && user.isOfType(PokemonType.POISON)) {
// Toxic as poison type return true;
case this.move.hasAttr(ToxicAccuracyAttr) && user.isOfType(PokemonType.POISON): }
// Lock On/Mind Reader // TODO: Fix lock on / mind reader check.
case !!user.getTag(BattlerTagType.IGNORE_ACCURACY): if (
// Spikes and company user.getTag(BattlerTagType.IGNORE_ACCURACY) &&
case isFieldTargeted(this.move): (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1
) {
return true;
}
if (isFieldTargeted(this.move)) {
return true; return true;
} }
return false;
} }
/** /**

View File

@ -27,16 +27,16 @@ export class TurnStartPhase extends FieldPhase {
/** /**
* This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array. * This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array.
* It also checks for Trick Room and reverses the array if it is present. * It also checks for Trick Room and reverses the array if it is present.
* @returns An array of {@linkcode BattlerIndex}es containing all on-field pokemon sorted in speed order. * @returns {@linkcode BattlerIndex[]} the battle indices of all pokemon on the field ordered by speed
*/ */
getSpeedOrder(): BattlerIndex[] { getSpeedOrder(): BattlerIndex[] {
const playerField = globalScene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; const playerField = globalScene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
const enemyField = globalScene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; const enemyField = globalScene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
// Shuffle the list before sorting so speed ties produce random results // We shuffle the list before sorting so speed ties produce random results
// This is seeded with the current turn to prevent an inconsistency with variable turn order
// based on how long since you last reloaded
let orderedTargets: Pokemon[] = playerField.concat(enemyField); let orderedTargets: Pokemon[] = playerField.concat(enemyField);
// We seed it with the current turn to prevent an inconsistency where it
// was varying based on how long since you last reloaded
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
orderedTargets = randSeedShuffle(orderedTargets); orderedTargets = randSeedShuffle(orderedTargets);
@ -45,11 +45,11 @@ export class TurnStartPhase extends FieldPhase {
globalScene.waveSeed, globalScene.waveSeed,
); );
// Check for Trick Room and reverse sort order if active. // Next, a check for Trick Room is applied to determine sort order.
// Notably, Pokerogue does NOT have the "outspeed trick room" glitch at >1809 spd.
const speedReversed = new BooleanHolder(false); const speedReversed = new BooleanHolder(false);
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed); globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
// Adjust the sort function based on whether Trick Room is active.
orderedTargets.sort((a: Pokemon, b: Pokemon) => { orderedTargets.sort((a: Pokemon, b: Pokemon) => {
const aSpeed = a?.getEffectiveStat(Stat.SPD) ?? 0; const aSpeed = a?.getEffectiveStat(Stat.SPD) ?? 0;
const bSpeed = b?.getEffectiveStat(Stat.SPD) ?? 0; const bSpeed = b?.getEffectiveStat(Stat.SPD) ?? 0;
@ -120,8 +120,7 @@ export class TurnStartPhase extends FieldPhase {
} }
} }
// If there is no difference between the move's calculated priorities, // If there is no difference between the move's calculated priorities, the game checks for differences in battlerBypassSpeed and returns the result.
// check for differences in battlerBypassSpeed and returns the result.
if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
return battlerBypassSpeed[a].value ? -1 : 1; return battlerBypassSpeed[a].value ? -1 : 1;
} }

View File

@ -44,6 +44,7 @@ describe("Abilities - Wimp Out", () => {
function confirmSwitch(): void { function confirmSwitch(): void {
const [pokemon1, pokemon2] = game.scene.getPlayerParty(); const [pokemon1, pokemon2] = game.scene.getPlayerParty();
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
expect(pokemon1.species.speciesId).not.toBe(Species.WIMPOD); expect(pokemon1.species.speciesId).not.toBe(Species.WIMPOD);
@ -55,34 +56,17 @@ describe("Abilities - Wimp Out", () => {
function confirmNoSwitch(): void { function confirmNoSwitch(): void {
const [pokemon1, pokemon2] = game.scene.getPlayerParty(); const [pokemon1, pokemon2] = game.scene.getPlayerParty();
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
expect(pokemon2.species.speciesId).not.toBe(Species.WIMPOD);
expect(pokemon1.species.speciesId).toBe(Species.WIMPOD); expect(pokemon1.species.speciesId).toBe(Species.WIMPOD);
expect(pokemon1.isFainted()).toBe(false); expect(pokemon1.isFainted()).toBe(false);
expect(pokemon1.getHpRatio()).toBeLessThan(0.5); expect(pokemon1.getHpRatio()).toBeLessThan(0.5);
expect(pokemon2.species.speciesId).not.toBe(Species.WIMPOD);
} }
it("should switch user out when falling below 50% HP, cancelling any pending moves", async () => { it("triggers regenerator passive single time when switching out with wimp out", async () => {
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
const wimpod = game.scene.getPlayerPokemon()!;
wimpod.hp *= 0.51;
game.move.select(Moves.SPLASH);
game.doSelectPartyPokemon(1);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("DamageAnimPhase", false);
game.phaseInterceptor.clearLogs();
await game.phaseInterceptor.to("TurnEndPhase");
confirmSwitch();
expect(wimpod.turnData.acted).toBe(false);
expect(game.phaseInterceptor.log).not.toContain("MoveEffectPhase");
});
it("should trigger regenerator passive when switching out", async () => {
game.override.passiveAbility(Abilities.REGENERATOR).startingLevel(5).enemyLevel(100); game.override.passiveAbility(Abilities.REGENERATOR).startingLevel(5).enemyLevel(100);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -96,7 +80,7 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("should cause wild pokemon to flee", async () => { it("It makes wild pokemon flee if triggered", async () => {
game.override.enemyAbility(Abilities.WIMP_OUT); game.override.enemyAbility(Abilities.WIMP_OUT);
await game.classicMode.startBattle([Species.GOLISOPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.GOLISOPOD, Species.TYRUNT]);
@ -106,11 +90,12 @@ describe("Abilities - Wimp Out", () => {
game.move.select(Moves.FALSE_SWIPE); game.move.select(Moves.FALSE_SWIPE);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
expect(enemyPokemon.visible).toBe(false); const isVisible = enemyPokemon.visible;
expect(enemyPokemon.switchOutStatus).toBe(true); const hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true);
}); });
it("should not trigger when HP is already below half", async () => { it("Does not trigger when HP already below half", async () => {
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
const wimpod = game.scene.getPlayerPokemon()!; const wimpod = game.scene.getPlayerPokemon()!;
wimpod.hp = 5; wimpod.hp = 5;
@ -122,7 +107,7 @@ describe("Abilities - Wimp Out", () => {
confirmNoSwitch(); confirmNoSwitch();
}); });
it("should bypass trapping moves and abilities", async () => { it("Trapping moves do not prevent Wimp Out from activating.", async () => {
game.override.enemyMoveset([Moves.SPIRIT_SHACKLE]).startingLevel(53).enemyLevel(45); game.override.enemyMoveset([Moves.SPIRIT_SHACKLE]).startingLevel(53).enemyLevel(45);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -137,7 +122,7 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("should block switching from U-Turn on activation", async () => { it("If this Ability activates due to being hit by U-turn or Volt Switch, the user of that move will not be switched out.", async () => {
game.override.startingLevel(95).enemyMoveset([Moves.U_TURN]); game.override.startingLevel(95).enemyMoveset([Moves.U_TURN]);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -151,7 +136,7 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("should not block switching from U-Turn on failed activation", async () => { it("If this Ability does not activate due to being hit by U-turn or Volt Switch, the user of that move will be switched out.", async () => {
game.override.startingLevel(190).startingWave(8).enemyMoveset([Moves.U_TURN]); game.override.startingLevel(190).startingWave(8).enemyMoveset([Moves.U_TURN]);
await game.classicMode.startBattle([Species.GOLISOPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.GOLISOPOD, Species.TYRUNT]);
const RIVAL_NINJASK1 = game.scene.getEnemyPokemon()?.id; const RIVAL_NINJASK1 = game.scene.getEnemyPokemon()?.id;
@ -160,7 +145,7 @@ describe("Abilities - Wimp Out", () => {
expect(game.scene.getEnemyPokemon()?.id !== RIVAL_NINJASK1); expect(game.scene.getEnemyPokemon()?.id !== RIVAL_NINJASK1);
}); });
it("Dragon Tail and Circle Throw switch out Pokémon before the Ability activates", async () => { it("Dragon Tail and Circle Throw switch out Pokémon before the Ability activates.", async () => {
game.override.startingLevel(69).enemyMoveset([Moves.DRAGON_TAIL]); game.override.startingLevel(69).enemyMoveset([Moves.DRAGON_TAIL]);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -177,7 +162,7 @@ describe("Abilities - Wimp Out", () => {
expect(game.scene.getPlayerPokemon()!.species.speciesId).not.toBe(Species.WIMPOD); expect(game.scene.getPlayerPokemon()!.species.speciesId).not.toBe(Species.WIMPOD);
}); });
it("should trigger from recoil damage", async () => { it("triggers when recoil damage is taken", async () => {
game.override.moveset([Moves.HEAD_SMASH]).enemyMoveset([Moves.SPLASH]); game.override.moveset([Moves.HEAD_SMASH]).enemyMoveset([Moves.SPLASH]);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -188,7 +173,7 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("should not activate when the Pokémon cuts its own HP", async () => { it("It does not activate when the Pokémon cuts its own HP", async () => {
game.override.moveset([Moves.SUBSTITUTE]).enemyMoveset([Moves.SPLASH]); game.override.moveset([Moves.SUBSTITUTE]).enemyMoveset([Moves.SPLASH]);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -201,19 +186,7 @@ describe("Abilities - Wimp Out", () => {
confirmNoSwitch(); confirmNoSwitch();
}); });
it("should not trigger from Sheer Force-boosted moves", async () => { it("Does not trigger when neutralized", async () => {
game.override.enemyAbility(Abilities.SHEER_FORCE).enemyMoveset(Moves.SLUDGE_BOMB).startingLevel(95);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.51;
game.move.select(Moves.ENDURE);
await game.phaseInterceptor.to("TurnEndPhase");
confirmNoSwitch();
});
it("should not trigger while neutralized", async () => {
game.override.enemyAbility(Abilities.NEUTRALIZING_GAS).startingLevel(5); game.override.enemyAbility(Abilities.NEUTRALIZING_GAS).startingLevel(5);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -250,14 +223,8 @@ describe("Abilities - Wimp Out", () => {
}, },
); );
// TODO: Condense into it.eaches
describe("Post Turn Damage Checks - ", () => {
beforeEach(() => {
game.override.enemyMoveset(Moves.SPLASH);
});
it("Wimp Out will activate due to weather damage", async () => { it("Wimp Out will activate due to weather damage", async () => {
game.override.weather(WeatherType.HAIL); game.override.weather(WeatherType.HAIL).enemyMoveset([Moves.SPLASH]);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.51; game.scene.getPlayerPokemon()!.hp *= 0.51;
@ -269,12 +236,37 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("Wimp Out will activate due to post turn status damage", async () => { it("Does not trigger when enemy has sheer force", async () => {
game.override.statusEffect(StatusEffect.POISON); game.override.enemyAbility(Abilities.SHEER_FORCE).enemyMoveset(Moves.SLUDGE_BOMB).startingLevel(95);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.51; game.scene.getPlayerPokemon()!.hp *= 0.51;
game.move.select(Moves.ENDURE);
await game.phaseInterceptor.to("TurnEndPhase");
confirmNoSwitch();
});
it("Wimp Out will activate due to post turn status damage", async () => {
game.override.statusEffect(StatusEffect.POISON).enemyMoveset([Moves.SPLASH]);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.51;
game.move.select(Moves.SPLASH);
game.doSelectPartyPokemon(1);
await game.toNextTurn();
confirmSwitch();
});
it("Wimp Out will activate due to bad dreams", async () => {
game.override.statusEffect(StatusEffect.SLEEP).enemyAbility(Abilities.BAD_DREAMS);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.52;
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
game.doSelectPartyPokemon(1); game.doSelectPartyPokemon(1);
await game.toNextTurn(); await game.toNextTurn();
@ -330,6 +322,45 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("Magic Guard passive should not allow indirect damage to trigger Wimp Out", async () => {
game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0, ArenaTagSide.ENEMY);
game.scene.arena.addTag(ArenaTagType.SPIKES, 1, Moves.SPIKES, 0, ArenaTagSide.ENEMY);
game.override
.passiveAbility(Abilities.MAGIC_GUARD)
.enemyMoveset([Moves.LEECH_SEED])
.weather(WeatherType.HAIL)
.statusEffect(StatusEffect.POISON);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.51;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.getPlayerParty()[0].getHpRatio()).toEqual(0.51);
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.WIMPOD);
});
it("Wimp Out activating should not cancel a double battle", async () => {
game.override.battleStyle("double").enemyAbility(Abilities.WIMP_OUT).enemyMoveset([Moves.SPLASH]).enemyLevel(1);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
const enemyLeadPokemon = game.scene.getEnemyParty()[0];
const enemySecPokemon = game.scene.getEnemyParty()[1];
game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to("BerryPhase");
const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus;
const isVisibleSec = enemySecPokemon.visible;
const hasFledSec = enemySecPokemon.switchOutStatus;
expect(!isVisibleLead && hasFledLead && isVisibleSec && !hasFledSec).toBe(true);
expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.getMaxHp());
expect(enemySecPokemon.hp).toEqual(enemySecPokemon.getMaxHp());
});
it("Wimp Out will activate due to aftermath", async () => { it("Wimp Out will activate due to aftermath", async () => {
game.override game.override
.moveset([Moves.THUNDER_PUNCH]) .moveset([Moves.THUNDER_PUNCH])
@ -347,19 +378,6 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("Wimp Out will activate due to bad dreams", async () => {
game.override.statusEffect(StatusEffect.SLEEP).enemyAbility(Abilities.BAD_DREAMS);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.52;
game.move.select(Moves.SPLASH);
game.doSelectPartyPokemon(1);
await game.toNextTurn();
confirmSwitch();
});
it("Activates due to entry hazards", async () => { it("Activates due to entry hazards", async () => {
game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0, ArenaTagSide.ENEMY); game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0, ArenaTagSide.ENEMY);
game.scene.arena.addTag(ArenaTagType.SPIKES, 1, Moves.SPIKES, 0, ArenaTagSide.ENEMY); game.scene.arena.addTag(ArenaTagType.SPIKES, 1, Moves.SPIKES, 0, ArenaTagSide.ENEMY);
@ -381,27 +399,6 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
});
it("should not trigger on Magic Guard-prevented damage", async () => {
game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0, ArenaTagSide.ENEMY);
game.scene.arena.addTag(ArenaTagType.SPIKES, 1, Moves.SPIKES, 0, ArenaTagSide.ENEMY);
game.override
.passiveAbility(Abilities.MAGIC_GUARD)
.enemyMoveset([Moves.LEECH_SEED])
.weather(WeatherType.HAIL)
.statusEffect(StatusEffect.POISON);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
game.scene.getPlayerPokemon()!.hp *= 0.51;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.getPlayerParty()[0].getHpRatio()).toEqual(0.51);
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.WIMPOD);
});
it("triggers status on the wimp out user before a new pokemon is switched in", async () => { it("triggers status on the wimp out user before a new pokemon is switched in", async () => {
game.override.enemyMoveset(Moves.SLUDGE_BOMB).startingLevel(80); game.override.enemyMoveset(Moves.SLUDGE_BOMB).startingLevel(80);
@ -416,27 +413,7 @@ describe("Abilities - Wimp Out", () => {
confirmSwitch(); confirmSwitch();
}); });
it("Wimp Out activating should not cancel a double battle", async () => { it("triggers after last hit of multi hit move", async () => {
game.override.battleStyle("double").enemyAbility(Abilities.WIMP_OUT).enemyMoveset([Moves.SPLASH]).enemyLevel(1);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
const enemyLeadPokemon = game.scene.getEnemyParty()[0];
const enemySecPokemon = game.scene.getEnemyParty()[1];
game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to("BerryPhase");
const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus;
const isVisibleSec = enemySecPokemon.visible;
const hasFledSec = enemySecPokemon.switchOutStatus;
expect(!isVisibleLead && hasFledLead && isVisibleSec && !hasFledSec).toBe(true);
expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.getMaxHp());
expect(enemySecPokemon.hp).toEqual(enemySecPokemon.getMaxHp());
});
it("triggers after last hit of multi hit moves", async () => {
game.override.enemyMoveset(Moves.BULLET_SEED).enemyAbility(Abilities.SKILL_LINK); game.override.enemyMoveset(Moves.BULLET_SEED).enemyAbility(Abilities.SKILL_LINK);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
@ -467,7 +444,6 @@ describe("Abilities - Wimp Out", () => {
expect(enemyPokemon.turnData.hitCount).toBe(2); expect(enemyPokemon.turnData.hitCount).toBe(2);
confirmSwitch(); confirmSwitch();
}); });
it("triggers after last hit of Parental Bond", async () => { it("triggers after last hit of Parental Bond", async () => {
game.override.enemyMoveset(Moves.TACKLE).enemyAbility(Abilities.PARENTAL_BOND); game.override.enemyMoveset(Moves.TACKLE).enemyAbility(Abilities.PARENTAL_BOND);
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]); await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);