From 0e6c2952ca4e68fa25230b5bfb0ffaba57068b07 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 17 Aug 2024 21:05:04 -0700 Subject: [PATCH] Make Disguise properly reset form on arena reset when fainted (#3612) --- src/battle-scene.ts | 4 +- src/data/ability.ts | 2 + src/test/abilities/disguise.test.ts | 66 ++++++++++++++++++++++++----- src/test/utils/phaseInterceptor.ts | 2 + 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a8edf7b3af0..674b4e256f9 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -37,7 +37,7 @@ import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "./ui/ui-theme"; import PokemonData from "./system/pokemon-data"; import { Nature } from "./data/nature"; -import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem } from "./data/pokemon-forms"; +import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem, SpeciesFormChange } from "./data/pokemon-forms"; import { FormChangePhase, QuietFormChangePhase } from "./form-change-phase"; import { getTypeRgb } from "./data/type"; import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler"; @@ -2579,7 +2579,7 @@ export default class BattleScene extends SceneBase { // in case this is NECROZMA, determine which forms this const matchingFormChangeOpts = pokemonFormChanges[pokemon.species.speciesId].filter(fc => fc.findTrigger(formChangeTriggerType) && fc.canChange(pokemon)); - let matchingFormChange; + let matchingFormChange: SpeciesFormChange | null; if (pokemon.species.speciesId === Species.NECROZMA && matchingFormChangeOpts.length > 1) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. const formChangeItemModifiers = (this.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]).filter(m => m.active).map(m => m.formChangeItem); diff --git a/src/data/ability.ts b/src/data/ability.ts index abc45273131..cfd900d621c 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5039,6 +5039,7 @@ export function initAbilities() { (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon) => Math.floor(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) + .bypassFaint() .ignorable(), new Ability(Abilities.BATTLE_BOND, 7) .attr(PostVictoryFormChangeAbAttr, () => 2) @@ -5191,6 +5192,7 @@ export function initAbilities() { .attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) .attr(PostBattleInitFormChangeAbAttr, () => 0) + .bypassFaint() .ignorable(), new Ability(Abilities.POWER_SPOT, 8) .attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3), diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index 183295f6f41..8b1b959bea8 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -2,12 +2,12 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; -import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import { StatusEffect } from "#app/data/status-effect.js"; -import { MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js"; +import { CommandPhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { Mode } from "#app/ui/ui.js"; const TIMEOUT = 20 * 1000; @@ -38,7 +38,7 @@ describe("Abilities - Disguise", () => { game.override.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]); }, TIMEOUT); - it("takes no damage from attacking move and transforms to Busted form, taking 1/8 max HP damage from the disguise breaking", async () => { + it("takes no damage from attacking move and transforms to Busted form, takes 1/8 max HP damage from the disguise breaking", async () => { await game.startBattle(); const mimikyu = game.scene.getEnemyPokemon()!; @@ -134,17 +134,30 @@ describe("Abilities - Disguise", () => { expect(mimikyu.formIndex).toBe(bustedForm); }, TIMEOUT); - it("reverts to Disguised on arena reset", async () => { - game.override.startingWave(4); + it("persists form change when wave changes with no arena reset", async () => { + game.override.starterSpecies(0); + game.override.starterForms({ + [Species.MIMIKYU]: bustedForm + }); + await game.startBattle([Species.FURRET, Species.MIMIKYU]); + const mimikyu = game.scene.getParty()[1]!; + expect(mimikyu.formIndex).toBe(bustedForm); + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.doKillOpponents(); + await game.toNextWave(); + + expect(mimikyu.formIndex).toBe(bustedForm); + }, TIMEOUT); + + it("reverts to Disguised form on arena reset", async () => { + game.override.startingWave(4); game.override.starterSpecies(Species.MIMIKYU); game.override.starterForms({ [Species.MIMIKYU]: bustedForm }); - game.override.enemySpecies(Species.MAGIKARP); - game.override.enemyAbility(Abilities.BALL_FETCH); - await game.startBattle(); const mimikyu = game.scene.getPlayerPokemon()!; @@ -153,10 +166,41 @@ describe("Abilities - Disguise", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); await game.doKillOpponents(); - await game.phaseInterceptor.to(TurnEndPhase); - game.doSelectModifier(); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextWave(); expect(mimikyu.formIndex).toBe(disguisedForm); }, TIMEOUT); + + it("reverts to Disguised form on biome change when fainted", async () => { + game.override.startingWave(10); + game.override.starterSpecies(0); + game.override.starterForms({ + [Species.MIMIKYU]: bustedForm + }); + + await game.startBattle([Species.MIMIKYU, Species.FURRET]); + + const mimikyu1 = game.scene.getPlayerPokemon()!; + + expect(mimikyu1.formIndex).toBe(bustedForm); + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.killPokemon(mimikyu1); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.doKillOpponents(); + game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { // TODO: Make tests run in set mode instead of switch mode + game.setMode(Mode.MESSAGE); + game.endPhase(); + }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); + + game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { + game.setMode(Mode.MESSAGE); + game.endPhase(); + }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); + await game.phaseInterceptor.to("PartyHealPhase"); + + expect(mimikyu1.formIndex).toBe(disguisedForm); + }, TIMEOUT); }); diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 34f79f93b6e..5a8b4ae01b2 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -15,6 +15,7 @@ import { MovePhase, NewBattlePhase, NextEncounterPhase, + PartyHealPhase, PostSummonPhase, SelectGenderPhase, SelectModifierPhase, @@ -92,6 +93,7 @@ export default class PhaseInterceptor { [QuietFormChangePhase, this.startPhase], [SwitchPhase, this.startPhase], [SwitchSummonPhase, this.startPhase], + [PartyHealPhase, this.startPhase], ]; private endBySetMode = [