Adjust status checks to respect ignoreStatus useModes

This commit is contained in:
Sirz Benjie 2025-08-18 20:23:27 -05:00
parent c0827230cb
commit ce752c9d07
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
4 changed files with 38 additions and 23 deletions

View File

@ -11,7 +11,17 @@ export enum BattlerTagLapseType {
* @see MoveUseMode for more information
*/
MOVE,
/** Tag activates during (or just after) the first failure check sequence in the move phase. */
/**
* Tag activates during (or just after) the first failure check sequence in the move phase.
*
* @remarks
*
* Note tags with this lapse type will lapse immediately after the first failure check sequence,
* regardless of whether the move was successful or not.
*
* To only lapse the tag between the first and second failure check sequences, use
* {@linkcode BattlerTagLapseType.MOVE} instead.
*/
PRE_MOVE,
/** Tag activates immediately after the holder's move finishes triggering (successful or not). */
AFTER_MOVE,

View File

@ -301,8 +301,8 @@ export class MovePhase extends BattlePhase {
// if it attempted to move at all.
user.turnData.acted = true;
const useMode = this.useMode;
const virtual = isVirtual(useMode);
if (!virtual && this.firstFailureCheck()) {
const ignoreStatus = isIgnoreStatus(useMode);
if (!ignoreStatus && this.firstFailureCheck()) {
// Lapse all other pre-move tags
user.lapseTags(BattlerTagLapseType.PRE_MOVE);
this.end();
@ -314,11 +314,13 @@ export class MovePhase extends BattlePhase {
- Protect, detect, ally switch, etc, resetting consecutive use count
- Rollout / ice ball "unlocking"
- protect / ally switch / other moves resetting their consecutive use count
- and others
In Pokerogue, these are instead handled by their respective methods, which generally
- and many others
In Pokerogue, these are instead handled elsewhere, and generally work in a way that aligns with cartridge behavior
*/
return;
}
// Tags still need to be lapsed if no failure occured
user.lapseTags(BattlerTagLapseType.PRE_MOVE);
// At this point, called moves should be decided.
// For now, this comment works as a placeholder until we rework how called moves are handled
@ -329,7 +331,7 @@ export class MovePhase extends BattlePhase {
this.post1stFailSleepOrThaw();
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats)
if (virtual) {
if (isVirtual(useMode)) {
this.pokemon.turnData.hitsLeft = -1;
this.pokemon.turnData.hitCount = 0;
}
@ -369,11 +371,7 @@ export class MovePhase extends BattlePhase {
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
}
// At this point, if the target index has not moved on from attacker, the move must fail
if (this.targets[0] === BattlerIndex.ATTACKER) {
this.fail();
}
if (this.targets[0] === BattlerIndex.ATTACKER || this.secondFailureCheck()) {
if (this.secondFailureCheck()) {
this.handlePreMoveFailures();
this.end();
return;
@ -393,7 +391,7 @@ export class MovePhase extends BattlePhase {
}
/**
* Check for cancellation edge cases - no targets remaining or the battler index being targeted is still the attacker
* Check for cancellation edge cases - no targets remaining
* @returns Whether the move fails
*/
protected resolveFinalPreMoveCancellationChecks(): boolean {
@ -402,8 +400,7 @@ export class MovePhase extends BattlePhase {
if (
(targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) ||
(moveQueue.length > 0 && moveQueue[0].move === MoveId.NONE) ||
this.targets[0] === BattlerIndex.ATTACKER
(moveQueue.length > 0 && moveQueue[0].move === MoveId.NONE)
) {
this.showFailedText();
this.fail();
@ -455,6 +452,7 @@ export class MovePhase extends BattlePhase {
return false;
}
// For some reason, dancer will immediately wake its user from sleep when triggering
if (this.useMode === MoveUseMode.INDIRECT) {
this.pokemon.resetStatus(false);
return false;
@ -493,6 +491,12 @@ export class MovePhase extends BattlePhase {
return false;
}
// For some reason, dancer will immediately its user
if (this.useMode === MoveUseMode.INDIRECT) {
this.pokemon.resetStatus(false);
return false;
}
// Check if move use would heal the user
if (Overrides.STATUS_ACTIVATION_OVERRIDE) {
@ -906,6 +910,9 @@ export class MovePhase extends BattlePhase {
applyMoveAttrs("CounterRedirectAttr", this.pokemon, null, this.move.getMove(), targetHolder);
this.targets[0] = targetHolder.value;
if (targetHolder.value === BattlerIndex.ATTACKER) {
this.fail();
}
}
/**

View File

@ -1,4 +1,3 @@
import { CudChewConsumeBerryAbAttr } from "#abilities/ability";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { AbilityId } from "#enums/ability-id";
@ -6,7 +5,6 @@ import { BerryType } from "#enums/berry-type";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { Pokemon } from "#field/pokemon";
import { GameManager } from "#test/test-utils/game-manager";
import i18next from "i18next";
import Phaser from "phaser";
@ -111,7 +109,6 @@ describe("Abilities - Cud Chew", () => {
it("can store multiple berries across 2 turns with teatime", async () => {
// always eat first berry for stuff cheeks & company
vi.spyOn(Pokemon.prototype, "randBattleSeedInt").mockReturnValue(0);
game.override
.startingHeldItems([
{ name: "BERRY", type: BerryType.PETAYA, count: 3 },
@ -122,6 +119,7 @@ describe("Abilities - Cud Chew", () => {
const farigiraf = game.field.getPlayerPokemon();
farigiraf.hp = 1; // needed to allow berry procs
vi.spyOn(farigiraf, "randBattleSeedInt").mockReturnValue(0);
game.move.select(MoveId.STUFF_CHEEKS);
await game.toNextTurn();
@ -196,10 +194,10 @@ describe("Abilities - Cud Chew", () => {
describe("regurgiates berries", () => {
it("re-triggers effects on eater without pushing to array", async () => {
const apply = vi.spyOn(CudChewConsumeBerryAbAttr.prototype, "apply");
await game.classicMode.startBattle([SpeciesId.FARIGIRAF]);
const farigiraf = game.field.getPlayerPokemon();
const apply = vi.spyOn(farigiraf.getAbilityAttrs("CudChewConsumeBerryAbAttr")[0], "apply");
farigiraf.hp = 1;
game.move.select(MoveId.SPLASH);

View File

@ -33,7 +33,7 @@ describe("Moves - Gigaton Hammer", () => {
});
it("can't be used two turns in a row", async () => {
await game.classicMode.startBattle();
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const enemy1 = game.field.getEnemyPokemon();
@ -46,17 +46,17 @@ describe("Moves - Gigaton Hammer", () => {
await game.doKillOpponents();
await game.toNextWave();
// Attempting to use Gigaton Hammer again should result in struggle
game.move.select(MoveId.GIGATON_HAMMER);
await game.toNextTurn();
const enemy2 = game.field.getEnemyPokemon();
expect(enemy2.hp).toBe(enemy2.getMaxHp());
const player = game.field.getPlayerPokemon();
expect(player.getLastXMoves()[0]?.move).toBe(MoveId.STRUGGLE);
});
it("can be used again if recalled and sent back out", async () => {
game.override.startingWave(4);
await game.classicMode.startBattle();
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const enemy1 = game.field.getEnemyPokemon();