mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-15 03:49:33 +02:00
Merge branch 'beta' into team_dupes
This commit is contained in:
commit
0251f578b3
@ -1865,7 +1865,7 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
generateRandomBiome(waveIndex: integer): Biome {
|
||||
const relWave = waveIndex % 250;
|
||||
const biomes = Utils.getEnumValues(Biome).slice(1, Utils.getEnumValues(Biome).filter(b => b >= 40).length * -1);
|
||||
const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END);
|
||||
const maxDepth = biomeDepths[Biome.END][0] - 2;
|
||||
const depthWeights = new Array(maxDepth + 1).fill(null)
|
||||
.map((_, i: integer) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250);
|
||||
@ -1878,9 +1878,9 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
const randInt = Utils.randSeedInt(totalWeight);
|
||||
|
||||
for (const biome of biomes) {
|
||||
if (randInt < biomeThresholds[biome]) {
|
||||
return biome;
|
||||
for (let i = 0; i < biomes.length; i++) {
|
||||
if (randInt < biomeThresholds[i]) {
|
||||
return biomes[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/mod
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { trainerConfigs } from "#app/data/trainer-config";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import type { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon";
|
||||
import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattleSpec } from "#enums/battle-spec";
|
||||
@ -45,12 +45,12 @@ export enum BattlerIndex {
|
||||
}
|
||||
|
||||
export interface TurnCommand {
|
||||
command: Command;
|
||||
cursor?: number;
|
||||
move?: QueuedMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
command: Command;
|
||||
cursor?: number;
|
||||
move?: TurnMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
}
|
||||
|
||||
export interface FaintLogEntry {
|
||||
|
@ -612,7 +612,7 @@ export class InterruptedTag extends BattlerTag {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
pokemon.getMoveQueue().shift();
|
||||
pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER });
|
||||
pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER, targets: []});
|
||||
}
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
|
@ -8,6 +8,7 @@ import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
|
||||
export interface DailyRunConfig {
|
||||
seed: integer;
|
||||
@ -71,3 +72,76 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve
|
||||
pokemon.destroy();
|
||||
return starter;
|
||||
}
|
||||
|
||||
interface BiomeWeights {
|
||||
[key: integer]: integer
|
||||
}
|
||||
|
||||
// Initially weighted by amount of exits each biome has
|
||||
// Town and End are set to 0 however
|
||||
// And some other biomes were balanced +1/-1 based on average size of the total daily.
|
||||
const dailyBiomeWeights: BiomeWeights = {
|
||||
[Biome.CAVE]: 3,
|
||||
[Biome.LAKE]: 3,
|
||||
[Biome.PLAINS]: 3,
|
||||
[Biome.SNOWY_FOREST]: 3,
|
||||
[Biome.SWAMP]: 3, // 2 -> 3
|
||||
[Biome.TALL_GRASS]: 3, // 2 -> 3
|
||||
|
||||
[Biome.ABYSS]: 2, // 3 -> 2
|
||||
[Biome.RUINS]: 2,
|
||||
[Biome.BADLANDS]: 2,
|
||||
[Biome.BEACH]: 2,
|
||||
[Biome.CONSTRUCTION_SITE]: 2,
|
||||
[Biome.DESERT]: 2,
|
||||
[Biome.DOJO]: 2, // 3 -> 2
|
||||
[Biome.FACTORY]: 2,
|
||||
[Biome.FAIRY_CAVE]: 2,
|
||||
[Biome.FOREST]: 2,
|
||||
[Biome.GRASS]: 2, // 1 -> 2
|
||||
[Biome.MEADOW]: 2,
|
||||
[Biome.MOUNTAIN]: 2, // 3 -> 2
|
||||
[Biome.SEA]: 2,
|
||||
[Biome.SEABED]: 2,
|
||||
[Biome.SLUM]: 2,
|
||||
[Biome.TEMPLE]: 2, // 3 -> 2
|
||||
[Biome.VOLCANO]: 2,
|
||||
|
||||
[Biome.GRAVEYARD]: 1,
|
||||
[Biome.ICE_CAVE]: 1,
|
||||
[Biome.ISLAND]: 1,
|
||||
[Biome.JUNGLE]: 1,
|
||||
[Biome.LABORATORY]: 1,
|
||||
[Biome.METROPOLIS]: 1,
|
||||
[Biome.POWER_PLANT]: 1,
|
||||
[Biome.SPACE]: 1,
|
||||
[Biome.WASTELAND]: 1,
|
||||
|
||||
[Biome.TOWN]: 0,
|
||||
[Biome.END]: 0,
|
||||
};
|
||||
|
||||
export function getDailyStartingBiome(): Biome {
|
||||
const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END);
|
||||
|
||||
let totalWeight = 0;
|
||||
const biomeThresholds: integer[] = [];
|
||||
for (const biome of biomes) {
|
||||
// Keep track of the total weight
|
||||
totalWeight += dailyBiomeWeights[biome];
|
||||
|
||||
// Keep track of each biomes cumulative weight
|
||||
biomeThresholds.push(totalWeight);
|
||||
}
|
||||
|
||||
const randInt = Utils.randSeedInt(totalWeight);
|
||||
|
||||
for (let i = 0; i < biomes.length; i++) {
|
||||
if (randInt < biomeThresholds[i]) {
|
||||
return biomes[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback in case something went wrong
|
||||
return biomes[Utils.randSeedInt(biomes.length)];
|
||||
}
|
||||
|
864
src/data/move.ts
864
src/data/move.ts
File diff suppressed because it is too large
Load Diff
@ -3298,7 +3298,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
|
||||
getMoveQueue(): QueuedMove[] {
|
||||
getMoveQueue(): TurnMove[] {
|
||||
return this.summonData.moveQueue;
|
||||
}
|
||||
|
||||
@ -4810,17 +4810,19 @@ export class EnemyPokemon extends Pokemon {
|
||||
* the Pokemon the move will target.
|
||||
* @returns this Pokemon's next move in the format {move, moveTargets}
|
||||
*/
|
||||
getNextMove(): QueuedMove {
|
||||
getNextMove(): TurnMove {
|
||||
// If this Pokemon has a move already queued, return it.
|
||||
const queuedMove = this.getMoveQueue().length
|
||||
? this.getMoveset().find(m => m?.moveId === this.getMoveQueue()[0].move)
|
||||
: null;
|
||||
if (queuedMove) {
|
||||
if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) {
|
||||
return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP };
|
||||
} else {
|
||||
this.getMoveQueue().shift();
|
||||
return this.getNextMove();
|
||||
const moveQueue = this.getMoveQueue();
|
||||
if (moveQueue.length !== 0) {
|
||||
const queuedMove = moveQueue[0];
|
||||
if (queuedMove) {
|
||||
const moveIndex = this.getMoveset().findIndex(m => m?.moveId === queuedMove.move);
|
||||
if ((moveIndex > -1 && this.getMoveset()[moveIndex]!.isUsable(this, queuedMove.ignorePP)) || queuedMove.virtual) {
|
||||
return queuedMove;
|
||||
} else {
|
||||
this.getMoveQueue().shift();
|
||||
return this.getNextMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5242,15 +5244,10 @@ export class EnemyPokemon extends Pokemon {
|
||||
|
||||
export interface TurnMove {
|
||||
move: Moves;
|
||||
targets?: BattlerIndex[];
|
||||
result: MoveResult;
|
||||
targets: BattlerIndex[];
|
||||
result?: MoveResult;
|
||||
virtual?: boolean;
|
||||
turn?: number;
|
||||
}
|
||||
|
||||
export interface QueuedMove {
|
||||
move: Moves;
|
||||
targets: BattlerIndex[];
|
||||
ignorePP?: boolean;
|
||||
}
|
||||
|
||||
@ -5266,7 +5263,7 @@ export interface AttackMoveResult {
|
||||
export class PokemonSummonData {
|
||||
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
||||
public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
|
||||
public moveQueue: QueuedMove[] = [];
|
||||
public moveQueue: TurnMove[] = [];
|
||||
public tags: BattlerTag[] = [];
|
||||
public abilitySuppressed: boolean = false;
|
||||
public abilitiesApplied: Abilities[] = [];
|
||||
|
@ -12,6 +12,7 @@ import { Biome } from "#enums/biome";
|
||||
import { Species } from "#enums/species";
|
||||
import { Challenges } from "./enums/challenges";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getDailyStartingBiome } from "./data/daily-run";
|
||||
|
||||
export enum GameModes {
|
||||
CLASSIC,
|
||||
@ -120,7 +121,7 @@ export class GameMode implements GameModeConfig {
|
||||
getStartingBiome(): Biome {
|
||||
switch (this.modeId) {
|
||||
case GameModes.DAILY:
|
||||
return globalScene.generateRandomBiome(this.getWaveForDifficulty(1));
|
||||
return getDailyStartingBiome();
|
||||
default:
|
||||
return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type { PlayerPokemon, TurnMove } from "#app/field/pokemon";
|
||||
import { FieldPosition } from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { Command } from "#app/ui/command-ui-handler";
|
||||
@ -86,19 +86,19 @@ export class CommandPhase extends FieldPhase {
|
||||
const moveQueue = playerPokemon.getMoveQueue();
|
||||
|
||||
while (moveQueue.length && moveQueue[0]
|
||||
&& moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move)
|
||||
&& moveQueue[0].move && !moveQueue[0].virtual && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move)
|
||||
|| !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct?
|
||||
moveQueue.shift();
|
||||
}
|
||||
|
||||
if (moveQueue.length) {
|
||||
if (moveQueue.length > 0) {
|
||||
const queuedMove = moveQueue[0];
|
||||
if (!queuedMove.move) {
|
||||
this.handleCommand(Command.FIGHT, -1, false);
|
||||
this.handleCommand(Command.FIGHT, -1);
|
||||
} else {
|
||||
const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move);
|
||||
if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct?
|
||||
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 });
|
||||
if ((moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) || queuedMove.virtual) { // TODO: is the bang correct?
|
||||
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, queuedMove);
|
||||
} else {
|
||||
globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
@ -120,12 +120,24 @@ export class CommandPhase extends FieldPhase {
|
||||
switch (command) {
|
||||
case Command.FIGHT:
|
||||
let useStruggle = false;
|
||||
const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined);
|
||||
if (cursor === -1 ||
|
||||
playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
|
||||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) {
|
||||
const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct?
|
||||
|
||||
let moveId: Moves;
|
||||
if (useStruggle) {
|
||||
moveId = Moves.STRUGGLE;
|
||||
} else if (turnMove !== undefined) {
|
||||
moveId = turnMove.move;
|
||||
} else if (cursor > -1) {
|
||||
moveId = playerPokemon.getMoveset()[cursor]!.moveId;
|
||||
} else {
|
||||
moveId = Moves.NONE;
|
||||
}
|
||||
|
||||
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args };
|
||||
const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2];
|
||||
const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 };
|
||||
if (!moveId) {
|
||||
turnCommand.targets = [ this.fieldIndex ];
|
||||
}
|
||||
|
@ -296,11 +296,6 @@ export class MovePhase extends BattlePhase {
|
||||
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
|
||||
}
|
||||
|
||||
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
|
||||
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
|
||||
globalScene.currentBattle.lastMove = this.move.moveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the move is successful (meaning that its damage/effects can be attempted)
|
||||
* by checking that all of the following are true:
|
||||
@ -324,6 +319,14 @@ export class MovePhase extends BattlePhase {
|
||||
|
||||
const success = passesConditions && !failedDueToWeather && !failedDueToTerrain;
|
||||
|
||||
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
|
||||
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
|
||||
// The last move used is unaffected by moves that fail
|
||||
if (success) {
|
||||
globalScene.currentBattle.lastMove = this.move.moveId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the move has not failed, trigger ability-based user type changes and then execute it.
|
||||
*
|
||||
@ -518,7 +521,7 @@ export class MovePhase extends BattlePhase {
|
||||
frenzyMissFunc(this.pokemon, this.move.getMove());
|
||||
}
|
||||
|
||||
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
|
||||
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL, targets: this.targets });
|
||||
|
||||
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||
this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
||||
|
105
src/test/moves/assist.test.ts
Normal file
105
src/test/moves/assist.test.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Assist", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
// Manual moveset overrides are required for the player pokemon in these tests
|
||||
// because the normal moveset override doesn't allow for accurate testing of moveset changes
|
||||
game.override
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("double")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyLevel(100)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should only use an ally's moves", async () => {
|
||||
game.override.enemyMoveset(Moves.SWORDS_DANCE);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]);
|
||||
|
||||
const [ feebas, shuckle ] = game.scene.getPlayerField();
|
||||
// These are all moves Assist cannot call; Sketch will be used to test that it can call other moves properly
|
||||
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
|
||||
game.move.select(Moves.ASSIST, 0);
|
||||
game.move.select(Moves.SKETCH, 1);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]);
|
||||
// Player_2 uses Sketch, copies Swords Dance, Player_1 uses Assist, uses Player_2's Sketched Swords Dance
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(2); // Stat raised from Assist -> Swords Dance
|
||||
});
|
||||
|
||||
it("should fail if there are no allies", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
const feebas = game.scene.getPlayerPokemon()!;
|
||||
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
|
||||
game.move.select(Moves.ASSIST, 0);
|
||||
await game.toNextTurn();
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should fail if ally has no usable moves and user has usable moves", async () => {
|
||||
game.override.enemyMoveset(Moves.SWORDS_DANCE);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]);
|
||||
|
||||
const [ feebas, shuckle ] = game.scene.getPlayerField();
|
||||
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
|
||||
game.move.select(Moves.SKETCH, 0);
|
||||
game.move.select(Moves.PROTECT, 1);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]);
|
||||
// Player uses Sketch to copy Swords Dance, Player_2 stalls a turn. Player will attempt Assist and should have no usable moves
|
||||
await game.toNextTurn();
|
||||
game.move.select(Moves.ASSIST, 0);
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
game.move.select(Moves.PROTECT, 1);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should apply secondary effects of a move", async () => {
|
||||
game.override.moveset([ Moves.ASSIST, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER ]);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]);
|
||||
|
||||
const [ feebas, shuckle ] = game.scene.getPlayerField();
|
||||
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
|
||||
|
||||
game.move.select(Moves.ASSIST, 0);
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
game.move.select(Moves.ASSIST, 1);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // should receive recoil damage from Wood Hammer
|
||||
});
|
||||
});
|
91
src/test/moves/copycat.test.ts
Normal file
91
src/test/moves/copycat.test.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves, RandomMoveAttr } from "#app/data/move";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Copycat", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0];
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.COPYCAT, Moves.SPIKY_SHIELD, Moves.SWORDS_DANCE, Moves.SPLASH ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.starterSpecies(Species.FEEBAS)
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should copy the last move successfully executed", async () => {
|
||||
game.override.enemyMoveset(Moves.SUCKER_PUNCH);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.SWORDS_DANCE);
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.COPYCAT); // Last successful move should be Swords Dance
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(4);
|
||||
});
|
||||
|
||||
it("should fail when the last move used is not a valid Copycat move", async () => {
|
||||
game.override.enemyMoveset(Moves.PROTECT); // Protect is not a valid move for Copycat to copy
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.SPIKY_SHIELD); // Spiky Shield is not a valid move for Copycat to copy
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.COPYCAT);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should copy the called move when the last move successfully calls another", async () => {
|
||||
game.override
|
||||
.moveset([ Moves.SPLASH, Moves.METRONOME ])
|
||||
.enemyMoveset(Moves.COPYCAT);
|
||||
await game.classicMode.startBattle();
|
||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.SWORDS_DANCE);
|
||||
|
||||
game.move.select(Moves.METRONOME);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); // Player moves first, so enemy can copy Swords Dance
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(2);
|
||||
});
|
||||
|
||||
it("should apply secondary effects of a move", async () => {
|
||||
game.override.enemyMoveset(Moves.ACID_SPRAY); // Secondary effect lowers SpDef by 2 stages
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.COPYCAT);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2);
|
||||
});
|
||||
});
|
113
src/test/moves/metronome.test.ts
Normal file
113
src/test/moves/metronome.test.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags";
|
||||
import { allMoves, RandomMoveAttr } from "#app/data/move";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||
|
||||
describe("Moves - Metronome", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0];
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.METRONOME, Moves.SPLASH ])
|
||||
.battleType("single")
|
||||
.startingLevel(100)
|
||||
.starterSpecies(Species.REGIELEKI)
|
||||
.enemyLevel(100)
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyAbility(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("should have one semi-invulnerable turn and deal damage on the second turn when a semi-invulnerable move is called", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.DIVE);
|
||||
|
||||
game.move.select(Moves.METRONOME);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player.getTag(SemiInvulnerableTag)).toBeTruthy();
|
||||
|
||||
await game.toNextTurn();
|
||||
expect(player.getTag(SemiInvulnerableTag)).toBeFalsy();
|
||||
expect(enemy.isFullHp()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should apply secondary effects of a move", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.WOOD_HAMMER);
|
||||
|
||||
game.move.select(Moves.METRONOME);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player.isFullHp()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should recharge after using recharge move", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.HYPER_BEAM);
|
||||
vi.spyOn(allMoves[Moves.HYPER_BEAM], "accuracy", "get").mockReturnValue(100);
|
||||
|
||||
game.move.select(Moves.METRONOME);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player.getTag(RechargingTag)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should only target ally for Aromatic Mist", async () => {
|
||||
game.override.battleType("double");
|
||||
await game.classicMode.startBattle([ Species.REGIELEKI, Species.RATTATA ]);
|
||||
const [ leftPlayer, rightPlayer ] = game.scene.getPlayerField();
|
||||
const [ leftOpp, rightOpp ] = game.scene.getEnemyField();
|
||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.AROMATIC_MIST);
|
||||
|
||||
game.move.select(Moves.METRONOME, 0);
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(rightPlayer.getStatStage(Stat.SPDEF)).toBe(1);
|
||||
expect(leftPlayer.getStatStage(Stat.SPDEF)).toBe(0);
|
||||
expect(leftOpp.getStatStage(Stat.SPDEF)).toBe(0);
|
||||
expect(rightOpp.getStatStage(Stat.SPDEF)).toBe(0);
|
||||
});
|
||||
|
||||
it("should cause opponent to flee, and not crash for Roar", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.ROAR);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.METRONOME);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
const isVisible = enemyPokemon.visible;
|
||||
const hasFled = enemyPokemon.switchOutStatus;
|
||||
expect(!isVisible && hasFled).toBe(true);
|
||||
|
||||
await game.phaseInterceptor.to("CommandPhase");
|
||||
});
|
||||
});
|
84
src/test/moves/mirror_move.test.ts
Normal file
84
src/test/moves/mirror_move.test.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Mirror Move", () => {
|
||||
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.MIRROR_MOVE, Moves.SPLASH ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should use the last move that the target used on the user", async () => {
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyMoveset([ Moves.TACKLE, Moves.GROWL ]);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.MIRROR_MOVE, 0, BattlerIndex.ENEMY); // target's last move is Tackle, enemy should receive damage from Mirror Move copying Tackle
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
|
||||
await game.forceEnemyMove(Moves.GROWL, BattlerIndex.PLAYER_2);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyField()[0].isFullHp()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should apply secondary effects of a move", async () => {
|
||||
game.override.enemyMoveset(Moves.ACID_SPRAY);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.MIRROR_MOVE);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2);
|
||||
});
|
||||
|
||||
it("should be able to copy status moves", async () => {
|
||||
game.override.enemyMoveset(Moves.GROWL);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.MIRROR_MOVE);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1);
|
||||
});
|
||||
|
||||
it("should fail if the target has not used any moves", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.MIRROR_MOVE);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
});
|
75
src/test/moves/sleep_talk.test.ts
Normal file
75
src/test/moves/sleep_talk.test.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { StatusEffect } from "#app/enums/status-effect";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Sleep Talk", () => {
|
||||
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.SPLASH, Moves.SLEEP_TALK ])
|
||||
.statusEffect(StatusEffect.SLEEP)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it("should fail when the user is not asleep", async () => {
|
||||
game.override.statusEffect(StatusEffect.NONE);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.SLEEP_TALK);
|
||||
await game.toNextTurn();
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should fail if the user has no valid moves", async () => {
|
||||
game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.METRONOME, Moves.SOLAR_BEAM ]);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.SLEEP_TALK);
|
||||
await game.toNextTurn();
|
||||
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();
|
||||
|
||||
game.move.select(Moves.SLEEP_TALK);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // Wood Hammer recoil effect should be applied
|
||||
});
|
||||
});
|
@ -125,7 +125,7 @@ describe("Moves - Spit Up", () => {
|
||||
game.move.select(Moves.SPIT_UP);
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.FAIL });
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.FAIL, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]});
|
||||
|
||||
expect(spitUp.calculateBattlePower).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -148,7 +148,7 @@ describe("Moves - Spit Up", () => {
|
||||
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]});
|
||||
|
||||
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
|
||||
|
||||
@ -176,7 +176,7 @@ describe("Moves - Spit Up", () => {
|
||||
game.move.select(Moves.SPIT_UP);
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]});
|
||||
|
||||
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
|
||||
|
||||
|
@ -72,7 +72,7 @@ describe("Moves - Stockpile", () => {
|
||||
expect(user.getStatStage(Stat.SPDEF)).toBe(3);
|
||||
expect(stockpilingTag).toBeDefined();
|
||||
expect(stockpilingTag.stockpiledCount).toBe(3);
|
||||
expect(user.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ result: MoveResult.FAIL, move: Moves.STOCKPILE });
|
||||
expect(user.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ result: MoveResult.FAIL, move: Moves.STOCKPILE, targets: [ user.getBattlerIndex() ]});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -135,7 +135,7 @@ describe("Moves - Swallow", () => {
|
||||
game.move.select(Moves.SWALLOW);
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.FAIL });
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.FAIL, targets: [ pokemon.getBattlerIndex() ]});
|
||||
});
|
||||
|
||||
describe("restores stat stage boosts granted by stacks", () => {
|
||||
@ -156,7 +156,7 @@ describe("Moves - Swallow", () => {
|
||||
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]});
|
||||
|
||||
expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
|
||||
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
|
||||
@ -183,7 +183,7 @@ describe("Moves - Swallow", () => {
|
||||
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
|
||||
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]});
|
||||
|
||||
expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
|
||||
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
|
||||
|
Loading…
Reference in New Issue
Block a user