mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 06:53:27 +02:00
[Bug] Future Sight no longer crashes after catching the user (#6479)
This commit is contained in:
parent
ddde977a0a
commit
309e31e196
@ -863,6 +863,8 @@ export class BattleScene extends SceneBase {
|
||||
* @param pokemonId - The ID whose Pokemon will be retrieved.
|
||||
* @returns The {@linkcode Pokemon} associated with the given id.
|
||||
* Returns `null` if the ID is `undefined` or not present in either party.
|
||||
* @todo Change the `null` to `undefined` and update callers' signatures -
|
||||
* this is weird and causes a lot of random jank
|
||||
*/
|
||||
getPokemonById(pokemonId: number | undefined): Pokemon | null {
|
||||
if (isNullOrUndefined(pokemonId)) {
|
||||
|
@ -835,7 +835,7 @@ export abstract class BattleAnim {
|
||||
// biome-ignore lint/complexity/noBannedTypes: callback is used liberally
|
||||
play(onSubstitute?: boolean, callback?: Function) {
|
||||
const isOppAnim = this.isOppAnim();
|
||||
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
|
||||
const user = !isOppAnim ? this.user! : this.target!; // TODO: These bangs are LITERALLY not correct at all
|
||||
const target = !isOppAnim ? this.target! : this.user!;
|
||||
|
||||
if (!target?.isOnField() && !this.playRegardlessOfIssues) {
|
||||
|
@ -126,7 +126,9 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs
|
||||
// Silently disappear if either source or target are missing or happen to be the same pokemon
|
||||
// (i.e. targeting oneself)
|
||||
// We also need to check for fainted targets as they don't technically leave the field until _after_ the turn ends
|
||||
return !!source && !!target && source !== target && !target.isFainted();
|
||||
// TODO: Figure out a way to store the target's offensive stat if they faint to allow pending attacks to persist
|
||||
// TODO: Remove the `?.scene` checks once battle anims are cleaned up - needed to avoid catch+release crash
|
||||
return !!source?.scene && !!target?.scene && source !== target && !target.isFainted();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,15 @@ import { allMoves } from "#data/data-lists";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { PositionalTagType } from "#enums/positional-tag-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { GameManager } from "#test/test-utils/game-manager";
|
||||
import i18next from "i18next";
|
||||
import Phaser from "phaser";
|
||||
@ -95,7 +98,7 @@ describe("Moves - Delayed Attacks", () => {
|
||||
|
||||
expectFutureSightActive(0);
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||
expect(enemy).not.toHaveFullHp();
|
||||
expect(game.textInterceptor.logs).toContain(
|
||||
i18next.t("moveTriggers:tookMoveAttack", {
|
||||
pokemonName: getPokemonNameWithAffix(enemy),
|
||||
@ -130,12 +133,12 @@ describe("Moves - Delayed Attacks", () => {
|
||||
|
||||
expectFutureSightActive();
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||
expect(enemy).toHaveFullHp();
|
||||
|
||||
await passTurns(2);
|
||||
|
||||
expectFutureSightActive(0);
|
||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||
expect(enemy).not.toHaveFullHp();
|
||||
});
|
||||
|
||||
it("should work when used against different targets in doubles", async () => {
|
||||
@ -149,15 +152,15 @@ describe("Moves - Delayed Attacks", () => {
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expectFutureSightActive(2);
|
||||
expect(enemy1.hp).toBe(enemy1.getMaxHp());
|
||||
expect(enemy2.hp).toBe(enemy2.getMaxHp());
|
||||
expect(enemy1).toHaveFullHp();
|
||||
expect(enemy2).toHaveFullHp();
|
||||
expect(karp.getLastXMoves()[0].result).toBe(MoveResult.OTHER);
|
||||
expect(feebas.getLastXMoves()[0].result).toBe(MoveResult.OTHER);
|
||||
|
||||
await passTurns(2);
|
||||
|
||||
expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp());
|
||||
expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp());
|
||||
expect(enemy1).not.toHaveFullHp();
|
||||
expect(enemy2).not.toHaveFullHp();
|
||||
});
|
||||
|
||||
it("should trigger multiple pending attacks in order of creation, even if that order changes later on", async () => {
|
||||
@ -222,8 +225,8 @@ describe("Moves - Delayed Attacks", () => {
|
||||
|
||||
expect(game.scene.getPlayerParty()).toEqual([milotic, karp, feebas]);
|
||||
|
||||
expect(karp.hp).toBe(karp.getMaxHp());
|
||||
expect(feebas.hp).toBe(feebas.getMaxHp());
|
||||
expect(karp).toHaveFullHp();
|
||||
expect(feebas).toHaveFullHp();
|
||||
expect(game.textInterceptor.logs).not.toContain(
|
||||
i18next.t("moveTriggers:tookMoveAttack", {
|
||||
pokemonName: getPokemonNameWithAffix(karp),
|
||||
@ -245,15 +248,14 @@ describe("Moves - Delayed Attacks", () => {
|
||||
expect(enemy2.isFainted()).toBe(true);
|
||||
expectFutureSightActive();
|
||||
|
||||
const attack = game.scene.arena.positionalTagManager.tags.find(
|
||||
t => t.tagType === PositionalTagType.DELAYED_ATTACK,
|
||||
)!;
|
||||
expect(attack).toBeDefined();
|
||||
expect(attack.targetIndex).toBe(enemy1.getBattlerIndex());
|
||||
expect(game).toHavePositionalTag({
|
||||
tagType: PositionalTagType.DELAYED_ATTACK,
|
||||
targetIndex: enemy1.getBattlerIndex(),
|
||||
});
|
||||
|
||||
await passTurns(2);
|
||||
|
||||
expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp());
|
||||
expect(enemy1).not.toHaveFullHp();
|
||||
expect(game.textInterceptor.logs).toContain(
|
||||
i18next.t("moveTriggers:tookMoveAttack", {
|
||||
pokemonName: getPokemonNameWithAffix(enemy1),
|
||||
@ -281,7 +283,7 @@ describe("Moves - Delayed Attacks", () => {
|
||||
await game.toNextTurn();
|
||||
|
||||
expectFutureSightActive(0);
|
||||
expect(enemy1.hp).toBe(enemy1.getMaxHp());
|
||||
expect(enemy1).toHaveFullHp();
|
||||
expect(game.textInterceptor.logs).not.toContain(
|
||||
i18next.t("moveTriggers:tookMoveAttack", {
|
||||
pokemonName: getPokemonNameWithAffix(enemy1),
|
||||
@ -317,8 +319,8 @@ describe("Moves - Delayed Attacks", () => {
|
||||
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(enemy1.hp).toBe(enemy1.getMaxHp());
|
||||
expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp());
|
||||
expect(enemy1).toHaveFullHp();
|
||||
expect(enemy2).not.toHaveFullHp();
|
||||
expect(game.textInterceptor.logs).toContain(
|
||||
i18next.t("moveTriggers:tookMoveAttack", {
|
||||
pokemonName: getPokemonNameWithAffix(enemy2),
|
||||
@ -351,7 +353,7 @@ describe("Moves - Delayed Attacks", () => {
|
||||
|
||||
// Player Normalize was not applied due to being off field
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||
expect(enemy).not.toHaveFullHp();
|
||||
expect(game.textInterceptor.logs).toContain(
|
||||
i18next.t("moveTriggers:tookMoveAttack", {
|
||||
pokemonName: getPokemonNameWithAffix(enemy),
|
||||
@ -384,6 +386,35 @@ describe("Moves - Delayed Attacks", () => {
|
||||
expect(typeBoostSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not crash when catching & releasing a Pokemon on the same turn its delayed attack expires", async () => {
|
||||
game.override.startingModifier([{ name: "MASTER_BALL", count: 1 }]);
|
||||
await game.classicMode.startBattle([
|
||||
SpeciesId.FEEBAS,
|
||||
SpeciesId.FEEBAS,
|
||||
SpeciesId.FEEBAS,
|
||||
SpeciesId.FEEBAS,
|
||||
SpeciesId.FEEBAS,
|
||||
SpeciesId.FEEBAS,
|
||||
]);
|
||||
|
||||
game.move.use(MoveId.SPLASH);
|
||||
await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT);
|
||||
await game.toNextTurn();
|
||||
|
||||
expectFutureSightActive(1);
|
||||
|
||||
await passTurns(1);
|
||||
|
||||
// Throw master ball and release the enemy
|
||||
game.doThrowPokeball(PokeballType.MASTER_BALL);
|
||||
game.onNextPrompt("AttemptCapturePhase", UiMode.CONFIRM, () => {
|
||||
game.scene.ui.processInput(Button.CANCEL);
|
||||
});
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expectFutureSightActive(0);
|
||||
});
|
||||
|
||||
// TODO: Implement and move to a power spot's test file
|
||||
it.todo("Should activate ally's power spot when switched in during single battles");
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { BattleScene } from "#app/battle-scene";
|
||||
import { Phase } from "#app/phase";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { AttemptCapturePhase } from "#phases/attempt-capture-phase";
|
||||
import { AttemptRunPhase } from "#phases/attempt-run-phase";
|
||||
import { BattleEndPhase } from "#phases/battle-end-phase";
|
||||
import { BerryPhase } from "#phases/berry-phase";
|
||||
@ -183,6 +184,7 @@ export class PhaseInterceptor {
|
||||
PostGameOverPhase,
|
||||
RevivalBlessingPhase,
|
||||
PokemonHealPhase,
|
||||
AttemptCapturePhase,
|
||||
];
|
||||
|
||||
private endBySetMode = [
|
||||
|
Loading…
Reference in New Issue
Block a user