mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-07 16:09:27 +02:00
Fixed bugs, split up status code, re-added required Rest parameter
This commit is contained in:
parent
9bfb1bba88
commit
db927e8adb
@ -856,6 +856,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
||||
this.layers === 1 ? StatusEffect.POISON : StatusEffect.TOXIC,
|
||||
true,
|
||||
null,
|
||||
0,
|
||||
this.getMoveName(),
|
||||
);
|
||||
}
|
||||
|
@ -1873,11 +1873,11 @@ export class HealAttr extends MoveEffectAttr {
|
||||
/** Should an animation be shown? */
|
||||
private showAnim: boolean;
|
||||
|
||||
constructor(healRatio?: number, showAnim?: boolean, selfTarget?: boolean) {
|
||||
super(selfTarget === undefined || selfTarget);
|
||||
constructor(healRatio = 1, showAnim = false, selfTarget = true) {
|
||||
super(selfTarget);
|
||||
|
||||
this.healRatio = healRatio || 1;
|
||||
this.showAnim = !!showAnim;
|
||||
this.healRatio = healRatio;
|
||||
this.showAnim = showAnim;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
@ -2440,9 +2440,8 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
|
||||
|
||||
export class StatusEffectAttr extends MoveEffectAttr {
|
||||
public effect: StatusEffect;
|
||||
private overrideStatus: boolean;
|
||||
|
||||
constructor(effect: StatusEffect, selfTarget = false, overrideStatus = false) {
|
||||
constructor(effect: StatusEffect, selfTarget = false) {
|
||||
super(selfTarget);
|
||||
|
||||
this.effect = effect;
|
||||
@ -2455,18 +2454,11 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
// non-status moves don't play sound effects for failures
|
||||
const quiet = move.category !== MoveCategory.STATUS;
|
||||
|
||||
// TODO: why
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (user !== target && move.category === MoveCategory.STATUS && !target.canSetStatus(this.effect, quiet, this.overrideStatus, user, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: What does a chance of -1 have to do with any of this???
|
||||
if (
|
||||
(!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||
&& pokemon.trySetStatus(this.effect, true, user, null, this.overrideStatus, quiet)
|
||||
this.doSetStatus(this.selfTarget ? user : target, user, quiet)
|
||||
) {
|
||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||
return true;
|
||||
@ -2476,10 +2468,23 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
|
||||
const score = (moveChance < 0) ? -10 : Math.floor(moveChance * -0.1);
|
||||
const score = moveChance < 0 ? -10 : Math.floor(moveChance * -0.1);
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
|
||||
return !pokemon.status && pokemon.canSetStatus(this.effect, true, false, user) ? score : 0;
|
||||
return pokemon.canSetStatus(this.effect, true, false, user) ? score : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function to attempt to set status of a pokemon.
|
||||
* Exists to allow super classes to override parameters.
|
||||
* @param pokemon - The {@linkcode Pokemon} being statused.
|
||||
* @param user - The {@linkcode Pokemon} doing the statusing.
|
||||
* @param quiet - Whether to suppress messages for status immunities.
|
||||
* @returns Whether the status was sucessfully applied.
|
||||
* @see {@linkcode Pokemon.trySetStatus}
|
||||
*/
|
||||
protected doSetStatus(pokemon: Pokemon, user: Pokemon, quiet: boolean): boolean {
|
||||
return pokemon.trySetStatus(this.effect, true, user, undefined, null, false, quiet)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2490,21 +2495,15 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
export class RestAttr extends StatusEffectAttr {
|
||||
private duration: number;
|
||||
|
||||
constructor(
|
||||
duration: number,
|
||||
overrideStatus: boolean
|
||||
){
|
||||
constructor(duration: number) {
|
||||
// Sleep is the only duration-based status ATM
|
||||
super(StatusEffect.SLEEP, true, overrideStatus);
|
||||
super(StatusEffect.SLEEP, true);
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const didStatus = super.apply(user, target, move, args);
|
||||
if (didStatus && user.status?.effect === this.effect) {
|
||||
user.status.sleepTurnsRemaining = this.duration;
|
||||
}
|
||||
return didStatus;
|
||||
// TODO: Add custom text for rest and make `HealAttr` no longer cause status
|
||||
protected override doSetStatus(pokemon: Pokemon, user: Pokemon, quiet: boolean): boolean {
|
||||
return pokemon.trySetStatus(this.effect, true, user, this.duration, null, true, quiet)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2543,25 +2542,39 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||
* @returns `true` if Psycho Shift's effect is able to be applied to the target
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||
|
||||
if (target.status) {
|
||||
// Bang is justified as condition func returns early if no status is found
|
||||
const statusToApply = user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : user.status?.effect!
|
||||
if (!target.trySetStatus(statusToApply, true, user)) {
|
||||
return false;
|
||||
} else {
|
||||
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||
const trySetStatus = canSetStatus ? target.trySetStatus(statusToApply, true, user) : false;
|
||||
}
|
||||
|
||||
if (trySetStatus && user.status) {
|
||||
// PsychoShiftTag is added to the user if move succeeds so that the user is healed of its status effect after its move
|
||||
user.addTag(BattlerTagType.PSYCHO_SHIFT);
|
||||
if (user.status) {
|
||||
// Add tag to user to heal its status effect after the move ends (unless we have comatose);
|
||||
// Occurs after move use to ensure correct Synchronize timing
|
||||
user.addTag(BattlerTagType.PSYCHO_SHIFT)
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target, _move) => {
|
||||
if (target.status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return trySetStatus;
|
||||
const statusToApply = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||
return !!statusToApply && target.canSetStatus(statusToApply, false, false, user);
|
||||
}
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
|
||||
const statusToApply =
|
||||
user.status?.effect ??
|
||||
(user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||
|
||||
// TODO: Give this an actual positive benefit score
|
||||
return !target.status && statusToApply && target.canSetStatus(statusToApply, true, false, user) ? -10 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2831,7 +2844,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
||||
*/
|
||||
constructor(selfTarget: boolean, effects: StatusEffect | StatusEffect[]) {
|
||||
super(selfTarget, { lastHitOnly: true });
|
||||
this.effects = [ effects ].flat(1);
|
||||
if (!Array.isArray(effects)) {
|
||||
effects = [ effects ]
|
||||
}
|
||||
this.effects = effects
|
||||
}
|
||||
|
||||
/**
|
||||
@ -8747,8 +8763,8 @@ export function initMoves() {
|
||||
.attr(MultiHitAttr, MultiHitType._2)
|
||||
.makesContact(false),
|
||||
new SelfStatusMove(Moves.REST, PokemonType.PSYCHIC, -1, 5, -1, 0, 1)
|
||||
.attr(RestAttr, 3, true)
|
||||
.attr(HealAttr, 1, true)
|
||||
.attr(RestAttr, 3)
|
||||
.condition((user, target, move) => !user.isFullHp() && user.canSetStatus(StatusEffect.SLEEP, true, true, user))
|
||||
.triageMove(),
|
||||
new AttackMove(Moves.ROCK_SLIDE, PokemonType.ROCK, MoveCategory.PHYSICAL, 75, 90, 10, 30, 0, 1)
|
||||
@ -9469,15 +9485,7 @@ export function initMoves() {
|
||||
.makesContact(false)
|
||||
.unimplemented(),
|
||||
new StatusMove(Moves.PSYCHO_SHIFT, PokemonType.PSYCHIC, 100, 10, -1, 0, 4)
|
||||
.attr(PsychoShiftEffectAttr)
|
||||
.condition((user, target, move) => {
|
||||
let statusToApply = user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined;
|
||||
if (user.status?.effect && isNonVolatileStatusEffect(user.status.effect)) {
|
||||
statusToApply = user.status.effect;
|
||||
}
|
||||
return !!statusToApply && target.canSetStatus(statusToApply, false, false, user);
|
||||
}
|
||||
),
|
||||
.attr(PsychoShiftEffectAttr),
|
||||
new AttackMove(Moves.TRUMP_CARD, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4)
|
||||
.makesContact()
|
||||
.attr(LessPPMorePowerAttr),
|
||||
|
@ -154,6 +154,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
|
||||
return randIntRange(0, 2) ? statusA : statusB;
|
||||
}
|
||||
|
||||
// TODO: Make this a type and remove these
|
||||
/**
|
||||
* Gets all non volatile status effects
|
||||
* @returns A list containing all non volatile status effects
|
||||
|
@ -1,3 +1,5 @@
|
||||
/** Enum representing all non-volatile status effects. */
|
||||
// TODO: Add a type that excludes `NONE` and `FAINT`
|
||||
export enum StatusEffect {
|
||||
NONE,
|
||||
POISON,
|
||||
|
@ -3719,7 +3719,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Status moves remain unchanged on weight, this encourages 1-2
|
||||
movePool = baseWeights
|
||||
.filter(m => !this.moveset.some(
|
||||
mo =>
|
||||
mo =>
|
||||
m[0] === mo.moveId ||
|
||||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)) // Only one self-KO move allowed
|
||||
))
|
||||
@ -3754,7 +3754,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
} else {
|
||||
// Non-trainer pokemon just use normal weights
|
||||
movePool = baseWeights.filter(m => !this.moveset.some(
|
||||
mo =>
|
||||
mo =>
|
||||
m[0] === mo.moveId ||
|
||||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)) // Only one self-KO move allowed
|
||||
));
|
||||
@ -5405,7 +5405,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
);
|
||||
}
|
||||
|
||||
queueImmuneMessage(quiet: boolean, effect?: StatusEffect): void {
|
||||
// TODO: Add messages for misty/electric terrain
|
||||
private queueImmuneMessage(quiet: boolean, effect?: StatusEffect): void {
|
||||
if (!effect || quiet) {
|
||||
return;
|
||||
}
|
||||
@ -5426,14 +5427,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param sourcePokemon - The {@linkcode Pokemon} applying the status effect to the target
|
||||
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
||||
*/
|
||||
// TODO: Review and verify the message order precedence in mainline if multiple status-blocking effects are present at once
|
||||
canSetStatus(
|
||||
effect: StatusEffect | undefined,
|
||||
effect: StatusEffect,
|
||||
quiet = false,
|
||||
overrideStatus = false,
|
||||
sourcePokemon: Pokemon | null = null,
|
||||
ignoreField = false,
|
||||
): boolean {
|
||||
if (effect !== StatusEffect.FAINT) {
|
||||
// Status-overriding moves (ie Rest) fail if their respective status already exists
|
||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
@ -5450,19 +5453,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
const types = this.getTypes(true, true);
|
||||
|
||||
// Check for specific immunities for certain statuses
|
||||
let isImmune = false;
|
||||
switch (effect) {
|
||||
case StatusEffect.POISON:
|
||||
case StatusEffect.TOXIC:
|
||||
// Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity
|
||||
const poisonImmunity = types.map(defType => {
|
||||
// Check if the Pokemon is not immune to Poison/Toxic
|
||||
// Check for type based immunities and/or Corrosion
|
||||
isImmune = types.some(defType => {
|
||||
if (defType !== PokemonType.POISON && defType !== PokemonType.STEEL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
||||
if (!sourcePokemon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const cancelImmunity = new BooleanHolder(false);
|
||||
if (sourcePokemon) {
|
||||
applyAbAttrs(
|
||||
IgnoreTypeStatusEffectImmunityAbAttr,
|
||||
sourcePokemon,
|
||||
@ -5471,57 +5477,36 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
effect,
|
||||
defType,
|
||||
);
|
||||
if (cancelImmunity.value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) {
|
||||
if (poisonImmunity.includes(true)) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return cancelImmunity.value;
|
||||
});
|
||||
break;
|
||||
case StatusEffect.PARALYSIS:
|
||||
if (this.isOfType(PokemonType.ELECTRIC)) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
isImmune = this.isOfType(PokemonType.ELECTRIC)
|
||||
break;
|
||||
case StatusEffect.SLEEP:
|
||||
if (
|
||||
isImmune =
|
||||
this.isGrounded() &&
|
||||
globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC
|
||||
) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC;
|
||||
break;
|
||||
case StatusEffect.FREEZE:
|
||||
if (
|
||||
isImmune =
|
||||
this.isOfType(PokemonType.ICE) ||
|
||||
(!ignoreField &&
|
||||
globalScene?.arena?.weather?.weatherType &&
|
||||
!ignoreField &&
|
||||
[WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(
|
||||
globalScene.arena.weather.weatherType,
|
||||
))
|
||||
) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
globalScene.arena.weather?.weatherType ?? WeatherType.NONE,
|
||||
)
|
||||
break;
|
||||
case StatusEffect.BURN:
|
||||
if (this.isOfType(PokemonType.FIRE)) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
isImmune = this.isOfType(PokemonType.FIRE)
|
||||
break;
|
||||
}
|
||||
|
||||
if (isImmune) {
|
||||
this.queueImmuneMessage(quiet, effect)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for cancellations from self/ally abilities
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyPreSetStatusAbAttrs(
|
||||
StatusEffectImmunityAbAttr,
|
||||
@ -5543,23 +5528,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
quiet, this, sourcePokemon,
|
||||
)
|
||||
if (cancelled.value) {
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancelled.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform safeguard checks
|
||||
if (
|
||||
sourcePokemon &&
|
||||
sourcePokemon !== this &&
|
||||
this.isSafeguarded(sourcePokemon)
|
||||
) {
|
||||
if(!quiet){
|
||||
if (!quiet) {
|
||||
globalScene.queueMessage(
|
||||
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this)
|
||||
}));
|
||||
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this)})
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -5567,14 +5549,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Make this take a destructured object as args to condense all these optional args...
|
||||
trySetStatus(
|
||||
effect?: StatusEffect,
|
||||
effect: StatusEffect,
|
||||
asPhase = false,
|
||||
sourcePokemon: Pokemon | null = null,
|
||||
turnsRemaining?: number,
|
||||
sourceText: string | null = null,
|
||||
overrideStatus?: boolean,
|
||||
quiet = true,
|
||||
): boolean {
|
||||
// TODO: Remove uses of `asPhase=false` in favor of checking status directly
|
||||
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
||||
return false;
|
||||
}
|
||||
@ -5598,47 +5583,76 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (overrideStatus) {
|
||||
this.resetStatus(false);
|
||||
}
|
||||
|
||||
globalScene.unshiftPhase(
|
||||
new ObtainStatusEffectPhase(
|
||||
this.getBattlerIndex(),
|
||||
effect,
|
||||
turnsRemaining,
|
||||
sourceText,
|
||||
sourcePokemon,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
this.doSetStatus(effect, turnsRemaining)
|
||||
}
|
||||
|
||||
let sleepTurnsRemaining: NumberHolder;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to give the specified Pokemon the given status effect.
|
||||
* Does **NOT** perform any feasibility checks whatsoever, and should thus never be called directly
|
||||
* unless conditions are known to be met.
|
||||
* @param effect - The {@linkcode StatusEffect} to set
|
||||
*/
|
||||
doSetStatus(effect: Exclude<StatusEffect, StatusEffect.SLEEP>): void;
|
||||
/**
|
||||
* Attempt to give the specified Pokemon the given status effect.
|
||||
* Does **NOT** perform any feasibility checks whatsoever, and should thus never be called directly
|
||||
* unless conditions are known to be met.
|
||||
* @param effect - StatusEffect.SLEEP
|
||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4.
|
||||
*/
|
||||
doSetStatus(effect: StatusEffect.SLEEP, sleepTurnsRemaining?: number): void;
|
||||
/**
|
||||
* Attempt to give the specified Pokemon the given status effect.
|
||||
* Does **NOT** perform any feasibility checks whatsoever, and should thus never be called directly
|
||||
* unless conditions are known to be met.
|
||||
* @param effect - The {@linkcode StatusEffect} to set
|
||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4.
|
||||
*/
|
||||
doSetStatus(effect: StatusEffect, sleepTurnsRemaining?: number): void;
|
||||
/**
|
||||
* Attempt to give the specified Pokemon the given status effect.
|
||||
* Does **NOT** perform any feasibility checks whatsoever, and should thus never be called directly
|
||||
* unless conditions are known to be met.
|
||||
* @param effect - The {@linkcode StatusEffect} to set
|
||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4.
|
||||
*/
|
||||
doSetStatus(effect: StatusEffect, sleepTurnsRemaining = this.randBattleSeedIntRange(2, 4)): void {
|
||||
if (effect === StatusEffect.SLEEP) {
|
||||
sleepTurnsRemaining = new NumberHolder(this.randBattleSeedIntRange(2, 4));
|
||||
|
||||
this.setFrameRate(4);
|
||||
|
||||
// If the user is invulnerable, lets remove their invulnerability when they fall asleep
|
||||
const invulnerableTags = [
|
||||
// If the user is invulnerable, remove their invulnerability when they fall asleep
|
||||
const invulnTag = [
|
||||
BattlerTagType.UNDERGROUND,
|
||||
BattlerTagType.UNDERWATER,
|
||||
BattlerTagType.HIDDEN,
|
||||
BattlerTagType.FLYING,
|
||||
];
|
||||
].find(t => this.getTag(t));
|
||||
|
||||
const tag = invulnerableTags.find(t => this.getTag(t));
|
||||
|
||||
if (tag) {
|
||||
this.removeTag(tag);
|
||||
this.getMoveQueue().pop();
|
||||
if (invulnTag) {
|
||||
this.removeTag(invulnTag);
|
||||
this.getMoveQueue().shift();
|
||||
}
|
||||
}
|
||||
|
||||
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
||||
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
||||
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
||||
|
||||
return true;
|
||||
this.status = new Status(effect, 0, sleepTurnsRemaining);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resets the status of a pokemon.
|
||||
* @param revive Whether revive should be cured; defaults to true.
|
||||
|
@ -1751,12 +1751,12 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to inflicts the holder with the associated {@linkcode StatusEffect}.
|
||||
* @param pokemon {@linkcode Pokemon} that holds the held item
|
||||
* Attempt to inflicts the holder with the associated {@linkcode StatusEffect}.
|
||||
* @param pokemon - The {@linkcode Pokemon} holds the item.
|
||||
* @returns `true` if the status effect was applied successfully
|
||||
*/
|
||||
override apply(pokemon: Pokemon): boolean {
|
||||
return pokemon.trySetStatus(this.effect, true, pokemon, this.type.name);
|
||||
return pokemon.trySetStatus(this.effect, true, pokemon, undefined, this.type.name);
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||
|
@ -12,19 +12,22 @@ import { isNullOrUndefined } from "#app/utils/common";
|
||||
|
||||
/** The phase where pokemon obtain status effects. */
|
||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||
private statusEffect?: StatusEffect;
|
||||
private statusEffect: StatusEffect;
|
||||
private turnsRemaining?: number;
|
||||
private sourceText?: string | null;
|
||||
private sourcePokemon?: Pokemon | null;
|
||||
|
||||
constructor(
|
||||
battlerIndex: BattlerIndex,
|
||||
statusEffect?: StatusEffect,
|
||||
statusEffect: StatusEffect,
|
||||
turnsRemaining?: number,
|
||||
sourceText?: string | null,
|
||||
sourcePokemon?: Pokemon | null,
|
||||
) {
|
||||
super(battlerIndex);
|
||||
|
||||
this.statusEffect = statusEffect;
|
||||
this.turnsRemaining = turnsRemaining;
|
||||
this.sourceText = sourceText;
|
||||
this.sourcePokemon = sourcePokemon;
|
||||
}
|
||||
@ -32,19 +35,19 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||
start() {
|
||||
const pokemon = this.getPokemon();
|
||||
if (pokemon.status?.effect === this.statusEffect) {
|
||||
globalScene.queueMessage(
|
||||
getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)),
|
||||
);
|
||||
globalScene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon)));
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
||||
// status application passes
|
||||
if (!pokemon.canSetStatus(this.statusEffect, false, false, this.sourcePokemon)) {
|
||||
// status application fails
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
pokemon.doSetStatus(this.statusEffect, this.turnsRemaining);
|
||||
|
||||
pokemon.updateInfo(true);
|
||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => {
|
||||
globalScene.queueMessage(
|
||||
@ -52,8 +55,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||
);
|
||||
if (!isNullOrUndefined(this.statusEffect) && this.statusEffect !== StatusEffect.FAINT) {
|
||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
||||
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
|
||||
// TODO: We may need to reset this for Ice Fang, etc.
|
||||
globalScene.arena.setIgnoreAbilities(false);
|
||||
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon);
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ describe("Abilities - Corrosion", () => {
|
||||
expect(enemyPokemon!.status).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should affect the user's held a Toxic Orb", async () => {
|
||||
it("should affect the user's held Toxic Orb", async () => {
|
||||
game.override.startingHeldItems([{ name: "TOXIC_ORB", count: 1 }]);
|
||||
await game.classicMode.startBattle([Species.SALAZZLE]);
|
||||
|
||||
|
@ -25,15 +25,6 @@ describe("Spec - Pokemon", () => {
|
||||
game = new GameManager(phaserGame);
|
||||
});
|
||||
|
||||
it("should not crash when trying to set status of undefined", async () => {
|
||||
await game.classicMode.runToSummon([Species.ABRA]);
|
||||
|
||||
const pkm = game.scene.getPlayerPokemon()!;
|
||||
expect(pkm).toBeDefined();
|
||||
|
||||
expect(pkm.trySetStatus(undefined)).toBe(true);
|
||||
});
|
||||
|
||||
describe("Add To Party", () => {
|
||||
let scene: BattleScene;
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
@ -25,7 +26,7 @@ describe("Moves - Rest", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.REST, Moves.SLEEP_TALK])
|
||||
.moveset([Moves.REST, Moves.SWORDS_DANCE])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
@ -34,12 +35,12 @@ describe("Moves - Rest", () => {
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should fully heal the user, cure its status and put it to sleep", async () => {
|
||||
it("should fully heal the user, cure its prior status and put it to sleep", async () => {
|
||||
game.override.statusEffect(StatusEffect.POISON);
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.hp = 1;
|
||||
snorlax.trySetStatus(StatusEffect.POISON);
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.POISON);
|
||||
|
||||
game.move.select(Moves.REST);
|
||||
@ -49,7 +50,35 @@ describe("Moves - Rest", () => {
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
});
|
||||
|
||||
it("should preserve non-volatile conditions", async () => {
|
||||
it("should always last 3 turns", async () => {
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.hp = 1;
|
||||
|
||||
// Cf https://bulbapedia.bulbagarden.net/wiki/Rest_(move):
|
||||
// > The user is unable to use moves while asleep for 2 turns after the turn when Rest is used.
|
||||
game.move.select(Moves.REST);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
expect(snorlax.status?.sleepTurnsRemaining).toBe(3);
|
||||
|
||||
game.move.select(Moves.SWORDS_DANCE);
|
||||
await game.toNextTurn();
|
||||
expect(snorlax.status?.sleepTurnsRemaining).toBe(2);
|
||||
|
||||
game.move.select(Moves.SWORDS_DANCE);
|
||||
await game.toNextTurn();
|
||||
expect(snorlax.status?.sleepTurnsRemaining).toBe(1);
|
||||
|
||||
game.move.select(Moves.SWORDS_DANCE);
|
||||
await game.toNextTurn();
|
||||
expect(snorlax.status?.effect).toBeUndefined();
|
||||
expect(snorlax.getStatStage(Stat.ATK)).toBe(2);
|
||||
});
|
||||
|
||||
it("should preserve non-volatile status conditions", async () => {
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
@ -64,15 +93,14 @@ describe("Moves - Rest", () => {
|
||||
|
||||
it.each<{ name: string; status?: StatusEffect; ability?: Abilities; dmg?: number }>([
|
||||
{ name: "is at full HP", dmg: 0 },
|
||||
{ name: "is affected by Electric Terrain", ability: Abilities.ELECTRIC_SURGE },
|
||||
{ name: "is affected by Misty Terrain", ability: Abilities.MISTY_SURGE },
|
||||
{ name: "is grounded on Electric Terrain", ability: Abilities.ELECTRIC_SURGE },
|
||||
{ name: "is grounded on Misty Terrain", ability: Abilities.MISTY_SURGE },
|
||||
{ name: "has Comatose", ability: Abilities.COMATOSE },
|
||||
])("should fail if the user $name", async ({ status = StatusEffect.NONE, ability = Abilities.NONE, dmg = 1 }) => {
|
||||
game.override.ability(ability);
|
||||
game.override.ability(ability).statusEffect(status);
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.trySetStatus(status);
|
||||
|
||||
snorlax.hp = snorlax.getMaxHp() - dmg;
|
||||
|
||||
@ -83,21 +111,22 @@ describe("Moves - Rest", () => {
|
||||
});
|
||||
|
||||
it("should fail if called while already asleep", async () => {
|
||||
game.override.statusEffect(StatusEffect.SLEEP).moveset([Moves.REST, Moves.SLEEP_TALK]);
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.hp = 1;
|
||||
snorlax.trySetStatus(StatusEffect.SLEEP);
|
||||
|
||||
game.move.select(Moves.SLEEP_TALK);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(snorlax.isFullHp()).toBe(false);
|
||||
expect(snorlax.status?.effect).toBeUndefined();
|
||||
expect(snorlax.getLastXMoves().map(tm => tm.result)).toEqual([MoveResult.FAIL, MoveResult.SUCCESS]);
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
expect(snorlax.getLastXMoves(-1).map(tm => tm.result)).toEqual([MoveResult.FAIL, MoveResult.SUCCESS]);
|
||||
});
|
||||
|
||||
it("should succeed if called the turn after waking up", async () => {
|
||||
game.override.statusEffect(StatusEffect.SLEEP);
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
@ -112,6 +141,6 @@ describe("Moves - Rest", () => {
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
expect(snorlax.isFullHp()).toBe(true);
|
||||
expect(snorlax.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
expect(snorlax.status?.sleepTurnsRemaining).toBe(3);
|
||||
expect(snorlax.status?.sleepTurnsRemaining).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user