mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-08 08:29:37 +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,
|
this.layers === 1 ? StatusEffect.POISON : StatusEffect.TOXIC,
|
||||||
true,
|
true,
|
||||||
null,
|
null,
|
||||||
|
0,
|
||||||
this.getMoveName(),
|
this.getMoveName(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1873,11 +1873,11 @@ export class HealAttr extends MoveEffectAttr {
|
|||||||
/** Should an animation be shown? */
|
/** Should an animation be shown? */
|
||||||
private showAnim: boolean;
|
private showAnim: boolean;
|
||||||
|
|
||||||
constructor(healRatio?: number, showAnim?: boolean, selfTarget?: boolean) {
|
constructor(healRatio = 1, showAnim = false, selfTarget = true) {
|
||||||
super(selfTarget === undefined || selfTarget);
|
super(selfTarget);
|
||||||
|
|
||||||
this.healRatio = healRatio || 1;
|
this.healRatio = healRatio;
|
||||||
this.showAnim = !!showAnim;
|
this.showAnim = showAnim;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
@ -2440,9 +2440,8 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
|
|||||||
|
|
||||||
export class StatusEffectAttr extends MoveEffectAttr {
|
export class StatusEffectAttr extends MoveEffectAttr {
|
||||||
public effect: StatusEffect;
|
public effect: StatusEffect;
|
||||||
private overrideStatus: boolean;
|
|
||||||
|
|
||||||
constructor(effect: StatusEffect, selfTarget = false, overrideStatus = false) {
|
constructor(effect: StatusEffect, selfTarget = false) {
|
||||||
super(selfTarget);
|
super(selfTarget);
|
||||||
|
|
||||||
this.effect = effect;
|
this.effect = effect;
|
||||||
@ -2455,18 +2454,11 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// non-status moves don't play sound effects for failures
|
||||||
const quiet = move.category !== MoveCategory.STATUS;
|
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 (
|
if (
|
||||||
(!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
this.doSetStatus(this.selfTarget ? user : target, user, quiet)
|
||||||
&& pokemon.trySetStatus(this.effect, true, user, null, this.overrideStatus, quiet)
|
|
||||||
) {
|
) {
|
||||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||||
return true;
|
return true;
|
||||||
@ -2476,10 +2468,23 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
|
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;
|
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 {
|
export class RestAttr extends StatusEffectAttr {
|
||||||
private duration: number;
|
private duration: number;
|
||||||
|
|
||||||
constructor(
|
constructor(duration: number) {
|
||||||
duration: number,
|
|
||||||
overrideStatus: boolean
|
|
||||||
){
|
|
||||||
// Sleep is the only duration-based status ATM
|
// Sleep is the only duration-based status ATM
|
||||||
super(StatusEffect.SLEEP, true, overrideStatus);
|
super(StatusEffect.SLEEP, true);
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
// TODO: Add custom text for rest and make `HealAttr` no longer cause status
|
||||||
const didStatus = super.apply(user, target, move, args);
|
protected override doSetStatus(pokemon: Pokemon, user: Pokemon, quiet: boolean): boolean {
|
||||||
if (didStatus && user.status?.effect === this.effect) {
|
return pokemon.trySetStatus(this.effect, true, user, this.duration, null, true, quiet)
|
||||||
user.status.sleepTurnsRemaining = this.duration;
|
|
||||||
}
|
|
||||||
return didStatus;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2543,25 +2542,39 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
* @returns `true` if Psycho Shift's effect is able to be applied to the target
|
* @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 {
|
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||||
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
// 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.status) {
|
if (!target.trySetStatus(statusToApply, true, user)) {
|
||||||
return false;
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return trySetStatus;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
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[]) {
|
constructor(selfTarget: boolean, effects: StatusEffect | StatusEffect[]) {
|
||||||
super(selfTarget, { lastHitOnly: true });
|
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)
|
.attr(MultiHitAttr, MultiHitType._2)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new SelfStatusMove(Moves.REST, PokemonType.PSYCHIC, -1, 5, -1, 0, 1)
|
new SelfStatusMove(Moves.REST, PokemonType.PSYCHIC, -1, 5, -1, 0, 1)
|
||||||
.attr(RestAttr, 3, true)
|
|
||||||
.attr(HealAttr, 1, true)
|
.attr(HealAttr, 1, true)
|
||||||
|
.attr(RestAttr, 3)
|
||||||
.condition((user, target, move) => !user.isFullHp() && user.canSetStatus(StatusEffect.SLEEP, true, true, user))
|
.condition((user, target, move) => !user.isFullHp() && user.canSetStatus(StatusEffect.SLEEP, true, true, user))
|
||||||
.triageMove(),
|
.triageMove(),
|
||||||
new AttackMove(Moves.ROCK_SLIDE, PokemonType.ROCK, MoveCategory.PHYSICAL, 75, 90, 10, 30, 0, 1)
|
new AttackMove(Moves.ROCK_SLIDE, PokemonType.ROCK, MoveCategory.PHYSICAL, 75, 90, 10, 30, 0, 1)
|
||||||
@ -9469,15 +9485,7 @@ export function initMoves() {
|
|||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new StatusMove(Moves.PSYCHO_SHIFT, PokemonType.PSYCHIC, 100, 10, -1, 0, 4)
|
new StatusMove(Moves.PSYCHO_SHIFT, PokemonType.PSYCHIC, 100, 10, -1, 0, 4)
|
||||||
.attr(PsychoShiftEffectAttr)
|
.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);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
new AttackMove(Moves.TRUMP_CARD, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4)
|
new AttackMove(Moves.TRUMP_CARD, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4)
|
||||||
.makesContact()
|
.makesContact()
|
||||||
.attr(LessPPMorePowerAttr),
|
.attr(LessPPMorePowerAttr),
|
||||||
|
@ -154,6 +154,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
|
|||||||
return randIntRange(0, 2) ? statusA : statusB;
|
return randIntRange(0, 2) ? statusA : statusB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this a type and remove these
|
||||||
/**
|
/**
|
||||||
* Gets all non volatile status effects
|
* Gets all non volatile status effects
|
||||||
* @returns A list containing 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 {
|
export enum StatusEffect {
|
||||||
NONE,
|
NONE,
|
||||||
POISON,
|
POISON,
|
||||||
|
@ -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) {
|
if (!effect || quiet) {
|
||||||
return;
|
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 sourcePokemon - The {@linkcode Pokemon} applying the status effect to the target
|
||||||
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
* @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(
|
canSetStatus(
|
||||||
effect: StatusEffect | undefined,
|
effect: StatusEffect,
|
||||||
quiet = false,
|
quiet = false,
|
||||||
overrideStatus = false,
|
overrideStatus = false,
|
||||||
sourcePokemon: Pokemon | null = null,
|
sourcePokemon: Pokemon | null = null,
|
||||||
ignoreField = false,
|
ignoreField = false,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
|
// Status-overriding moves (ie Rest) fail if their respective status already exists
|
||||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
||||||
this.queueImmuneMessage(quiet, effect);
|
this.queueImmuneMessage(quiet, effect);
|
||||||
return false;
|
return false;
|
||||||
@ -5450,19 +5453,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
const types = this.getTypes(true, true);
|
const types = this.getTypes(true, true);
|
||||||
|
|
||||||
|
// Check for specific immunities for certain statuses
|
||||||
|
let isImmune = false;
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case StatusEffect.POISON:
|
case StatusEffect.POISON:
|
||||||
case StatusEffect.TOXIC:
|
case StatusEffect.TOXIC:
|
||||||
// Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity
|
// Check for type based immunities and/or Corrosion
|
||||||
const poisonImmunity = types.map(defType => {
|
isImmune = types.some(defType => {
|
||||||
// Check if the Pokemon is not immune to Poison/Toxic
|
|
||||||
if (defType !== PokemonType.POISON && defType !== PokemonType.STEEL) {
|
if (defType !== PokemonType.POISON && defType !== PokemonType.STEEL) {
|
||||||
return false;
|
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);
|
const cancelImmunity = new BooleanHolder(false);
|
||||||
if (sourcePokemon) {
|
|
||||||
applyAbAttrs(
|
applyAbAttrs(
|
||||||
IgnoreTypeStatusEffectImmunityAbAttr,
|
IgnoreTypeStatusEffectImmunityAbAttr,
|
||||||
sourcePokemon,
|
sourcePokemon,
|
||||||
@ -5471,57 +5477,36 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
effect,
|
effect,
|
||||||
defType,
|
defType,
|
||||||
);
|
);
|
||||||
if (cancelImmunity.value) {
|
return 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StatusEffect.PARALYSIS:
|
case StatusEffect.PARALYSIS:
|
||||||
if (this.isOfType(PokemonType.ELECTRIC)) {
|
isImmune = this.isOfType(PokemonType.ELECTRIC)
|
||||||
this.queueImmuneMessage(quiet, effect);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StatusEffect.SLEEP:
|
case StatusEffect.SLEEP:
|
||||||
if (
|
isImmune =
|
||||||
this.isGrounded() &&
|
this.isGrounded() &&
|
||||||
globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC
|
globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC;
|
||||||
) {
|
|
||||||
this.queueImmuneMessage(quiet, effect);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StatusEffect.FREEZE:
|
case StatusEffect.FREEZE:
|
||||||
if (
|
isImmune =
|
||||||
this.isOfType(PokemonType.ICE) ||
|
this.isOfType(PokemonType.ICE) ||
|
||||||
(!ignoreField &&
|
!ignoreField &&
|
||||||
globalScene?.arena?.weather?.weatherType &&
|
|
||||||
[WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(
|
[WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(
|
||||||
globalScene.arena.weather.weatherType,
|
globalScene.arena.weather?.weatherType ?? WeatherType.NONE,
|
||||||
))
|
)
|
||||||
) {
|
|
||||||
this.queueImmuneMessage(quiet, effect);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StatusEffect.BURN:
|
case StatusEffect.BURN:
|
||||||
if (this.isOfType(PokemonType.FIRE)) {
|
isImmune = this.isOfType(PokemonType.FIRE)
|
||||||
this.queueImmuneMessage(quiet, effect);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isImmune) {
|
||||||
|
this.queueImmuneMessage(quiet, effect)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for cancellations from self/ally abilities
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreSetStatusAbAttrs(
|
applyPreSetStatusAbAttrs(
|
||||||
StatusEffectImmunityAbAttr,
|
StatusEffectImmunityAbAttr,
|
||||||
@ -5542,15 +5527,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
cancelled,
|
cancelled,
|
||||||
quiet, this, sourcePokemon,
|
quiet, this, sourcePokemon,
|
||||||
)
|
)
|
||||||
if (cancelled.value) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform safeguard checks
|
||||||
if (
|
if (
|
||||||
sourcePokemon &&
|
sourcePokemon &&
|
||||||
sourcePokemon !== this &&
|
sourcePokemon !== this &&
|
||||||
@ -5558,8 +5540,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
) {
|
) {
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
globalScene.queueMessage(
|
globalScene.queueMessage(
|
||||||
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this)
|
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this)})
|
||||||
}));
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -5567,14 +5549,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this take a destructured object as args to condense all these optional args...
|
||||||
trySetStatus(
|
trySetStatus(
|
||||||
effect?: StatusEffect,
|
effect: StatusEffect,
|
||||||
asPhase = false,
|
asPhase = false,
|
||||||
sourcePokemon: Pokemon | null = null,
|
sourcePokemon: Pokemon | null = null,
|
||||||
|
turnsRemaining?: number,
|
||||||
sourceText: string | null = null,
|
sourceText: string | null = null,
|
||||||
overrideStatus?: boolean,
|
overrideStatus?: boolean,
|
||||||
quiet = true,
|
quiet = true,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
// TODO: Remove uses of `asPhase=false` in favor of checking status directly
|
||||||
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -5598,47 +5583,76 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (overrideStatus) {
|
if (overrideStatus) {
|
||||||
this.resetStatus(false);
|
this.resetStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.unshiftPhase(
|
globalScene.unshiftPhase(
|
||||||
new ObtainStatusEffectPhase(
|
new ObtainStatusEffectPhase(
|
||||||
this.getBattlerIndex(),
|
this.getBattlerIndex(),
|
||||||
effect,
|
effect,
|
||||||
|
turnsRemaining,
|
||||||
sourceText,
|
sourceText,
|
||||||
sourcePokemon,
|
sourcePokemon,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this.doSetStatus(effect, turnsRemaining)
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sleepTurnsRemaining: NumberHolder;
|
/**
|
||||||
|
* 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) {
|
if (effect === StatusEffect.SLEEP) {
|
||||||
sleepTurnsRemaining = new NumberHolder(this.randBattleSeedIntRange(2, 4));
|
|
||||||
|
|
||||||
this.setFrameRate(4);
|
this.setFrameRate(4);
|
||||||
|
|
||||||
// If the user is invulnerable, lets remove their invulnerability when they fall asleep
|
// If the user is invulnerable, remove their invulnerability when they fall asleep
|
||||||
const invulnerableTags = [
|
const invulnTag = [
|
||||||
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 (invulnTag) {
|
||||||
|
this.removeTag(invulnTag);
|
||||||
if (tag) {
|
this.getMoveQueue().shift();
|
||||||
this.removeTag(tag);
|
|
||||||
this.getMoveQueue().pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
this.status = new Status(effect, 0, sleepTurnsRemaining);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the status of a pokemon.
|
* Resets the status of a pokemon.
|
||||||
* @param revive Whether revive should be cured; defaults to true.
|
* @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}.
|
* Attempt to inflicts the holder with the associated {@linkcode StatusEffect}.
|
||||||
* @param pokemon {@linkcode Pokemon} that holds the held item
|
* @param pokemon - The {@linkcode Pokemon} holds the item.
|
||||||
* @returns `true` if the status effect was applied successfully
|
* @returns `true` if the status effect was applied successfully
|
||||||
*/
|
*/
|
||||||
override apply(pokemon: Pokemon): boolean {
|
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 {
|
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||||
|
@ -12,19 +12,22 @@ import { isNullOrUndefined } from "#app/utils/common";
|
|||||||
|
|
||||||
/** The phase where pokemon obtain status effects. */
|
/** The phase where pokemon obtain status effects. */
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
private statusEffect?: StatusEffect;
|
private statusEffect: StatusEffect;
|
||||||
|
private turnsRemaining?: number;
|
||||||
private sourceText?: string | null;
|
private sourceText?: string | null;
|
||||||
private sourcePokemon?: Pokemon | null;
|
private sourcePokemon?: Pokemon | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
battlerIndex: BattlerIndex,
|
battlerIndex: BattlerIndex,
|
||||||
statusEffect?: StatusEffect,
|
statusEffect: StatusEffect,
|
||||||
|
turnsRemaining?: number,
|
||||||
sourceText?: string | null,
|
sourceText?: string | null,
|
||||||
sourcePokemon?: Pokemon | null,
|
sourcePokemon?: Pokemon | null,
|
||||||
) {
|
) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
|
|
||||||
this.statusEffect = statusEffect;
|
this.statusEffect = statusEffect;
|
||||||
|
this.turnsRemaining = turnsRemaining;
|
||||||
this.sourceText = sourceText;
|
this.sourceText = sourceText;
|
||||||
this.sourcePokemon = sourcePokemon;
|
this.sourcePokemon = sourcePokemon;
|
||||||
}
|
}
|
||||||
@ -32,19 +35,19 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
start() {
|
start() {
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (pokemon.status?.effect === this.statusEffect) {
|
if (pokemon.status?.effect === this.statusEffect) {
|
||||||
globalScene.queueMessage(
|
globalScene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon)));
|
||||||
getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)),
|
|
||||||
);
|
|
||||||
this.end();
|
this.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
if (!pokemon.canSetStatus(this.statusEffect, false, false, this.sourcePokemon)) {
|
||||||
// status application passes
|
// status application fails
|
||||||
this.end();
|
this.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pokemon.doSetStatus(this.statusEffect, this.turnsRemaining);
|
||||||
|
|
||||||
pokemon.updateInfo(true);
|
pokemon.updateInfo(true);
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => {
|
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => {
|
||||||
globalScene.queueMessage(
|
globalScene.queueMessage(
|
||||||
@ -52,8 +55,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
);
|
);
|
||||||
if (!isNullOrUndefined(this.statusEffect) && this.statusEffect !== StatusEffect.FAINT) {
|
if (!isNullOrUndefined(this.statusEffect) && this.statusEffect !== StatusEffect.FAINT) {
|
||||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
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);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon);
|
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon);
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ describe("Abilities - Corrosion", () => {
|
|||||||
expect(enemyPokemon!.status).toBeUndefined();
|
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 }]);
|
game.override.startingHeldItems([{ name: "TOXIC_ORB", count: 1 }]);
|
||||||
await game.classicMode.startBattle([Species.SALAZZLE]);
|
await game.classicMode.startBattle([Species.SALAZZLE]);
|
||||||
|
|
||||||
|
@ -25,15 +25,6 @@ describe("Spec - Pokemon", () => {
|
|||||||
game = new GameManager(phaserGame);
|
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", () => {
|
describe("Add To Party", () => {
|
||||||
let scene: BattleScene;
|
let scene: BattleScene;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { Abilities } from "#enums/abilities";
|
|||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
@ -25,7 +26,7 @@ describe("Moves - Rest", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([Moves.REST, Moves.SLEEP_TALK])
|
.moveset([Moves.REST, Moves.SWORDS_DANCE])
|
||||||
.ability(Abilities.BALL_FETCH)
|
.ability(Abilities.BALL_FETCH)
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.disableCrits()
|
.disableCrits()
|
||||||
@ -34,12 +35,12 @@ describe("Moves - Rest", () => {
|
|||||||
.enemyMoveset(Moves.SPLASH);
|
.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]);
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
const snorlax = game.scene.getPlayerPokemon()!;
|
const snorlax = game.scene.getPlayerPokemon()!;
|
||||||
snorlax.hp = 1;
|
snorlax.hp = 1;
|
||||||
snorlax.trySetStatus(StatusEffect.POISON);
|
|
||||||
expect(snorlax.status?.effect).toBe(StatusEffect.POISON);
|
expect(snorlax.status?.effect).toBe(StatusEffect.POISON);
|
||||||
|
|
||||||
game.move.select(Moves.REST);
|
game.move.select(Moves.REST);
|
||||||
@ -49,7 +50,35 @@ describe("Moves - Rest", () => {
|
|||||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
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]);
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
const snorlax = game.scene.getPlayerPokemon()!;
|
const snorlax = game.scene.getPlayerPokemon()!;
|
||||||
@ -64,15 +93,14 @@ describe("Moves - Rest", () => {
|
|||||||
|
|
||||||
it.each<{ name: string; status?: StatusEffect; ability?: Abilities; dmg?: number }>([
|
it.each<{ name: string; status?: StatusEffect; ability?: Abilities; dmg?: number }>([
|
||||||
{ name: "is at full HP", dmg: 0 },
|
{ name: "is at full HP", dmg: 0 },
|
||||||
{ name: "is affected by Electric Terrain", ability: Abilities.ELECTRIC_SURGE },
|
{ name: "is grounded on Electric Terrain", ability: Abilities.ELECTRIC_SURGE },
|
||||||
{ name: "is affected by Misty Terrain", ability: Abilities.MISTY_SURGE },
|
{ name: "is grounded on Misty Terrain", ability: Abilities.MISTY_SURGE },
|
||||||
{ name: "has Comatose", ability: Abilities.COMATOSE },
|
{ name: "has Comatose", ability: Abilities.COMATOSE },
|
||||||
])("should fail if the user $name", async ({ status = StatusEffect.NONE, ability = Abilities.NONE, dmg = 1 }) => {
|
])("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]);
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
const snorlax = game.scene.getPlayerPokemon()!;
|
const snorlax = game.scene.getPlayerPokemon()!;
|
||||||
snorlax.trySetStatus(status);
|
|
||||||
|
|
||||||
snorlax.hp = snorlax.getMaxHp() - dmg;
|
snorlax.hp = snorlax.getMaxHp() - dmg;
|
||||||
|
|
||||||
@ -83,21 +111,22 @@ describe("Moves - Rest", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail if called while already asleep", async () => {
|
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]);
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
const snorlax = game.scene.getPlayerPokemon()!;
|
const snorlax = game.scene.getPlayerPokemon()!;
|
||||||
snorlax.hp = 1;
|
snorlax.hp = 1;
|
||||||
snorlax.trySetStatus(StatusEffect.SLEEP);
|
|
||||||
|
|
||||||
game.move.select(Moves.SLEEP_TALK);
|
game.move.select(Moves.SLEEP_TALK);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(snorlax.isFullHp()).toBe(false);
|
expect(snorlax.isFullHp()).toBe(false);
|
||||||
expect(snorlax.status?.effect).toBeUndefined();
|
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
expect(snorlax.getLastXMoves().map(tm => tm.result)).toEqual([MoveResult.FAIL, MoveResult.SUCCESS]);
|
expect(snorlax.getLastXMoves(-1).map(tm => tm.result)).toEqual([MoveResult.FAIL, MoveResult.SUCCESS]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should succeed if called the turn after waking up", async () => {
|
it("should succeed if called the turn after waking up", async () => {
|
||||||
|
game.override.statusEffect(StatusEffect.SLEEP);
|
||||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
const snorlax = game.scene.getPlayerPokemon()!;
|
const snorlax = game.scene.getPlayerPokemon()!;
|
||||||
@ -112,6 +141,6 @@ describe("Moves - Rest", () => {
|
|||||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
expect(snorlax.isFullHp()).toBe(true);
|
expect(snorlax.isFullHp()).toBe(true);
|
||||||
expect(snorlax.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
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