mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-30 13:33:01 +02:00
Reworked status code, fixed bugs and added Rest tests
This commit is contained in:
parent
663c64fdb4
commit
45bbaf2b25
@ -7444,7 +7444,7 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.ORICHALCUM_PULSE, 9)
|
new Ability(Abilities.ORICHALCUM_PULSE, 9)
|
||||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY)
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY)
|
||||||
.attr(PostBiomeChangeWeatherChangeAbAttr, 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)
|
new Ability(Abilities.HADRON_ENGINE, 9)
|
||||||
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
||||||
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
||||||
|
@ -827,10 +827,14 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
if (pokemon.isGrounded()) {
|
if (!pokemon.isGrounded()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// poision types will neutralize toxic spikes
|
||||||
if (pokemon.isOfType(PokemonType.POISON)) {
|
if (pokemon.isOfType(PokemonType.POISON)) {
|
||||||
this.neutralized = true;
|
this.neutralized = true;
|
||||||
if (globalScene.arena.removeTag(this.tagType)) {
|
if (globalScene.arena.removeTag(this.tagType)) {
|
||||||
@ -840,21 +844,22 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||||||
moveName: this.getMoveName(),
|
moveName: this.getMoveName(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return true;
|
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.status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return pokemon.trySetStatus(
|
||||||
|
this.layers === 1 ? StatusEffect.POISON : StatusEffect.TOXIC,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
this.getMoveName(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
||||||
if (pokemon.isGrounded() || !pokemon.canSetStatus(StatusEffect.POISON, true)) {
|
if (pokemon.isGrounded() || !pokemon.canSetStatus(StatusEffect.POISON, true)) {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -2440,32 +2440,37 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
|
|||||||
|
|
||||||
export class StatusEffectAttr extends MoveEffectAttr {
|
export class StatusEffectAttr extends MoveEffectAttr {
|
||||||
public effect: StatusEffect;
|
public effect: StatusEffect;
|
||||||
public turnsRemaining?: number;
|
private overrideStatus: boolean;
|
||||||
public overrideStatus: boolean = false;
|
|
||||||
|
|
||||||
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
|
constructor(effect: StatusEffect, selfTarget = false, overrideStatus = false) {
|
||||||
super(selfTarget);
|
super(selfTarget);
|
||||||
|
|
||||||
this.effect = effect;
|
this.effect = effect;
|
||||||
this.turnsRemaining = turnsRemaining;
|
|
||||||
this.overrideStatus = overrideStatus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||||
const statusCheck = moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance;
|
const statusCheck = moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance;
|
||||||
const quiet = move.category !== MoveCategory.STATUS;
|
if (!statusCheck) {
|
||||||
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;
|
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)) {
|
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)
|
||||||
|
) {
|
||||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
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 {
|
export class MultiStatusEffectAttr extends StatusEffectAttr {
|
||||||
public effects: StatusEffect[];
|
public effects: StatusEffect[];
|
||||||
|
|
||||||
constructor(effects: StatusEffect[], selfTarget?: boolean, turnsRemaining?: number, overrideStatus?: boolean) {
|
constructor(effects: StatusEffect[], selfTarget?: boolean) {
|
||||||
super(effects[0], selfTarget, turnsRemaining, overrideStatus);
|
super(effects[0], selfTarget);
|
||||||
this.effects = effects;
|
this.effects = effects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -8704,7 +8735,7 @@ 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(StatusEffectAttr, StatusEffect.SLEEP, true, 3, true)
|
.attr(RestAttr, 3, true)
|
||||||
.attr(HealAttr, 1, true)
|
.attr(HealAttr, 1, true)
|
||||||
.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(),
|
||||||
|
@ -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 effect - The {@linkcode StatusEffect} whose applicability is being checked
|
||||||
* @param quiet Whether in-battle messages should trigger or not
|
* @param quiet - Whether to suppress in-battle messages for status checks; default `false`
|
||||||
* @param overrideStatus Whether the Pokemon's current status can be overriden
|
* @param overrideStatus - Whether to allow overriding the Pokemon's current status with a different one; default `false`
|
||||||
* @param sourcePokemon The Pokemon that is setting the status effect
|
* @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
|
||||||
*/
|
*/
|
||||||
canSetStatus(
|
canSetStatus(
|
||||||
@ -5586,7 +5586,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
effect?: StatusEffect,
|
effect?: StatusEffect,
|
||||||
asPhase = false,
|
asPhase = false,
|
||||||
sourcePokemon: Pokemon | null = null,
|
sourcePokemon: Pokemon | null = null,
|
||||||
turnsRemaining = 0,
|
|
||||||
sourceText: string | null = null,
|
sourceText: string | null = null,
|
||||||
overrideStatus?: boolean,
|
overrideStatus?: boolean,
|
||||||
quiet = true,
|
quiet = true,
|
||||||
@ -5618,7 +5617,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
new ObtainStatusEffectPhase(
|
new ObtainStatusEffectPhase(
|
||||||
this.getBattlerIndex(),
|
this.getBattlerIndex(),
|
||||||
effect,
|
effect,
|
||||||
turnsRemaining,
|
|
||||||
sourceText,
|
sourceText,
|
||||||
sourcePokemon,
|
sourcePokemon,
|
||||||
),
|
),
|
||||||
|
@ -1756,7 +1756,7 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
|
|||||||
* @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, undefined, undefined, this.type.name);
|
return pokemon.trySetStatus(this.effect, true, pokemon, this.type.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||||
|
@ -10,58 +10,54 @@ import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
|
|||||||
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability";
|
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
|
|
||||||
|
/** 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (pokemon && !pokemon.status) {
|
if (pokemon.status?.effect === this.statusEffect) {
|
||||||
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
globalScene.queueMessage(
|
||||||
if (this.turnsRemaining) {
|
getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)),
|
||||||
pokemon.status!.sleepTurnsRemaining = this.turnsRemaining;
|
);
|
||||||
|
this.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
||||||
|
// status application passes
|
||||||
|
this.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
||||||
getStatusEffectObtainText(
|
getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined),
|
||||||
this.statusEffect,
|
|
||||||
getPokemonNameWithAffix(pokemon),
|
|
||||||
this.sourceText ?? undefined,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
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
|
// 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);
|
||||||
}
|
}
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (pokemon.status?.effect === this.statusEffect) {
|
|
||||||
globalScene.queueMessage(
|
|
||||||
getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.end();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
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";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
@ -22,7 +23,7 @@ describe("Abilities - Corrosion", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([Moves.SPLASH])
|
.moveset([Moves.SPLASH, Moves.TOXIC, Moves.TOXIC_SPIKES])
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.disableCrits()
|
.disableCrits()
|
||||||
.enemySpecies(Species.GRIMER)
|
.enemySpecies(Species.GRIMER)
|
||||||
@ -30,9 +31,35 @@ describe("Abilities - Corrosion", () => {
|
|||||||
.enemyMoveset(Moves.TOXIC);
|
.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);
|
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 playerPokemon = game.scene.getPlayerPokemon();
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
@ -43,4 +70,16 @@ describe("Abilities - Corrosion", () => {
|
|||||||
expect(playerPokemon!.status).toBeDefined();
|
expect(playerPokemon!.status).toBeDefined();
|
||||||
expect(enemyPokemon!.status).toBeUndefined();
|
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);
|
.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 () => {
|
it("should fail when the user is not asleep", async () => {
|
||||||
game.override.statusEffect(StatusEffect.NONE);
|
game.override.statusEffect(StatusEffect.NONE);
|
||||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||||
@ -54,15 +79,6 @@ describe("Moves - Sleep Talk", () => {
|
|||||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
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 () => {
|
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
|
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();
|
await game.classicMode.startBattle();
|
||||||
|
Loading…
Reference in New Issue
Block a user