mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-17 01:37:36 +00:00
[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 Phaser from "phaser";
|
||||||
import UI from "./ui/ui";
|
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 Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
|
||||||
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
||||||
import { Constructor } from "#app/utils";
|
import { Constructor } from "#app/utils";
|
||||||
@ -14,7 +14,7 @@ import { Arena, ArenaBase } from "./field/arena";
|
|||||||
import { GameData } from "./system/game-data";
|
import { GameData } from "./system/game-data";
|
||||||
import { TextStyle, addTextObject, getTextColor } from "./ui/text";
|
import { TextStyle, addTextObject, getTextColor } from "./ui/text";
|
||||||
import { allMoves } from "./data/move";
|
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 AbilityBar from "./ui/ability-bar";
|
||||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
||||||
import { allAbilities } 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 { TimedEventManager } from "#app/timed-event-manager.js";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {TrainerType} from "#enums/trainer-type";
|
import {TrainerType} from "#enums/trainer-type";
|
||||||
|
import { battleSpecDialogue } from "./data/dialogue";
|
||||||
|
|
||||||
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
|
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;
|
(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 { Mode } from "./ui/ui";
|
||||||
import { Command } from "./ui/command-ui-handler";
|
import { Command } from "./ui/command-ui-handler";
|
||||||
import { Stat } from "./data/pokemon-stat";
|
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 PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
|
||||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
|
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
|
||||||
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims";
|
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 { clientSessionId, loggedInUser, updateUserInfo } from "./account";
|
||||||
import { SessionSaveData } from "./system/game-data";
|
import { SessionSaveData } from "./system/game-data";
|
||||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims";
|
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 { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue";
|
||||||
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler";
|
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler";
|
||||||
import { SettingKeys } from "./system/settings/settings";
|
import { SettingKeys } from "./system/settings/settings";
|
||||||
@ -3598,6 +3598,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
this.end();
|
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 {
|
export class MessagePhase extends Phase {
|
||||||
@ -3705,34 +3713,12 @@ export class DamagePhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
end() {
|
override end() {
|
||||||
switch (this.scene.currentBattle.battleSpec) {
|
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
|
||||||
case BattleSpec.FINAL_BOSS:
|
this.scene.initFinalBossPhaseTwo(this.getPokemon());
|
||||||
const pokemon = this.getPokemon();
|
} else {
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.end();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
89
src/test/final_boss.test.ts
Normal file
89
src/test/final_boss.test.ts
Normal file
@ -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);
|
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 {
|
getLatestMessage(): string {
|
||||||
return this.logs.pop();
|
return this.logs.pop();
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ export default class MockSprite {
|
|||||||
};
|
};
|
||||||
this.anims = {
|
this.anims = {
|
||||||
pause: () => null,
|
pause: () => null,
|
||||||
|
stop: () => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,8 +235,8 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||||||
(this.scene as BattleScene).uiContainer.add(this.tooltipContainer);
|
(this.scene as BattleScene).uiContainer.add(this.tooltipContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHandler(): UiHandler {
|
getHandler<H extends UiHandler = UiHandler>(): H {
|
||||||
return this.handlers[this.mode];
|
return this.handlers[this.mode] as H;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMessageHandler(): BattleMessageUiHandler {
|
getMessageHandler(): BattleMessageUiHandler {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user