mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-29 21:12:45 +02:00
Reworked status code, fixed bugs and added Rest tests
This commit is contained in:
parent
663c64fdb4
commit
45bbaf2b25
@ -1246,7 +1246,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
|
||||
/**
|
||||
* Determine if the move type change attribute can be applied
|
||||
*
|
||||
*
|
||||
* Can be applied if:
|
||||
* - The ability's condition is met, e.g. pixilate only boosts normal moves,
|
||||
* - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode Moves.MULTI_ATTACK}
|
||||
@ -1262,7 +1262,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
*/
|
||||
override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean {
|
||||
return (!this.condition || this.condition(pokemon, _defender, move)) &&
|
||||
!noAbilityTypeOverrideMoves.has(move.id) &&
|
||||
!noAbilityTypeOverrideMoves.has(move.id) &&
|
||||
(!pokemon.isTerastallized ||
|
||||
(move.id !== Moves.TERA_BLAST &&
|
||||
(move.id !== Moves.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(Species.TERAPAGOS))));
|
||||
@ -7444,7 +7444,7 @@ export function initAbilities() {
|
||||
new Ability(Abilities.ORICHALCUM_PULSE, 9)
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY)
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 4 / 3),
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 4 / 3), // No game freak rounding jank
|
||||
new Ability(Abilities.HADRON_ENGINE, 9)
|
||||
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
||||
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
||||
|
@ -827,32 +827,37 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
||||
}
|
||||
|
||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
if (pokemon.isGrounded()) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
if (pokemon.isOfType(PokemonType.POISON)) {
|
||||
this.neutralized = true;
|
||||
if (globalScene.arena.removeTag(this.tagType)) {
|
||||
globalScene.queueMessage(
|
||||
i18next.t("arenaTag:toxicSpikesActivateTrapPoison", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
moveName: this.getMoveName(),
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} else if (!pokemon.status) {
|
||||
const toxic = this.layers > 1;
|
||||
if (
|
||||
pokemon.trySetStatus(!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, true, null, 0, this.getMoveName())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!pokemon.isGrounded()) {
|
||||
return false;
|
||||
}
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// poision types will neutralize toxic spikes
|
||||
if (pokemon.isOfType(PokemonType.POISON)) {
|
||||
this.neutralized = true;
|
||||
if (globalScene.arena.removeTag(this.tagType)) {
|
||||
globalScene.queueMessage(
|
||||
i18next.t("arenaTag:toxicSpikesActivateTrapPoison", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
moveName: this.getMoveName(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pokemon.status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pokemon.trySetStatus(
|
||||
this.layers === 1 ? StatusEffect.POISON : StatusEffect.TOXIC,
|
||||
true,
|
||||
null,
|
||||
this.getMoveName(),
|
||||
);
|
||||
}
|
||||
|
||||
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
||||
|
@ -2440,31 +2440,36 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
|
||||
|
||||
export class StatusEffectAttr extends MoveEffectAttr {
|
||||
public effect: StatusEffect;
|
||||
public turnsRemaining?: number;
|
||||
public overrideStatus: boolean = false;
|
||||
private overrideStatus: boolean;
|
||||
|
||||
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
|
||||
constructor(effect: StatusEffect, selfTarget = false, overrideStatus = false) {
|
||||
super(selfTarget);
|
||||
|
||||
this.effect = effect;
|
||||
this.turnsRemaining = turnsRemaining;
|
||||
this.overrideStatus = overrideStatus;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
const statusCheck = moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance;
|
||||
if (!statusCheck) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const quiet = move.category !== MoveCategory.STATUS;
|
||||
if (statusCheck) {
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (user !== target && move.category === MoveCategory.STATUS && !target.canSetStatus(this.effect, quiet, false, user, true)) {
|
||||
return false;
|
||||
}
|
||||
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
|
||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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)
|
||||
) {
|
||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -2478,11 +2483,37 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute to put the target to sleep for a fixed duration and cure its status.
|
||||
* Used for {@linkcode Moves.REST}.
|
||||
*/
|
||||
export class RestAttr extends StatusEffectAttr {
|
||||
private duration: number;
|
||||
|
||||
constructor(
|
||||
duration: number,
|
||||
overrideStatus: boolean
|
||||
){
|
||||
// Sleep is the only duration-based status ATM
|
||||
super(StatusEffect.SLEEP, true, overrideStatus);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class MultiStatusEffectAttr extends StatusEffectAttr {
|
||||
public effects: StatusEffect[];
|
||||
|
||||
constructor(effects: StatusEffect[], selfTarget?: boolean, turnsRemaining?: number, overrideStatus?: boolean) {
|
||||
super(effects[0], selfTarget, turnsRemaining, overrideStatus);
|
||||
constructor(effects: StatusEffect[], selfTarget?: boolean) {
|
||||
super(effects[0], selfTarget);
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
@ -8704,7 +8735,7 @@ export function initMoves() {
|
||||
.attr(MultiHitAttr, MultiHitType._2)
|
||||
.makesContact(false),
|
||||
new SelfStatusMove(Moves.REST, PokemonType.PSYCHIC, -1, 5, -1, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.SLEEP, true, 3, true)
|
||||
.attr(RestAttr, 3, true)
|
||||
.attr(HealAttr, 1, true)
|
||||
.condition((user, target, move) => !user.isFullHp() && user.canSetStatus(StatusEffect.SLEEP, true, true, user))
|
||||
.triageMove(),
|
||||
|
@ -5433,12 +5433,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a status effect can be applied to the Pokemon.
|
||||
* Check if a status effect can be applied to this {@linckode Pokemon}.
|
||||
*
|
||||
* @param effect The {@linkcode StatusEffect} whose applicability is being checked
|
||||
* @param quiet Whether in-battle messages should trigger or not
|
||||
* @param overrideStatus Whether the Pokemon's current status can be overriden
|
||||
* @param sourcePokemon The Pokemon that is setting the status effect
|
||||
* @param effect - The {@linkcode StatusEffect} whose applicability is being checked
|
||||
* @param quiet - Whether to suppress in-battle messages for status checks; default `false`
|
||||
* @param overrideStatus - Whether to allow overriding the Pokemon's current status with a different one; default `false`
|
||||
* @param sourcePokemon - The {@linkcode Pokemon} applying the status effect to the target
|
||||
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
||||
*/
|
||||
canSetStatus(
|
||||
@ -5586,7 +5586,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
effect?: StatusEffect,
|
||||
asPhase = false,
|
||||
sourcePokemon: Pokemon | null = null,
|
||||
turnsRemaining = 0,
|
||||
sourceText: string | null = null,
|
||||
overrideStatus?: boolean,
|
||||
quiet = true,
|
||||
@ -5618,7 +5617,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
new ObtainStatusEffectPhase(
|
||||
this.getBattlerIndex(),
|
||||
effect,
|
||||
turnsRemaining,
|
||||
sourceText,
|
||||
sourcePokemon,
|
||||
),
|
||||
|
@ -1756,7 +1756,7 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
|
||||
* @returns `true` if the status effect was applied successfully
|
||||
*/
|
||||
override apply(pokemon: Pokemon): boolean {
|
||||
return pokemon.trySetStatus(this.effect, true, undefined, undefined, this.type.name);
|
||||
return pokemon.trySetStatus(this.effect, true, pokemon, this.type.name);
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||
|
@ -10,58 +10,54 @@ import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
|
||||
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability";
|
||||
import { isNullOrUndefined } from "#app/utils/common";
|
||||
|
||||
/** The phase where pokemon obtain status effects. */
|
||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||
private statusEffect?: StatusEffect;
|
||||
private turnsRemaining?: number;
|
||||
private sourceText?: string | null;
|
||||
private sourcePokemon?: Pokemon | null;
|
||||
|
||||
constructor(
|
||||
battlerIndex: BattlerIndex,
|
||||
statusEffect?: StatusEffect,
|
||||
turnsRemaining?: number,
|
||||
sourceText?: string | null,
|
||||
sourcePokemon?: Pokemon | null,
|
||||
) {
|
||||
super(battlerIndex);
|
||||
|
||||
this.statusEffect = statusEffect;
|
||||
this.turnsRemaining = turnsRemaining;
|
||||
this.sourceText = sourceText;
|
||||
this.sourcePokemon = sourcePokemon;
|
||||
}
|
||||
|
||||
start() {
|
||||
const pokemon = this.getPokemon();
|
||||
if (pokemon && !pokemon.status) {
|
||||
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
||||
if (this.turnsRemaining) {
|
||||
pokemon.status!.sleepTurnsRemaining = this.turnsRemaining;
|
||||
}
|
||||
pokemon.updateInfo(true);
|
||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => {
|
||||
globalScene.queueMessage(
|
||||
getStatusEffectObtainText(
|
||||
this.statusEffect,
|
||||
getPokemonNameWithAffix(pokemon),
|
||||
this.sourceText ?? undefined,
|
||||
),
|
||||
);
|
||||
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
|
||||
globalScene.arena.setIgnoreAbilities(false);
|
||||
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon);
|
||||
}
|
||||
this.end();
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (pokemon.status?.effect === this.statusEffect) {
|
||||
if (pokemon.status?.effect === this.statusEffect) {
|
||||
globalScene.queueMessage(
|
||||
getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)),
|
||||
);
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
this.end();
|
||||
|
||||
if (!pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
||||
// status application passes
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
pokemon.updateInfo(true);
|
||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => {
|
||||
globalScene.queueMessage(
|
||||
getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined),
|
||||
);
|
||||
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);
|
||||
}
|
||||
this.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -22,7 +23,7 @@ describe("Abilities - Corrosion", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.SPLASH])
|
||||
.moveset([Moves.SPLASH, Moves.TOXIC, Moves.TOXIC_SPIKES])
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.GRIMER)
|
||||
@ -30,9 +31,35 @@ describe("Abilities - Corrosion", () => {
|
||||
.enemyMoveset(Moves.TOXIC);
|
||||
});
|
||||
|
||||
it("If a Poison- or Steel-type Pokémon with this Ability poisons a target with Synchronize, Synchronize does not gain the ability to poison Poison- or Steel-type Pokémon.", async () => {
|
||||
it.each<{ name: string; species: Species }>([
|
||||
{ name: "Poison", species: Species.GRIMER },
|
||||
{ name: "Steel", species: Species.KLINK },
|
||||
])("should grant the user the ability to poison $name-type opponents", async ({ species }) => {
|
||||
game.override.ability(Abilities.SYNCHRONIZE);
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
await game.classicMode.startBattle([species]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
expect(enemyPokemon.status).toBeUndefined();
|
||||
|
||||
game.move.select(Moves.TOXIC);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemyPokemon.status).toBeDefined();
|
||||
});
|
||||
|
||||
it("should not affect Toxic Spikes", async () => {
|
||||
await game.classicMode.startBattle([Species.SALANDIT]);
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.doKillOpponents();
|
||||
await game.toNextWave();
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
expect(enemyPokemon.status).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not affect an opponent's Synchronize ability", async () => {
|
||||
game.override.ability(Abilities.SYNCHRONIZE);
|
||||
await game.classicMode.startBattle([Species.ARBOK]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
@ -43,4 +70,16 @@ describe("Abilities - Corrosion", () => {
|
||||
expect(playerPokemon!.status).toBeDefined();
|
||||
expect(enemyPokemon!.status).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should affect the user's held a Toxic Orb", async () => {
|
||||
game.override.startingHeldItems([{ name: "TOXIC_ORB", count: 1 }]);
|
||||
await game.classicMode.startBattle([Species.SALAZZLE]);
|
||||
|
||||
const salazzle = game.scene.getPlayerPokemon()!;
|
||||
expect(salazzle.status?.effect).toBeUndefined();
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(salazzle.status?.effect).toBe(StatusEffect.TOXIC);
|
||||
});
|
||||
});
|
||||
|
134
test/moves/rest.test.ts
Normal file
134
test/moves/rest.test.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Rest", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.REST, Moves.SLEEP_TALK])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.EKANS)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should fully heal the user, cure its status and put it to sleep", async () => {
|
||||
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);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(snorlax.isFullHp()).toBe(true);
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
});
|
||||
|
||||
it("should preserve non-volatile conditions", async () => {
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.hp = 1;
|
||||
snorlax.addTag(BattlerTagType.CONFUSED, 999);
|
||||
|
||||
game.move.select(Moves.REST);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(snorlax.getTag(BattlerTagType.CONFUSED)).toBeDefined();
|
||||
});
|
||||
|
||||
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: "has Comatose", ability: Abilities.COMATOSE },
|
||||
])("should fail if the user $name", async ({ status = StatusEffect.NONE, ability = Abilities.NONE, dmg = 1 }) => {
|
||||
game.override.ability(ability);
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.trySetStatus(status);
|
||||
|
||||
snorlax.hp = snorlax.getMaxHp() - dmg;
|
||||
|
||||
game.move.select(Moves.REST);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(snorlax.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should fail if called while already asleep", async () => {
|
||||
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]);
|
||||
});
|
||||
|
||||
it("should succeed if called the turn after waking up", async () => {
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const snorlax = game.scene.getPlayerPokemon()!;
|
||||
snorlax.hp = 1;
|
||||
|
||||
// Turn 1
|
||||
game.move.select(Moves.REST);
|
||||
await game.toNextTurn();
|
||||
|
||||
snorlax.hp = 1;
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
|
||||
// Turn 2
|
||||
game.move.select(Moves.REST);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
|
||||
// Turn 3
|
||||
game.move.select(Moves.REST);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(snorlax.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
|
||||
// Turn 4 (wakeup)
|
||||
game.move.select(Moves.REST);
|
||||
await game.toNextTurn();
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
@ -36,6 +36,31 @@ describe("Moves - Sleep Talk", () => {
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it("should call a random valid move if the user is asleep", async () => {
|
||||
game.override.moveset([Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.SWORDS_DANCE]); // Dig and Fly are invalid moves, Swords Dance should always be called
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
|
||||
game.move.select(Moves.SLEEP_TALK);
|
||||
await game.toNextTurn();
|
||||
const feebas = game.scene.getPlayerPokemon()!;
|
||||
|
||||
expect(feebas.getStatStage(Stat.ATK)).toBe(2);
|
||||
expect(feebas.getLastXMoves()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
move: Moves.SWORDS_DANCE,
|
||||
result: MoveResult.SUCCESS,
|
||||
virtual: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
move: Moves.SLEEP_TALK,
|
||||
result: MoveResult.SUCCESS,
|
||||
virtual: false,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail when the user is not asleep", async () => {
|
||||
game.override.statusEffect(StatusEffect.NONE);
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
@ -54,15 +79,6 @@ describe("Moves - Sleep Talk", () => {
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should call a random valid move if the user is asleep", async () => {
|
||||
game.override.moveset([Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.SWORDS_DANCE]); // Dig and Fly are invalid moves, Swords Dance should always be called
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
|
||||
game.move.select(Moves.SLEEP_TALK);
|
||||
await game.toNextTurn();
|
||||
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK));
|
||||
});
|
||||
|
||||
it("should apply secondary effects of a move", async () => {
|
||||
game.override.moveset([Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.WOOD_HAMMER]); // Dig and Fly are invalid moves, Wood Hammer should always be called
|
||||
await game.classicMode.startBattle();
|
||||
|
Loading…
Reference in New Issue
Block a user