Cleaned up Stockpile tests

This commit is contained in:
Bertie690 2025-08-05 17:17:05 -04:00
parent 89536fafda
commit 751d824af8
4 changed files with 142 additions and 144 deletions

View File

@ -2698,29 +2698,30 @@ export class StockpilingTag extends SerializableBattlerTag {
* For each stat, an internal counter is incremented (by 1) if the stat was successfully changed.
*/
onAdd(pokemon: Pokemon): void {
if (this.stockpiledCount < 3) {
this.stockpiledCount++;
globalScene.phaseManager.queueMessage(
i18next.t("battlerTags:stockpilingOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
stockpiledCount: this.stockpiledCount,
}),
);
// Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes.
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.DEF],
1,
true,
false,
true,
this.onStatStagesChanged,
);
if (this.stockpiledCount >= 3) {
return;
}
this.stockpiledCount++;
globalScene.phaseManager.queueMessage(
i18next.t("battlerTags:stockpilingOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
stockpiledCount: this.stockpiledCount,
}),
);
// Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes.
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.DEF],
1,
true,
false,
true,
this.onStatStagesChanged,
);
}
onOverlap(pokemon: Pokemon): void {

View File

@ -2038,8 +2038,9 @@ export class VariableHealAttr extends HealAttr {
private healFunc: (user: Pokemon, target: Pokemon, move: Move) => number,
showAnim = false,
selfTarget = true,
failOnFullHp = true,
) {
super(1, showAnim, selfTarget);
super(1, showAnim, selfTarget, selfTarget);
this.healFunc = healFunc;
}
@ -9293,14 +9294,14 @@ export function initMoves() {
.partial(), // Does not lock the user, does not stop Pokemon from sleeping
// Likely can make use of FrenzyAttr and an ArenaTag (just without the FrenzyMissFunc)
new SelfStatusMove(MoveId.STOCKPILE, PokemonType.NORMAL, -1, 20, -1, 0, 3)
.condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3)
.condition(user => (user.getTag(BattlerTagType.STOCKPILING)?.stockpiledCount ?? 0) < 3)
.attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true),
new AttackMove(MoveId.SPIT_UP, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 3)
.attr(SpitUpPowerAttr, 100)
.condition(hasStockpileStacksCondition)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.STOCKPILING ], true),
new SelfStatusMove(MoveId.SWALLOW, PokemonType.NORMAL, -1, 10, -1, 0, 3)
.attr(VariableHealAttr, swallowHealFunc)
.attr(VariableHealAttr, swallowHealFunc, false, true, true)
.condition(hasStockpileStacksCondition)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.STOCKPILING ], true)
.triageMove(),

View File

@ -1,109 +1,110 @@
import { StockpilingTag } from "#data/battler-tags";
import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id";
import { MoveResult } from "#enums/move-result";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { TurnInitPhase } from "#phases/turn-init-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Stockpile", () => {
describe("integration tests", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleStyle("single")
.enemySpecies(SpeciesId.RATTATA)
.enemyMoveset(MoveId.SPLASH)
.enemyAbility(AbilityId.NONE)
.startingLevel(2000)
.moveset([MoveId.STOCKPILE, MoveId.SPLASH])
.ability(AbilityId.NONE);
});
game.override
.battleStyle("single")
.enemySpecies(SpeciesId.RATTATA)
.enemyMoveset(MoveId.SPLASH)
.enemyAbility(AbilityId.BALL_FETCH)
.startingLevel(2000)
.ability(AbilityId.BALL_FETCH);
});
it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
it("should gain a stockpile stack and raise DEF and SPDEF when used, up to 3 times", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
const user = game.field.getPlayerPokemon();
const user = game.field.getPlayerPokemon();
// Unfortunately, Stockpile stacks are not directly queryable (i.e. there is no pokemon.getStockpileStacks()),
// we just have to know that they're implemented as a BattlerTag.
expect(user).toHaveStatStage(Stat.DEF, 0);
expect(user).toHaveStatStage(Stat.SPDEF, 0);
expect(user.getTag(StockpilingTag)).toBeUndefined();
expect(user.getStatStage(Stat.DEF)).toBe(0);
expect(user.getStatStage(Stat.SPDEF)).toBe(0);
// use Stockpile thrice
for (let i = 0; i < 3; i++) {
game.move.use(MoveId.STOCKPILE);
await game.toNextTurn();
// use Stockpile four times
for (let i = 0; i < 4; i++) {
game.move.select(MoveId.STOCKPILE);
await game.toNextTurn();
const stockpilingTag = user.getTag(StockpilingTag)!;
if (i < 3) {
// first three uses should behave normally
expect(user.getStatStage(Stat.DEF)).toBe(i + 1);
expect(user.getStatStage(Stat.SPDEF)).toBe(i + 1);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(i + 1);
} else {
// fourth should have failed
expect(user.getStatStage(Stat.DEF)).toBe(3);
expect(user.getStatStage(Stat.SPDEF)).toBe(3);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(3);
expect(user.getMoveHistory().at(-1)).toMatchObject({
result: MoveResult.FAIL,
move: MoveId.STOCKPILE,
targets: [user.getBattlerIndex()],
});
}
}
});
it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
const user = game.field.getPlayerPokemon();
user.setStatStage(Stat.DEF, 6);
user.setStatStage(Stat.SPDEF, 6);
expect(user.getTag(StockpilingTag)).toBeUndefined();
expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.getStatStage(Stat.SPDEF)).toBe(6);
game.move.select(MoveId.STOCKPILE);
await game.phaseInterceptor.to(TurnInitPhase);
const stockpilingTag = user.getTag(StockpilingTag)!;
const stockpilingTag = user.getTag(BattlerTagType.STOCKPILING)!;
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(1);
expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.getStatStage(Stat.SPDEF)).toBe(6);
expect(stockpilingTag.stockpiledCount).toBe(i + 1);
expect(user).toHaveStatStage(Stat.DEF, i + 1);
expect(user).toHaveStatStage(Stat.SPDEF, i + 1);
}
});
game.move.select(MoveId.STOCKPILE);
await game.phaseInterceptor.to(TurnInitPhase);
it("should fail when used at max stacks", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
const stockpilingTagAgain = user.getTag(StockpilingTag)!;
expect(stockpilingTagAgain).toBeDefined();
expect(stockpilingTagAgain.stockpiledCount).toBe(2);
expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.getStatStage(Stat.SPDEF)).toBe(6);
const user = game.field.getPlayerPokemon();
user.addTag(BattlerTagType.STOCKPILING);
user.addTag(BattlerTagType.STOCKPILING);
user.addTag(BattlerTagType.STOCKPILING);
const stockpilingTag = user.getTag(BattlerTagType.STOCKPILING)!;
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(3);
game.move.use(MoveId.STOCKPILE);
await game.toNextTurn();
// should have failed
expect(user).toHaveStatStage(Stat.DEF, 3);
expect(user).toHaveStatStage(Stat.SPDEF, 3);
expect(stockpilingTag.stockpiledCount).toBe(3);
expect(user).toHaveUsedMove({
move: MoveId.STOCKPILE,
result: MoveResult.FAIL,
});
});
it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
const user = game.field.getPlayerPokemon();
user.setStatStage(Stat.DEF, 6);
user.setStatStage(Stat.SPDEF, 6);
expect(user).not.toHaveBattlerTag(BattlerTagType.STOCKPILING);
game.move.use(MoveId.STOCKPILE);
await game.toNextTurn();
const stockpilingTag = user.getTag(BattlerTagType.STOCKPILING)!;
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(1);
expect(user).toHaveStatStage(Stat.DEF, 6);
expect(user).toHaveStatStage(Stat.SPDEF, 6);
game.move.use(MoveId.STOCKPILE);
await game.toNextTurn();
const stockpilingTagAgain = user.getTag(BattlerTagType.STOCKPILING)!;
expect(stockpilingTagAgain).toBeDefined();
expect(stockpilingTagAgain.stockpiledCount).toBe(2);
expect(user).toHaveStatStage(Stat.DEF, 6);
expect(user).toHaveStatStage(Stat.SPDEF, 6);
});
});

View File

@ -63,8 +63,8 @@ describe("Moves - Swallow & Spit Up - ", () => {
game.move.use(MoveId.SWALLOW);
await game.toEndOfTurn();
expect(swalot.getHpRatio()).toBeCloseTo(healPercent / 100, 1);
expect(swalot.getTag(StockpilingTag)).toBeUndefined();
expect(swalot).toHaveHp((swalot.getMaxHp() * healPercent) / 100 + 1);
expect(swalot).not.toHaveBattlerTag(BattlerTagType.STOCKPILING);
},
);
@ -74,40 +74,38 @@ describe("Moves - Swallow & Spit Up - ", () => {
const player = game.field.getPlayerPokemon();
player.hp = 1;
const stockpilingTag = player.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeUndefined();
expect(player).not.toHaveBattlerTag(BattlerTagType.STOCKPILING);
game.move.use(MoveId.SWALLOW);
await game.toEndOfTurn();
expect(player.getLastXMoves()[0]).toMatchObject({
expect(player).toHaveUsedMove({
move: MoveId.SWALLOW,
result: MoveResult.FAIL,
});
});
// TODO: Does this consume stacks or not?
it.todo("should fail and display message at full HP, consuming stacks", async () => {
it("should count as a success and consume stacks despite displaying message at full HP", async () => {
await game.classicMode.startBattle([SpeciesId.SWALOT]);
const swalot = game.field.getPlayerPokemon();
swalot.addTag(BattlerTagType.STOCKPILING);
const stockpilingTag = swalot.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined();
expect(swalot).toHaveBattlerTag(BattlerTagType.STOCKPILING);
game.move.use(MoveId.SWALLOW);
await game.toEndOfTurn();
expect(swalot.getLastXMoves()[0]).toMatchObject({
// Swallow counted as a "success" as its other effect (removing Stockpile) _did_ work
expect(swalot).toHaveUsedMove({
move: MoveId.SWALLOW,
result: MoveResult.FAIL,
result: MoveResult.SUCCESS,
});
expect(game.textInterceptor.logs).toContain(
i18next.t("battle:hpIsFull", {
pokemonName: getPokemonNameWithAffix(swalot),
}),
);
expect(stockpilingTag).toBeDefined();
expect(swalot).not.toHaveBattlerTag(BattlerTagType.STOCKPILING);
});
});
@ -147,13 +145,12 @@ describe("Moves - Swallow & Spit Up - ", () => {
const player = game.field.getPlayerPokemon();
const stockpilingTag = player.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeUndefined();
expect(player).not.toHaveBattlerTag(BattlerTagType.STOCKPILING);
game.move.use(MoveId.SPIT_UP);
await game.toEndOfTurn();
expect(player.getLastXMoves()[0]).toMatchObject({
expect(player).toHaveUsedMove({
move: MoveId.SPIT_UP,
result: MoveResult.FAIL,
});
@ -162,28 +159,27 @@ describe("Moves - Swallow & Spit Up - ", () => {
describe("Stockpile stack removal", () => {
it("should undo stat boosts when losing stacks", async () => {
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
await game.classicMode.startBattle([SpeciesId.SWALOT]);
const player = game.field.getPlayerPokemon();
player.hp = 1;
game.move.use(MoveId.STOCKPILE);
await game.toNextTurn();
const stockpilingTag = player.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined();
expect(player.getStatStage(Stat.DEF)).toBe(1);
expect(player.getStatStage(Stat.SPDEF)).toBe(1);
expect(player).toHaveBattlerTag(BattlerTagType.STOCKPILING);
expect(player).toHaveStatStage(Stat.DEF, 1);
expect(player).toHaveStatStage(Stat.SPDEF, 1);
// remove the prior stat boosts from the log
// remove the prior stat boost phases from the log
game.phaseInterceptor.clearLogs();
game.move.use(MoveId.SWALLOW);
await game.move.forceEnemyMove(MoveId.ACID_SPRAY);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toEndOfTurn();
expect(player.getStatStage(Stat.DEF)).toBe(0);
expect(player.getStatStage(Stat.SPDEF)).toBe(-2); // +1 --> -1 --> -2
expect(player).toHaveStatStage(Stat.DEF, 0);
expect(player).toHaveStatStage(Stat.SPDEF, -2); // +1 --> -1 --> -2
expect(game.phaseInterceptor.log.filter(l => l === "StatStageChangePhase")).toHaveLength(3);
});
@ -197,21 +193,20 @@ describe("Moves - Swallow & Spit Up - ", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextTurn();
expect(player.getStatStage(Stat.DEF)).toBe(1);
expect(player.getStatStage(Stat.SPDEF)).toBe(1);
expect(player).toHaveStatStage(Stat.DEF, 1);
expect(player).toHaveStatStage(Stat.SPDEF, 1);
expect(player.hasAbility(AbilityId.SIMPLE)).toBe(true);
game.move.use(MoveId.SPIT_UP);
game.move.use(MoveId.SWALLOW);
await game.move.forceEnemyMove(MoveId.SPLASH);
await game.toEndOfTurn();
// should have fallen by 2 stages from Simple
expect(player.getStatStage(Stat.DEF)).toBe(-1);
expect(player.getStatStage(Stat.SPDEF)).toBe(-1);
expect(player).toHaveStatStage(Stat.DEF, -1);
expect(player).toHaveStatStage(Stat.SPDEF, -1);
});
it("should invert stat drops when gaining Contrary", async () => {
game.override.enemyAbility(AbilityId.CONTRARY);
await game.classicMode.startBattle([SpeciesId.ABOMASNOW]);
const player = game.field.getPlayerPokemon();
@ -221,17 +216,17 @@ describe("Moves - Swallow & Spit Up - ", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toEndOfTurn();
expect(player.getStatStage(Stat.DEF)).toBe(1);
expect(player.getStatStage(Stat.SPDEF)).toBe(1);
expect(player.hasAbility(AbilityId.CONTRARY)).toBe(true);
expect(player).toHaveStatStage(Stat.DEF, 1);
expect(player).toHaveStatStage(Stat.SPDEF, 1);
expect(player).toHaveAbilityApplied(AbilityId.CONTRARY);
game.move.use(MoveId.SPIT_UP);
await game.move.forceEnemyMove(MoveId.SPLASH);
await game.toEndOfTurn();
// should have risen 1 stage from Contrary
expect(player.getStatStage(Stat.DEF)).toBe(2);
expect(player.getStatStage(Stat.SPDEF)).toBe(2);
expect(player).toHaveStatStage(Stat.DEF, 2);
expect(player).toHaveStatStage(Stat.SPDEF, 2);
});
});
});