mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 09:02:47 +02:00
Undid accidental class shuffling; fixed Sap Sipper tests
This commit is contained in:
parent
3413d075e9
commit
cc3c68d3b6
@ -6788,6 +6788,89 @@ export abstract class CallMoveAttr extends OverrideMoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to call a random move among moves not in a banlist.
|
||||||
|
* Used for {@linkcode MoveId.METRONOME}.
|
||||||
|
*/
|
||||||
|
export class RandomMoveAttr extends CallMoveAttr {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* A {@linkcode ReadonlySet} containing all moves that this MoveAttr cannot copy,
|
||||||
|
* in addition to unimplemented moves and `MoveId.NONE`.
|
||||||
|
* The move will fail if the chosen move is inside this banlist (if it exists).
|
||||||
|
*/
|
||||||
|
protected readonly invalidMoves: ReadonlySet<MoveId>,
|
||||||
|
) {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick a random move to execute, barring unimplemented moves and ones
|
||||||
|
* in this move's {@linkcode invalidMetronomeMoves | exclusion list}.
|
||||||
|
* Overridden as public to allow tests to override move choice using mocks.
|
||||||
|
*
|
||||||
|
* @param user - The {@linkcode Pokemon} using the move
|
||||||
|
* @returns The {@linkcode MoveId} that will be called.
|
||||||
|
*/
|
||||||
|
public override getMove(user: Pokemon): MoveId {
|
||||||
|
const moveIds = getEnumValues(MoveId).filter(m => m !== MoveId.NONE && !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)"));
|
||||||
|
return moveIds[user.randBattleSeedInt(moveIds.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute used to call a random move in the user or party's moveset.
|
||||||
|
* Used for {@linkcode MoveId.ASSIST} and {@linkcode MoveId.SLEEP_TALK}
|
||||||
|
*
|
||||||
|
* Fails if the user has no callable moves.
|
||||||
|
*/
|
||||||
|
export class RandomMovesetMoveAttr extends RandomMoveAttr {
|
||||||
|
/**
|
||||||
|
* The previously-selected MoveId for this attribute.
|
||||||
|
* Reset to `MoveId.NONE` after successful use.
|
||||||
|
*/
|
||||||
|
private selectedMove: MoveId = MoveId.NONE
|
||||||
|
constructor(invalidMoves: ReadonlySet<MoveId>,
|
||||||
|
/**
|
||||||
|
* Whether to consider all moves from the user's party (`true`) or the user's own moveset (`false`);
|
||||||
|
* default `false`.
|
||||||
|
*/
|
||||||
|
private includeParty = false
|
||||||
|
) {
|
||||||
|
super(invalidMoves);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a random move from either the user's or its party members' movesets,
|
||||||
|
* or return an already-selected one if one exists.
|
||||||
|
*
|
||||||
|
* @param user - The {@linkcode Pokemon} using the move.
|
||||||
|
* @returns The {@linkcode MoveId} that will be called.
|
||||||
|
*/
|
||||||
|
override getMove(user: Pokemon): MoveId {
|
||||||
|
if (this.selectedMove) {
|
||||||
|
const m = this.selectedMove;
|
||||||
|
this.selectedMove = MoveId.NONE;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// includeParty will be true for Assist, false for Sleep Talk
|
||||||
|
const allies: Pokemon[] = this.includeParty
|
||||||
|
? (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user)
|
||||||
|
: [ user ];
|
||||||
|
|
||||||
|
// Assist & Sleep Talk consider duplicate moves for their selection (hence why we use an array instead of a set)
|
||||||
|
const moveset = allies.flatMap(p => p.moveset);
|
||||||
|
const eligibleMoves = moveset.filter(m => m.moveId !== MoveId.NONE && !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)"));
|
||||||
|
this.selectedMove = eligibleMoves[user.randBattleSeedInt(eligibleMoves.length)]?.moveId ?? MoveId.NONE; // will fail if 0 length array
|
||||||
|
return this.selectedMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
override getCondition(): MoveConditionFunc {
|
||||||
|
return (user) => this.getMove(user) !== MoveId.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute to call a different move based on the current terrain and biome.
|
* Attribute to call a different move based on the current terrain and biome.
|
||||||
* Used by {@linkcode MoveId.NATURE_POWER}
|
* Used by {@linkcode MoveId.NATURE_POWER}
|
||||||
@ -6935,88 +7018,8 @@ export class CopyMoveAttr extends CallMoveAttr {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Attribute to call a random move among moves not in a banlist.
|
|
||||||
* Used for {@linkcode MoveId.METRONOME}.
|
|
||||||
*/
|
|
||||||
export class RandomMoveAttr extends CallMoveAttr {
|
|
||||||
constructor(
|
|
||||||
/**
|
|
||||||
* A {@linkcode ReadonlySet} containing all moves that this MoveAttr cannot copy,
|
|
||||||
* in addition to unimplemented moves and `MoveId.NONE`.
|
|
||||||
* The move will fail if the chosen move is inside this banlist (if it exists).
|
|
||||||
*/
|
|
||||||
protected readonly invalidMoves: ReadonlySet<MoveId>,
|
|
||||||
) {
|
|
||||||
super(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pick a random move to execute, barring unimplemented moves and ones
|
|
||||||
* in this move's {@linkcode invalidMetronomeMoves | exclusion list}.
|
|
||||||
* Overridden as public to allow tests to override move choice using mocks.
|
|
||||||
*
|
|
||||||
* @param user - The {@linkcode Pokemon} using the move
|
|
||||||
* @returns The {@linkcode MoveId} that will be called.
|
|
||||||
*/
|
|
||||||
public override getMove(user: Pokemon): MoveId {
|
|
||||||
const moveIds = getEnumValues(MoveId).filter(m => m !== MoveId.NONE && !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)"));
|
|
||||||
return moveIds[user.randBattleSeedInt(moveIds.length)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attribute used to call a random move in the user or party's moveset.
|
|
||||||
* Used for {@linkcode MoveId.ASSIST} and {@linkcode MoveId.SLEEP_TALK}
|
|
||||||
*
|
|
||||||
* Fails if the user has no callable moves.
|
|
||||||
*/
|
|
||||||
export class RandomMovesetMoveAttr extends RandomMoveAttr {
|
|
||||||
/**
|
|
||||||
* The previously-selected MoveId for this attribute.
|
|
||||||
* Reset to `MoveId.NONE` after successful use.
|
|
||||||
*/
|
|
||||||
private selectedMove: MoveId = MoveId.NONE
|
|
||||||
constructor(invalidMoves: ReadonlySet<MoveId>,
|
|
||||||
/**
|
|
||||||
* Whether to consider all moves from the user's party (`true`) or the user's own moveset (`false`);
|
|
||||||
* default `false`.
|
|
||||||
*/
|
|
||||||
private includeParty = false
|
|
||||||
) {
|
|
||||||
super(invalidMoves);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a random move from either the user's or its party members' movesets,
|
|
||||||
* or return an already-selected one if one exists.
|
|
||||||
*
|
|
||||||
* @param user - The {@linkcode Pokemon} using the move.
|
|
||||||
* @returns The {@linkcode MoveId} that will be called.
|
|
||||||
*/
|
|
||||||
override getMove(user: Pokemon): MoveId {
|
|
||||||
if (this.selectedMove) {
|
|
||||||
const m = this.selectedMove;
|
|
||||||
this.selectedMove = MoveId.NONE;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
// includeParty will be true for Assist, false for Sleep Talk
|
|
||||||
const allies: Pokemon[] = this.includeParty
|
|
||||||
? (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user)
|
|
||||||
: [ user ];
|
|
||||||
|
|
||||||
// Assist & Sleep Talk consider duplicate moves for their selection (hence why we use an array instead of a set)
|
|
||||||
const moveset = allies.flatMap(p => p.moveset);
|
|
||||||
const eligibleMoves = moveset.filter(m => m.moveId !== MoveId.NONE && !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)"));
|
|
||||||
this.selectedMove = eligibleMoves[user.randBattleSeedInt(eligibleMoves.length)]?.moveId ?? MoveId.NONE; // will fail if 0 length array
|
|
||||||
return this.selectedMove;
|
|
||||||
}
|
|
||||||
|
|
||||||
override getCondition(): MoveConditionFunc {
|
|
||||||
return (user) => this.getMove(user) !== MoveId.NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute used for moves that causes the target to repeat their last used move.
|
* Attribute used for moves that causes the target to repeat their last used move.
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { TerrainType } from "#app/data/terrain";
|
import { TerrainType } from "#app/data/terrain";
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
@ -10,7 +8,6 @@ import GameManager from "#test/testUtils/gameManager";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { RandomMoveAttr } from "#app/data/moves/move";
|
import { RandomMoveAttr } from "#app/data/moves/move";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
|
||||||
|
|
||||||
// See also: TypeImmunityAbAttr
|
// See also: TypeImmunityAbAttr
|
||||||
describe("Abilities - Sap Sipper", () => {
|
describe("Abilities - Sap Sipper", () => {
|
||||||
@ -38,131 +35,98 @@ describe("Abilities - Sap Sipper", () => {
|
|||||||
.enemyMoveset(MoveId.SPLASH);
|
.enemyMoveset(MoveId.SPLASH);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("raises ATK stat stage by 1 and block effects when activated against a grass attack", async () => {
|
it("should nullify all effects of Grass-type attacks and raise ATK by 1 stage", async () => {
|
||||||
const moveToUse = MoveId.LEAFAGE;
|
|
||||||
|
|
||||||
game.override.moveset(moveToUse);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
game.move.use(MoveId.LEAFAGE);
|
||||||
const initialEnemyHp = enemyPokemon.hp;
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
const enemyPokemon = game.field.getEnemyPokemon();
|
||||||
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
|
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("raises ATK stat stage by 1 and block effects when activated against a grass status move", async () => {
|
it("should work on grass status moves", async () => {
|
||||||
const moveToUse = MoveId.SPORE;
|
|
||||||
|
|
||||||
game.override.moveset(moveToUse);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.use(MoveId.SPORE);
|
||||||
|
await game.toNextTurn();
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
expect(enemyPokemon.status).toBeUndefined();
|
expect(enemyPokemon.status).toBeUndefined();
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("do not activate against status moves that target the field", async () => {
|
it("should not activate on non Grass-type moves", async () => {
|
||||||
const moveToUse = MoveId.GRASSY_TERRAIN;
|
|
||||||
|
|
||||||
game.override.moveset(moveToUse);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.use(MoveId.TACKLE);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
expect(game.scene.arena.terrain).toBeDefined();
|
expect(enemy.getStatStage(Stat.ATK)).toBe(0);
|
||||||
expect(game.scene.arena.terrain!.terrainType).toBe(TerrainType.GRASSY);
|
|
||||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("activate once against multi-hit grass attacks", async () => {
|
|
||||||
const moveToUse = MoveId.BULLET_SEED;
|
|
||||||
|
|
||||||
game.override.moveset(moveToUse);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
const initialEnemyHp = enemyPokemon.hp;
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
|
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("do not activate against status moves that target the user", async () => {
|
|
||||||
const moveToUse = MoveId.SPIKY_SHIELD;
|
|
||||||
|
|
||||||
game.override.moveset(moveToUse);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
|
||||||
|
|
||||||
expect(playerPokemon.getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
|
|
||||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("activate once against multi-hit grass attacks (metronome)", async () => {
|
it("should not activate against field-targeted moves", async () => {
|
||||||
const moveToUse = MoveId.METRONOME;
|
|
||||||
|
|
||||||
const randomMoveAttr = allMoves[MoveId.METRONOME].findAttr(
|
|
||||||
attr => attr instanceof RandomMoveAttr,
|
|
||||||
) as RandomMoveAttr;
|
|
||||||
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.BULLET_SEED);
|
|
||||||
|
|
||||||
game.override.moveset(moveToUse);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
game.move.use(MoveId.GRASSY_TERRAIN);
|
||||||
const initialEnemyHp = enemyPokemon.hp;
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
expect(game.scene.arena.terrain).toBeDefined();
|
||||||
|
expect(game.scene.arena.terrain!.terrainType).toBe(TerrainType.GRASSY);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(0);
|
||||||
|
|
||||||
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
|
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("still activates regardless of accuracy check", async () => {
|
it("should trigger and cancel multi-hit moves, including ones called indirectly", async () => {
|
||||||
game.override.moveset(MoveId.LEAF_BLADE);
|
vi.spyOn(RandomMoveAttr.prototype, "getMove").mockReturnValue(MoveId.BULLET_SEED);
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const player = game.field.getPlayerPokemon();
|
||||||
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
|
||||||
game.move.select(MoveId.LEAF_BLADE);
|
game.move.use(MoveId.BULLET_SEED);
|
||||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
expect(enemy.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
expect(player.turnData.hitCount).toBe(1);
|
||||||
|
|
||||||
|
game.move.use(MoveId.METRONOME);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
expect(enemy.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
expect(player.turnData.hitCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not activate on self-targeted status moves", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
|
const player = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
|
game.move.use(MoveId.SPIKY_SHIELD);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
expect(player.getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
|
||||||
|
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(player.getStatStage(Stat.ATK)).toBe(0);
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate even on missed moves", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
|
||||||
|
|
||||||
|
game.move.use(MoveId.LEAF_BLADE);
|
||||||
await game.move.forceMiss();
|
await game.move.forceMiss();
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
const enemyPokemon = game.field.getEnemyPokemon();
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user