[Bug] Fix final boss 2nd phase transition on status effect damage (#3226)
* fix final-boss phase change on status-effect dmg - move final-boss 2nd phase check into battle-scene. - call method at the end of damage phase - call method at the end of PostTurnStatusEffect phase * improve ui.getHandler types * WIP: final_boss.test.ts * add "should spawn Eternatus on wave 200 in END biome" test * add more final_boss tests couldn't cover the form change due to lack of support from current test framework
This commit is contained in:
parent
c2b2cf08db
commit
7582eefabc
|
@ -1,6 +1,6 @@
|
|||
import Phaser from "phaser";
|
||||
import UI from "./ui/ui";
|
||||
import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from "./phases";
|
||||
import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase, SummonPhase, ToggleDoublePositionPhase } from "./phases";
|
||||
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
|
||||
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
||||
import { Constructor } from "#app/utils";
|
||||
|
@ -14,7 +14,7 @@ import { Arena, ArenaBase } from "./field/arena";
|
|||
import { GameData } from "./system/game-data";
|
||||
import { TextStyle, addTextObject, getTextColor } from "./ui/text";
|
||||
import { allMoves } from "./data/move";
|
||||
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from "./modifier/modifier-type";
|
||||
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, modifierTypes } from "./modifier/modifier-type";
|
||||
import AbilityBar from "./ui/ability-bar";
|
||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
||||
import { allAbilities } from "./data/ability";
|
||||
|
@ -68,6 +68,7 @@ import { UiTheme } from "#enums/ui-theme";
|
|||
import { TimedEventManager } from "#app/timed-event-manager.js";
|
||||
import i18next from "i18next";
|
||||
import {TrainerType} from "#enums/trainer-type";
|
||||
import { battleSpecDialogue } from "./data/dialogue";
|
||||
|
||||
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
|
||||
|
||||
|
@ -2619,4 +2620,33 @@ export default class BattleScene extends SceneBase {
|
|||
};
|
||||
(window as any).gameInfo = gameInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized the 2nd phase of the final boss (e.g. form-change for Eternatus)
|
||||
* @param pokemon The (enemy) pokemon
|
||||
*/
|
||||
initFinalBossPhaseTwo(pokemon: Pokemon): void {
|
||||
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
|
||||
this.fadeOutBgm(Utils.fixedInt(2000), false);
|
||||
this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, null, () => {
|
||||
this.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true);
|
||||
pokemon.generateAndPopulateMoveset(1);
|
||||
this.setFieldScale(0.75);
|
||||
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
|
||||
this.currentBattle.double = true;
|
||||
const availablePartyMembers = this.getParty().filter((p) => p.isAllowedInBattle());
|
||||
if (availablePartyMembers.length > 1) {
|
||||
this.pushPhase(new ToggleDoublePositionPhase(this, true));
|
||||
if (!availablePartyMembers[1].isOnField()) {
|
||||
this.pushPhase(new SummonPhase(this, 1));
|
||||
}
|
||||
}
|
||||
|
||||
this.shiftPhase();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.shiftPhase();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMov
|
|||
import { Mode } from "./ui/ui";
|
||||
import { Command } from "./ui/command-ui-handler";
|
||||
import { Stat } from "./data/pokemon-stat";
|
||||
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier } from "./modifier/modifier";
|
||||
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier } from "./modifier/modifier";
|
||||
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
|
||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
|
||||
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims";
|
||||
|
@ -37,7 +37,7 @@ import { vouchers } from "./system/voucher";
|
|||
import { clientSessionId, loggedInUser, updateUserInfo } from "./account";
|
||||
import { SessionSaveData } from "./system/game-data";
|
||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims";
|
||||
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms";
|
||||
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms";
|
||||
import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue";
|
||||
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler";
|
||||
import { SettingKeys } from "./system/settings/settings";
|
||||
|
@ -3598,6 +3598,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
override end() {
|
||||
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
|
||||
this.scene.initFinalBossPhaseTwo(this.getPokemon());
|
||||
} else {
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MessagePhase extends Phase {
|
||||
|
@ -3705,34 +3713,12 @@ export class DamagePhase extends PokemonPhase {
|
|||
}
|
||||
}
|
||||
|
||||
end() {
|
||||
switch (this.scene.currentBattle.battleSpec) {
|
||||
case BattleSpec.FINAL_BOSS:
|
||||
const pokemon = this.getPokemon();
|
||||
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
|
||||
this.scene.fadeOutBgm(Utils.fixedInt(2000), false);
|
||||
this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, null, () => {
|
||||
this.scene.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true);
|
||||
pokemon.generateAndPopulateMoveset(1);
|
||||
this.scene.setFieldScale(0.75);
|
||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
|
||||
this.scene.currentBattle.double = true;
|
||||
const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle());
|
||||
if (availablePartyMembers.length > 1) {
|
||||
this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||
if (!availablePartyMembers[1].isOnField()) {
|
||||
this.scene.pushPhase(new SummonPhase(this.scene, 1));
|
||||
}
|
||||
}
|
||||
|
||||
override end() {
|
||||
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
|
||||
this.scene.initFinalBossPhaseTwo(this.getPokemon());
|
||||
} else {
|
||||
super.end();
|
||||
});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import { Biome } from "#app/enums/biome.js";
|
||||
import { Species } from "#app/enums/species.js";
|
||||
import { GameModes, getGameMode } from "#app/game-mode.js";
|
||||
import { EncounterPhase, SelectStarterPhase } from "#app/phases.js";
|
||||
import { Mode } from "#app/ui/ui.js";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import GameManager from "./utils/gameManager";
|
||||
import { generateStarter } from "./utils/gameManagerUtils";
|
||||
|
||||
const FinalWave = {
|
||||
Classic: 200,
|
||||
};
|
||||
|
||||
describe("Final Boss", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.startingWave(FinalWave.Classic).startingBiome(Biome.END).disableCrits();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
it("should spawn Eternatus on wave 200 in END biome", async () => {
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
expect(game.scene.getEnemyPokemon().species.speciesId).toBe(Species.ETERNATUS);
|
||||
});
|
||||
|
||||
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
|
||||
game.override.startingWave(FinalWave.Classic - 1);
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).not.toBe(FinalWave.Classic);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
expect(game.scene.getEnemyPokemon().species.speciesId).not.toBe(Species.ETERNATUS);
|
||||
});
|
||||
|
||||
it("should NOT spawn Eternatus outside of END biome", async () => {
|
||||
game.override.startingBiome(Biome.FOREST);
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
|
||||
expect(game.scene.arena.biomeType).not.toBe(Biome.END);
|
||||
expect(game.scene.getEnemyPokemon().species.speciesId).not.toBe(Species.ETERNATUS);
|
||||
});
|
||||
|
||||
it.todo("should change form on direct hit down to last boss fragment", () => {});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper function to run to the final boss encounter as it's a bit tricky due to extra dialogue
|
||||
* @param game - The game manager
|
||||
*/
|
||||
async function runToFinalBossEncounter(game: GameManager, species: Species[]) {
|
||||
console.log("===to final boss encounter===");
|
||||
await game.runToTitle();
|
||||
|
||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
|
||||
const starters = generateStarter(game.scene, species);
|
||||
const selectStarterPhase = new SelectStarterPhase(game.scene);
|
||||
game.scene.pushPhase(new EncounterPhase(game.scene, false));
|
||||
selectStarterPhase.initBattle(starters);
|
||||
});
|
||||
|
||||
game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => {
|
||||
// This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler)
|
||||
game.setMode(Mode.MESSAGE);
|
||||
const encounterPhase = game.scene.getCurrentPhase() as EncounterPhase;
|
||||
|
||||
// No need to end phase, this will do it for you
|
||||
encounterPhase.doEncounterCommon(false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(EncounterPhase, true);
|
||||
console.log("===finished run to final boss encounter===");
|
||||
}
|
|
@ -11,6 +11,11 @@ export default class TextInterceptor {
|
|||
this.logs.push(text);
|
||||
}
|
||||
|
||||
showDialogue(text: string, name: string, delay?: integer, callback?: Function, callbackDelay?: integer, promptDelay?: integer): void {
|
||||
console.log(name, text);
|
||||
this.logs.push(name, text);
|
||||
}
|
||||
|
||||
getLatestMessage(): string {
|
||||
return this.logs.pop();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class MockSprite {
|
|||
};
|
||||
this.anims = {
|
||||
pause: () => null,
|
||||
stop: () => null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -235,8 +235,8 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||
(this.scene as BattleScene).uiContainer.add(this.tooltipContainer);
|
||||
}
|
||||
|
||||
getHandler(): UiHandler {
|
||||
return this.handlers[this.mode];
|
||||
getHandler<H extends UiHandler = UiHandler>(): H {
|
||||
return this.handlers[this.mode] as H;
|
||||
}
|
||||
|
||||
getMessageHandler(): BattleMessageUiHandler {
|
||||
|
|
Loading…
Reference in New Issue