2024-08-28 19:00:38 +00:00
|
|
|
import { clientSessionId } from "#app/account";
|
|
|
|
import BattleScene from "#app/battle-scene";
|
|
|
|
import { BattleType } from "#app/battle";
|
2024-08-30 17:59:39 +00:00
|
|
|
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
2024-08-28 19:00:38 +00:00
|
|
|
import { pokemonEvolutions } from "#app/data/pokemon-evolutions";
|
|
|
|
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
|
|
|
|
import { trainerConfigs } from "#app/data/trainer-config";
|
|
|
|
import { PlayerGender } from "#app/enums/player-gender";
|
|
|
|
import { TrainerType } from "#app/enums/trainer-type";
|
|
|
|
import Pokemon from "#app/field/pokemon";
|
|
|
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
|
|
|
import { achvs, ChallengeAchv } from "#app/system/achv";
|
|
|
|
import { Unlockables } from "#app/system/unlockables";
|
|
|
|
import { Mode } from "#app/ui/ui";
|
2024-08-19 02:23:52 +00:00
|
|
|
import i18next from "i18next";
|
2024-08-28 19:00:38 +00:00
|
|
|
import * as Utils from "#app/utils";
|
2024-08-19 02:23:52 +00:00
|
|
|
import { BattlePhase } from "./battle-phase";
|
|
|
|
import { CheckSwitchPhase } from "./check-switch-phase";
|
|
|
|
import { EncounterPhase } from "./encounter-phase";
|
|
|
|
import { GameOverModifierRewardPhase } from "./game-over-modifier-reward-phase";
|
|
|
|
import { RibbonModifierRewardPhase } from "./ribbon-modifier-reward-phase";
|
|
|
|
import { SummonPhase } from "./summon-phase";
|
|
|
|
import { EndCardPhase } from "./end-card-phase";
|
|
|
|
import { PostGameOverPhase } from "./post-game-over-phase";
|
|
|
|
import { UnlockPhase } from "./unlock-phase";
|
2024-08-28 19:00:38 +00:00
|
|
|
import { SessionSaveData } from "../system/game-data";
|
|
|
|
import TrainerData from "../system/trainer-data";
|
|
|
|
import PokemonData from "../system/pokemon-data";
|
|
|
|
import PersistentModifierData from "../system/modifier-data";
|
|
|
|
import ChallengeData from "../system/challenge-data";
|
|
|
|
import ArenaData from "../system/arena-data";
|
2024-08-19 02:23:52 +00:00
|
|
|
|
|
|
|
export class GameOverPhase extends BattlePhase {
|
|
|
|
private victory: boolean;
|
|
|
|
private firstRibbons: PokemonSpecies[] = [];
|
|
|
|
|
|
|
|
constructor(scene: BattleScene, victory?: boolean) {
|
|
|
|
super(scene);
|
|
|
|
|
|
|
|
this.victory = !!victory;
|
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
|
|
|
super.start();
|
|
|
|
|
|
|
|
// Failsafe if players somehow skip floor 200 in classic mode
|
|
|
|
if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) {
|
|
|
|
this.victory = true;
|
|
|
|
}
|
|
|
|
|
2024-09-22 03:47:32 +00:00
|
|
|
// Handle Mystery Encounter special Game Over cases
|
|
|
|
// Situations such as when player lost a battle, but it isn't treated as full Game Over
|
|
|
|
if (!this.victory && this.scene.currentBattle.mysteryEncounter?.onGameOver && !this.scene.currentBattle.mysteryEncounter.onGameOver(this.scene)) {
|
|
|
|
// Do not end the game
|
|
|
|
return this.end();
|
|
|
|
}
|
|
|
|
// Otherwise, continue standard Game Over logic
|
|
|
|
|
2024-08-19 02:23:52 +00:00
|
|
|
if (this.victory && this.scene.gameMode.isEndless) {
|
2024-09-06 17:23:19 +00:00
|
|
|
const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
|
|
|
|
const genderStr = PlayerGender[genderIndex].toLowerCase();
|
|
|
|
this.scene.ui.showDialogue(i18next.t("miscDialogue:ending_endless", { context: genderStr }), i18next.t("miscDialogue:ending_name"), 0, () => this.handleGameOver());
|
2024-08-19 02:23:52 +00:00
|
|
|
} else if (this.victory || !this.scene.enableRetries) {
|
|
|
|
this.handleGameOver();
|
|
|
|
} else {
|
|
|
|
this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => {
|
|
|
|
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
|
|
|
this.scene.ui.fadeOut(1250).then(() => {
|
|
|
|
this.scene.reset();
|
|
|
|
this.scene.clearPhaseQueue();
|
|
|
|
this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => {
|
|
|
|
this.scene.pushPhase(new EncounterPhase(this.scene, true));
|
|
|
|
|
|
|
|
const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length;
|
|
|
|
|
|
|
|
this.scene.pushPhase(new SummonPhase(this.scene, 0));
|
|
|
|
if (this.scene.currentBattle.double && availablePartyMembers > 1) {
|
|
|
|
this.scene.pushPhase(new SummonPhase(this.scene, 1));
|
|
|
|
}
|
|
|
|
if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) {
|
|
|
|
this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double));
|
|
|
|
if (this.scene.currentBattle.double && availablePartyMembers > 1) {
|
|
|
|
this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scene.ui.fadeIn(1250);
|
|
|
|
this.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, () => this.handleGameOver(), false, 0, 0, 1000);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleGameOver(): void {
|
|
|
|
const doGameOver = (newClear: boolean) => {
|
|
|
|
this.scene.disableMenu = true;
|
|
|
|
this.scene.time.delayedCall(1000, () => {
|
|
|
|
let firstClear = false;
|
|
|
|
if (this.victory && newClear) {
|
|
|
|
if (this.scene.gameMode.isClassic) {
|
|
|
|
firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY);
|
|
|
|
this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY);
|
|
|
|
this.scene.gameData.gameStats.sessionsWon++;
|
|
|
|
for (const pokemon of this.scene.getParty()) {
|
|
|
|
this.awardRibbon(pokemon);
|
|
|
|
|
|
|
|
if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) {
|
|
|
|
this.awardRibbon(pokemon, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (this.scene.gameMode.isDaily && newClear) {
|
|
|
|
this.scene.gameData.gameStats.dailyRunSessionsWon++;
|
|
|
|
}
|
|
|
|
}
|
2024-08-28 19:00:38 +00:00
|
|
|
this.scene.gameData.saveRunHistory(this.scene, this.getFinalSessionData(), this.victory);
|
2024-08-19 02:23:52 +00:00
|
|
|
const fadeDuration = this.victory ? 10000 : 5000;
|
|
|
|
this.scene.fadeOutBgm(fadeDuration, true);
|
|
|
|
const activeBattlers = this.scene.getField().filter(p => p?.isActive(true));
|
|
|
|
activeBattlers.map(p => p.hideInfo());
|
|
|
|
this.scene.ui.fadeOut(fadeDuration).then(() => {
|
|
|
|
activeBattlers.map(a => a.setVisible(false));
|
|
|
|
this.scene.setFieldScale(1, true);
|
|
|
|
this.scene.clearPhaseQueue();
|
|
|
|
this.scene.ui.clearText();
|
|
|
|
|
|
|
|
if (this.victory && this.scene.gameMode.isChallenge) {
|
|
|
|
this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c));
|
|
|
|
}
|
|
|
|
|
|
|
|
const clear = (endCardPhase?: EndCardPhase) => {
|
|
|
|
if (newClear) {
|
|
|
|
this.handleUnlocks();
|
|
|
|
}
|
|
|
|
if (this.victory && newClear) {
|
|
|
|
for (const species of this.firstRibbons) {
|
|
|
|
this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species));
|
|
|
|
}
|
|
|
|
if (!firstClear) {
|
|
|
|
this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase));
|
|
|
|
this.end();
|
|
|
|
};
|
|
|
|
|
|
|
|
if (this.victory && this.scene.gameMode.isClassic) {
|
2024-08-30 17:59:39 +00:00
|
|
|
const dialogueKey = "miscDialogue:ending";
|
2024-08-19 02:23:52 +00:00
|
|
|
|
2024-08-30 17:59:39 +00:00
|
|
|
if (!this.scene.ui.shouldSkipDialogue(dialogueKey)) {
|
2024-08-19 02:23:52 +00:00
|
|
|
this.scene.ui.fadeIn(500).then(() => {
|
2024-08-30 17:59:39 +00:00
|
|
|
const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
|
|
|
|
const genderStr = PlayerGender[genderIndex].toLowerCase();
|
|
|
|
// Dialogue has to be retrieved so that the rival's expressions can be loaded and shown via getCharVariantFromDialogue
|
|
|
|
const dialogue = i18next.t(dialogueKey, { context: genderStr });
|
|
|
|
this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(dialogue)).then(() => {
|
|
|
|
this.scene.ui.showDialogue(dialogueKey, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => {
|
2024-08-19 02:23:52 +00:00
|
|
|
this.scene.ui.fadeOut(500).then(() => {
|
|
|
|
this.scene.charSprite.hide().then(() => {
|
|
|
|
const endCardPhase = new EndCardPhase(this.scene);
|
|
|
|
this.scene.unshiftPhase(endCardPhase);
|
|
|
|
clear(endCardPhase);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const endCardPhase = new EndCardPhase(this.scene);
|
|
|
|
this.scene.unshiftPhase(endCardPhase);
|
|
|
|
clear(endCardPhase);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
clear();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Added a local check to see if the game is running offline on victory
|
|
|
|
If Online, execute apiFetch as intended
|
|
|
|
If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */
|
|
|
|
if (this.victory) {
|
|
|
|
if (!Utils.isLocal) {
|
|
|
|
Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(newClear => doGameOver(newClear));
|
|
|
|
} else {
|
|
|
|
this.scene.gameData.offlineNewClear(this.scene).then(result => {
|
|
|
|
doGameOver(result);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
doGameOver(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleUnlocks(): void {
|
|
|
|
if (this.victory && this.scene.gameMode.isClassic) {
|
|
|
|
if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
|
|
|
|
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE));
|
|
|
|
}
|
|
|
|
if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) {
|
|
|
|
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.SPLICED_ENDLESS_MODE));
|
|
|
|
}
|
|
|
|
if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) {
|
|
|
|
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE));
|
|
|
|
}
|
|
|
|
if (!this.scene.gameData.unlocks[Unlockables.EVIOLITE] && this.scene.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) {
|
|
|
|
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.EVIOLITE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void {
|
|
|
|
const speciesId = getPokemonSpecies(pokemon.species.speciesId);
|
|
|
|
const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter);
|
|
|
|
// first time classic win, award voucher
|
|
|
|
if (speciesRibbonCount === 1) {
|
|
|
|
this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter)));
|
|
|
|
}
|
|
|
|
}
|
2024-08-28 19:00:38 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This function mirrors game-data.ts' getSessionSaveData() to update the session data to reflect any changes that occurred within the last wave
|
|
|
|
* This means that level ups, item usage, evolutions, etc. will all be accurately reflected.
|
|
|
|
* @returns {@linkCode SessionSaveData} an updated version of the wave's SessionSaveData that accurately reflects the events of the wave
|
|
|
|
*/
|
|
|
|
private getFinalSessionData(): SessionSaveData {
|
|
|
|
return {
|
|
|
|
seed: this.scene.seed,
|
|
|
|
playTime: this.scene.sessionPlayTime,
|
|
|
|
gameMode: this.scene.gameMode.modeId,
|
|
|
|
party: this.scene.getParty().map(p => new PokemonData(p)),
|
|
|
|
enemyParty: this.scene.getEnemyParty().map(p => new PokemonData(p)),
|
|
|
|
modifiers: this.scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
|
|
|
|
enemyModifiers: this.scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
|
|
|
|
arena: new ArenaData(this.scene.arena),
|
|
|
|
pokeballCounts: this.scene.pokeballCounts,
|
2024-09-28 03:48:15 +00:00
|
|
|
money: Math.floor(this.scene.money),
|
2024-08-28 19:00:38 +00:00
|
|
|
score: this.scene.score,
|
|
|
|
waveIndex: this.scene.currentBattle.waveIndex,
|
|
|
|
battleType: this.scene.currentBattle.battleType,
|
|
|
|
trainer: this.scene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(this.scene.currentBattle.trainer) : null,
|
|
|
|
gameVersion: this.scene.game.config.gameVersion,
|
|
|
|
timestamp: new Date().getTime(),
|
2024-09-14 02:05:58 +00:00
|
|
|
challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)),
|
2024-09-19 14:42:29 +00:00
|
|
|
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
2024-09-14 02:05:58 +00:00
|
|
|
mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData
|
2024-08-28 19:00:38 +00:00
|
|
|
} as SessionSaveData;
|
|
|
|
}
|
2024-08-19 02:23:52 +00:00
|
|
|
}
|
2024-08-28 19:00:38 +00:00
|
|
|
|