pokerogue/src/ui/run-info-ui-handler.ts

1156 lines
49 KiB
TypeScript

import { GameModes } from "#enums/game-modes";
import UiHandler from "./ui-handler";
import type { SessionSaveData } from "../system/game-data";
import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text";
import { UiMode } from "#enums/ui-mode";
import { addWindow } from "./ui-theme";
import { getPokeballAtlasKey } from "#app/data/pokeball";
import { formatLargeNumber, getPlayTimeString, formatMoney, formatFancyLargeNumber } from "#app/utils/common";
import type PokemonData from "../system/pokemon-data";
import i18next from "i18next";
import { Button } from "../enums/buttons";
import { BattleType } from "#enums/battle-type";
import { TrainerVariant } from "#enums/trainer-variant";
import { Challenges } from "#enums/challenges";
import { getLuckString, getLuckTextTint } from "../modifier/modifier-type";
import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle";
import { getTypeRgb } from "#app/data/type";
import { PokemonType } from "#enums/pokemon-type";
import { TypeColor, TypeShadow } from "#app/enums/color";
import { getNatureStatMultiplier, getNatureName } from "../data/nature";
import { getVariantTint } from "#app/sprites/variant";
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
import * as Modifier from "#app/modifier/modifier";
import type { SpeciesId } from "#enums/species-id";
import { PlayerGender } from "#enums/player-gender";
import { SettingKeyboard } from "#app/system/settings/settings-keyboard";
import { getBiomeName } from "#app/data/balance/biomes";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
/**
* RunInfoUiMode indicates possible overlays of RunInfoUiHandler.
* MAIN <-- default overlay that can return back to RunHistoryUiHandler + should eventually have its own enum once more pages are added to RunInfoUiHandler
* HALL_OF_FAME, ENDING_ART, etc. <-- overlays that should return back to MAIN
*/
enum RunInfoUiMode {
MAIN,
HALL_OF_FAME,
ENDING_ART,
}
export enum RunDisplayMode {
RUN_HISTORY,
SESSION_PREVIEW,
}
/**
* Some variables are protected because this UI class will most likely be extended in the future to display more information.
* These variables will most likely be shared across 'classes' aka pages.
* I believe that it is possible that the contents/methods of the first page will be placed in their own class that is an extension of RunInfoUiHandler as more pages are added.
* For now, I leave as is.
*/
export default class RunInfoUiHandler extends UiHandler {
protected runDisplayMode: RunDisplayMode;
protected runInfo: SessionSaveData;
protected isVictory: boolean;
protected pageMode: RunInfoUiMode;
protected runContainer: Phaser.GameObjects.Container;
private runResultContainer: Phaser.GameObjects.Container;
private runInfoContainer: Phaser.GameObjects.Container;
private partyContainer: Phaser.GameObjects.Container;
private statsBgWidth: number;
private hallofFameContainer: Phaser.GameObjects.Container;
private endCardContainer: Phaser.GameObjects.Container;
private partyVisibility: boolean;
private modifiersModule: any;
constructor() {
super(UiMode.RUN_INFO);
}
override async setup() {
this.runContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1);
// The import of the modifiersModule is loaded here to sidestep async/await issues.
this.modifiersModule = Modifier;
this.runContainer.setVisible(false);
globalScene.loadImage("encounter_exclaim", "mystery-encounters");
}
/**
* This takes a run's RunEntry and uses the information provided to display essential information about the player's run.
* @param args[0] : a RunEntry object
*
* show() creates these UI objects in order -
* A solid-color background used to hide RunHistoryUiHandler
* Header: Page Title + Option to Display Modifiers
* Run Result Container:
* Party Container:
* this.isVictory === true --> Hall of Fame Container:
*/
override show(args: any[]): boolean {
super.show(args);
const gameStatsBg = globalScene.add.rectangle(
0,
0,
globalScene.game.canvas.width,
globalScene.game.canvas.height,
0x006860,
);
gameStatsBg.setOrigin(0, 0);
this.runContainer.add(gameStatsBg);
const run = args[0];
this.runDisplayMode = args[1];
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
this.runInfo = globalScene.gameData.parseSessionData(JSON.stringify(run.entry));
this.isVictory = run.isVictory ?? false;
} else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) {
this.runInfo = args[0];
}
// Assigning information necessary for the UI's creation
this.pageMode = RunInfoUiMode.MAIN;
// Creates Header and adds to this.runContainer
this.addHeader();
this.statsBgWidth = (globalScene.game.canvas.width / 6 - 2) / 3;
// Creates Run Result Container
this.runResultContainer = globalScene.add.container(0, 24);
const runResultWindow = addWindow(0, 0, this.statsBgWidth - 11, 65);
runResultWindow.setOrigin(0, 0);
runResultWindow.setName("Run_Result_Window");
this.runResultContainer.add(runResultWindow);
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
this.parseRunResult();
} else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) {
this.parseRunStatus();
}
// Creates Run Info Container
this.runInfoContainer = globalScene.add.container(0, 89);
const runInfoWindow = addWindow(0, 0, this.statsBgWidth - 11, 90);
const runInfoWindowCoords = runInfoWindow.getBottomRight();
this.runInfoContainer.add(runInfoWindow);
this.parseRunInfo(runInfoWindowCoords.x, runInfoWindowCoords.y);
// Creates Player Party Container
this.partyContainer = globalScene.add.container(this.statsBgWidth - 10, 23);
this.parsePartyInfo();
this.showParty(true);
this.runContainer.setInteractive(
new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6),
Phaser.Geom.Rectangle.Contains,
);
this.getUi().bringToTop(this.runContainer);
this.runContainer.setVisible(true);
// Creates Hall of Fame if the run entry contains a victory
if (this.isVictory) {
this.createHallofFame();
this.getUi().bringToTop(this.hallofFameContainer);
}
this.setCursor(0);
this.getUi().add(this.runContainer);
this.getUi().hideTooltip();
return true;
}
/**
* Creates and adds the header background, title text, and important buttons to RunInfoUiHandler
* It does check if the run has modifiers before adding a button for the user to display their party's held items
* It does not check if the run has any PokemonHeldItemModifiers though.
*/
private addHeader() {
const headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24);
headerBg.setOrigin(0, 0);
this.runContainer.add(headerBg);
if (this.runInfo.modifiers.length !== 0) {
const headerBgCoords = headerBg.getTopRight();
const abilityButtonContainer = globalScene.add.container(0, 0);
const abilityButtonText = addTextObject(8, 0, i18next.t("runHistory:viewHeldItems"), TextStyle.WINDOW, {
fontSize: "34px",
});
const gamepadType = this.getUi().getGamepadType();
let abilityButtonElement: Phaser.GameObjects.Sprite;
if (gamepadType === "touch") {
abilityButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 2, "keyboard", "E.png");
} else {
abilityButtonElement = new Phaser.GameObjects.Sprite(
globalScene,
0,
2,
gamepadType,
globalScene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Ability),
);
}
abilityButtonContainer.add([abilityButtonText, abilityButtonElement]);
abilityButtonContainer.setPosition(
headerBgCoords.x - abilityButtonText.displayWidth - abilityButtonElement.displayWidth - 8,
10,
);
this.runContainer.add(abilityButtonContainer);
}
const headerText = addTextObject(0, 0, i18next.t("runHistory:runInfo"), TextStyle.SETTINGS_LABEL);
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
this.runContainer.add(headerText);
}
/**
* Shows the run's end result
*
* Victory : The run will display options to allow the player to view the Hall of Fame + Ending Art
* Defeat : The run will show the opposing Pokemon (+ Trainer) that the trainer was defeated by.
* Defeat can call either parseWildSingleDefeat(), parseWildDoubleDefeat(), or parseTrainerDefeat()
*
*/
private async parseRunResult() {
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
const genderStr = PlayerGender[genderIndex];
const runResultTextStyle = this.isVictory ? TextStyle.PERFECT_IV : TextStyle.SUMMARY_RED;
const runResultTitle = this.isVictory
? i18next.t("runHistory:victory")
: i18next.t("runHistory:defeated", { context: genderStr });
const runResultText = addTextObject(
6,
5,
`${runResultTitle} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`,
runResultTextStyle,
{ fontSize: "65px", lineSpacing: 0.1 },
);
if (this.isVictory) {
const hallofFameInstructionContainer = globalScene.add.container(0, 0);
const shinyButtonText = addTextObject(8, 0, i18next.t("runHistory:viewHallOfFame"), TextStyle.WINDOW, {
fontSize: "65px",
});
const formButtonText = addTextObject(8, 12, i18next.t("runHistory:viewEndingSplash"), TextStyle.WINDOW, {
fontSize: "65px",
});
const gamepadType = this.getUi().getGamepadType();
let shinyButtonElement: Phaser.GameObjects.Sprite;
let formButtonElement: Phaser.GameObjects.Sprite;
if (gamepadType === "touch") {
shinyButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 4, "keyboard", "R.png");
formButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 16, "keyboard", "F.png");
} else {
shinyButtonElement = new Phaser.GameObjects.Sprite(
globalScene,
0,
4,
gamepadType,
globalScene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Shiny),
);
formButtonElement = new Phaser.GameObjects.Sprite(
globalScene,
0,
16,
gamepadType,
globalScene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Form),
);
}
hallofFameInstructionContainer.add([shinyButtonText, shinyButtonElement]);
hallofFameInstructionContainer.add([formButtonText, formButtonElement]);
hallofFameInstructionContainer.setPosition(12, 25);
this.runResultContainer.add(hallofFameInstructionContainer);
}
this.runResultContainer.add(runResultText);
if (!this.isVictory) {
const enemyContainer = globalScene.add.container(0, 0);
// Wild - Single and Doubles
if (
this.runInfo.battleType === BattleType.WILD ||
(this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && !this.runInfo.trainer)
) {
switch (this.runInfo.enemyParty.length) {
case 1:
// Wild - Singles
this.parseWildSingleDefeat(enemyContainer);
break;
case 2:
//Wild - Doubles
this.parseWildDoubleDefeat(enemyContainer);
break;
}
} else if (
this.runInfo.battleType === BattleType.TRAINER ||
(this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && this.runInfo.trainer)
) {
this.parseTrainerDefeat(enemyContainer);
}
this.runResultContainer.add(enemyContainer);
}
this.runContainer.add(this.runResultContainer);
}
/**
* This function is used when the Run Info UI is used to preview a Session.
* It edits {@linkcode runResultContainer}, but most importantly - does not display the negative results of a Mystery Encounter or any details of a trainer's party.
* Trainer Parties are replaced with their sprites, names, and their party size.
* Mystery Encounters contain sprites associated with MEs + the title of the specific ME.
*/
private parseRunStatus() {
const enemyContainer = globalScene.add.container(0, 0);
this.runResultContainer.add(enemyContainer);
if (this.runInfo.battleType === BattleType.WILD) {
if (this.runInfo.enemyParty.length === 1) {
this.parseWildSingleDefeat(enemyContainer);
} else if (this.runInfo.enemyParty.length === 2) {
this.parseWildDoubleDefeat(enemyContainer);
}
} else if (this.runInfo.battleType === BattleType.TRAINER) {
this.showTrainerSprites(enemyContainer);
const row_limit = 3;
this.runInfo.enemyParty.forEach((p, i) => {
const pokeball = globalScene.add.sprite(0, 0, "pb");
pokeball.setFrame(getPokeballAtlasKey(p.pokeball));
pokeball.setScale(0.5);
pokeball.setPosition(58 + (i % row_limit) * 8, i <= 2 ? 18 : 25);
enemyContainer.add(pokeball);
});
const trainerObj = this.runInfo.trainer.toTrainer();
const RIVAL_TRAINER_ID_THRESHOLD = 375;
let trainerName = "";
if (this.runInfo.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) {
trainerName =
trainerObj.variant === TrainerVariant.FEMALE
? i18next.t("trainerNames:rival_female")
: i18next.t("trainerNames:rival");
} else {
trainerName = trainerObj.getName(0, true);
}
const boxString = i18next
.t(trainerObj.variant !== TrainerVariant.DOUBLE ? "battle:trainerAppeared" : "battle:trainerAppearedDouble", {
trainerName: trainerName,
})
.replace(/\n/g, " ");
const descContainer = globalScene.add.container(0, 0);
const textBox = addTextObject(0, 0, boxString, TextStyle.WINDOW, {
fontSize: "35px",
wordWrap: { width: 200 },
});
descContainer.add(textBox);
descContainer.setPosition(55, 32);
this.runResultContainer.add(descContainer);
} else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) {
const encounterExclaim = globalScene.add.sprite(0, 0, "encounter_exclaim");
encounterExclaim.setPosition(34, 26);
encounterExclaim.setScale(0.65);
const subSprite = globalScene.add.sprite(56, -106, "pkmn__sub");
subSprite.setScale(0.65);
subSprite.setPosition(34, 46);
const mysteryEncounterTitle = i18next.t(
globalScene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true)
.localizationKey + ":title",
);
const descContainer = globalScene.add.container(0, 0);
const textBox = addTextObject(0, 0, mysteryEncounterTitle, TextStyle.WINDOW, {
fontSize: "45px",
wordWrap: { width: 160 },
});
descContainer.add(textBox);
descContainer.setPosition(47, 37);
this.runResultContainer.add([encounterExclaim, subSprite, descContainer]);
}
const runResultWindow = this.runResultContainer.getByName("Run_Result_Window") as Phaser.GameObjects.Image;
const windowCenterX = runResultWindow.getTopCenter().x;
const windowBottomY = runResultWindow.getBottomCenter().y;
const runStatusText = addTextObject(
windowCenterX,
5,
`${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`,
TextStyle.WINDOW,
{ fontSize: "60px", lineSpacing: 0.1 },
);
runStatusText.setOrigin(0.5, 0);
const currentBiomeText = addTextObject(
windowCenterX,
windowBottomY - 5,
`${getBiomeName(this.runInfo.arena.biome)}`,
TextStyle.WINDOW,
{ fontSize: "60px" },
);
currentBiomeText.setOrigin(0.5, 1);
this.runResultContainer.add([runStatusText, currentBiomeText]);
this.runContainer.add(this.runResultContainer);
}
/**
* This function is called to edit an enemyContainer to represent a loss from a defeat by a wild single Pokemon battle.
* @param enemyContainer - container holding enemy visual and level information
*/
private parseWildSingleDefeat(enemyContainer: Phaser.GameObjects.Container) {
const enemyIconContainer = globalScene.add.container(0, 0);
const enemyData = this.runInfo.enemyParty[0];
const bossStatus = enemyData.boss;
enemyData.boss = false;
enemyData["player"] = true;
//addPokemonIcon() throws an error if the Pokemon used is a boss
const enemy = enemyData.toPokemon();
const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0);
const enemyLevelStyle = bossStatus ? TextStyle.PARTY_RED : TextStyle.PARTY;
const enemyLevel = addTextObject(
36,
26,
`${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`,
enemyLevelStyle,
{ fontSize: "44px", color: "#f8f8f8" },
);
enemyLevel.setShadow(0, 0, undefined);
enemyLevel.setStroke("#424242", 14);
enemyLevel.setOrigin(1, 0);
enemyIconContainer.add(enemyIcon);
enemyIconContainer.add(enemyLevel);
enemyContainer.add(enemyIconContainer);
enemyContainer.setPosition(27, 12);
enemy.destroy();
}
/**
* This function is called to edit a container to represent a loss from a defeat by a wild double Pokemon battle.
* This function and parseWildSingleDefeat can technically be merged, but I find it tricky to manipulate the different 'centers' a single battle / double battle container will hold.
* @param enemyContainer - container holding enemy visuals and level information
*/
private parseWildDoubleDefeat(enemyContainer: Phaser.GameObjects.Container) {
this.runInfo.enemyParty.forEach((enemyData, e) => {
const enemyIconContainer = globalScene.add.container(0, 0);
const bossStatus = enemyData.boss;
enemyData.boss = false;
enemyData["player"] = true;
const enemy = enemyData.toPokemon();
const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0);
const enemyLevel = addTextObject(
36,
26,
`${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`,
bossStatus ? TextStyle.PARTY_RED : TextStyle.PARTY,
{ fontSize: "44px", color: "#f8f8f8" },
);
enemyLevel.setShadow(0, 0, undefined);
enemyLevel.setStroke("#424242", 14);
enemyLevel.setOrigin(1, 0);
enemyIconContainer.add(enemyIcon);
enemyIconContainer.add(enemyLevel);
enemyIconContainer.setPosition(e * 35, 0);
enemyContainer.add(enemyIconContainer);
enemy.destroy();
});
enemyContainer.setPosition(8, 14);
}
/**
* This loads the enemy sprites, positions, and scales them according to the current display mode of the RunInfo UI and then adds them to the container parameter.
* Used by {@linkcode parseRunStatus} and {@linkcode parseTrainerDefeat}
* @param enemyContainer a Phaser Container that should hold enemy sprites
*/
private showTrainerSprites(enemyContainer: Phaser.GameObjects.Container) {
// Creating the trainer sprite and adding it to enemyContainer
const tObj = this.runInfo.trainer.toTrainer();
// Loads trainer assets on demand, as they are not loaded by default in the scene
tObj.config.loadAssets(this.runInfo.trainer.variant).then(() => {
const tObjSpriteKey = tObj.config.getSpriteKey(this.runInfo.trainer.variant === TrainerVariant.FEMALE, false);
const tObjSprite = globalScene.add.sprite(0, 5, tObjSpriteKey);
if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE && !tObj.config.doubleOnly) {
const doubleContainer = globalScene.add.container(5, 8);
tObjSprite.setPosition(-3, -3);
const tObjPartnerSpriteKey = tObj.config.getSpriteKey(true, true);
const tObjPartnerSprite = globalScene.add.sprite(5, -3, tObjPartnerSpriteKey);
// Double Trainers have smaller sprites than Single Trainers
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
tObjPartnerSprite.setScale(0.2);
tObjSprite.setScale(0.2);
doubleContainer.add(tObjSprite);
doubleContainer.add(tObjPartnerSprite);
doubleContainer.setPosition(12, 38);
} else {
tObjSprite.setScale(0.55);
tObjSprite.setPosition(-9, -3);
tObjPartnerSprite.setScale(0.55);
doubleContainer.add([tObjSprite, tObjPartnerSprite]);
doubleContainer.setPosition(28, 34);
}
enemyContainer.add(doubleContainer);
} else {
const scale = this.runDisplayMode === RunDisplayMode.RUN_HISTORY ? 0.35 : 0.55;
const position = this.runDisplayMode === RunDisplayMode.RUN_HISTORY ? [12, 28] : [30, 32];
tObjSprite.setScale(scale, scale);
tObjSprite.setPosition(position[0], position[1]);
enemyContainer.add(tObjSprite);
}
});
}
/**
* This edits a container to represent a loss from a defeat by a trainer battle.
* The trainers are placed to the left of their party.
* Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...)
*
* Party Pokemon have their icons, terastalization status, and level shown.
* @param enemyContainer - container holding enemy visuals and level information
*/
private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) {
// Loads and adds trainer sprites to the UI
this.showTrainerSprites(enemyContainer);
// Creates the Pokemon icons + level information and adds it to enemyContainer
// 2 Rows x 3 Columns
const enemyPartyContainer = globalScene.add.container(0, 0);
this.runInfo.enemyParty.forEach((enemyData, e) => {
const pokemonRowHeight = Math.floor(e / 3);
const enemyIconContainer = globalScene.add.container(0, 0);
enemyIconContainer.setScale(0.6);
const isBoss = enemyData.boss;
enemyData.boss = false;
enemyData["player"] = true;
const enemy = enemyData.toPokemon();
const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0);
enemyIcon.setPosition(39 * (e % 3) + 5, 35 * pokemonRowHeight);
const enemyLevel = addTextObject(
43 * (e % 3),
27 * (pokemonRowHeight + 1),
`${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`,
isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY,
{ fontSize: "54px" },
);
enemyLevel.setShadow(0, 0, undefined);
enemyLevel.setStroke("#424242", 14);
enemyLevel.setOrigin(0, 0);
enemyIconContainer.add(enemyIcon);
enemyIconContainer.add(enemyLevel);
enemyPartyContainer.add(enemyIconContainer);
enemy.destroy();
});
enemyPartyContainer.setPosition(25, 15);
enemyContainer.add(enemyPartyContainer);
}
/**
* Shows information about the run like the run's mode, duration, luck, money, and player held items
* The values for luck and money are from the end of the run, not the player's luck at the start of the run.
* @param windowX
* @param windowY These two params are the coordinates of the window's bottom right corner. This is used to dynamically position Luck based on its length, creating a nice layout regardless of language / luck value.
*/
private async parseRunInfo(windowX: number, windowY: number) {
// Parsing and displaying the mode.
// In the future, parsing Challenges + Challenge Rules may have to be reworked as PokeRogue adds additional challenges and users can stack these challenges in various ways.
const modeText = addBBCodeTextObject(7, 0, "", TextStyle.WINDOW, {
fontSize: "50px",
lineSpacing: 3,
});
modeText.setPosition(7, 5);
modeText.appendText(i18next.t("runHistory:mode") + ": ", false);
switch (this.runInfo.gameMode) {
case GameModes.DAILY:
modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false);
break;
case GameModes.SPLICED_ENDLESS:
modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false);
break;
case GameModes.CHALLENGE: {
modeText.appendText(`${i18next.t("gameMode:challenge")}`, false);
modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `);
modeText.setWrapMode(1); // wrap by word
modeText.setWrapWidth(500);
const rules: string[] = this.challengeParser();
if (rules) {
for (let i = 0; i < rules.length; i++) {
if (i > 0) {
modeText.appendText(" + ", false);
}
modeText.appendText(rules[i], false);
}
}
break;
}
case GameModes.ENDLESS:
modeText.appendText(`${i18next.t("gameMode:endless")}`, false);
break;
case GameModes.CLASSIC:
modeText.appendText(`${i18next.t("gameMode:classic")}`, false);
break;
}
// If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement.
if (
(this.runInfo.gameMode === GameModes.ENDLESS || this.runInfo.gameMode === GameModes.SPLICED_ENDLESS) &&
this.runInfo.waveIndex === globalScene.gameData.gameStats.highestEndlessWave
) {
modeText.appendText(` [${i18next.t("runHistory:personalBest")}]`);
modeText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969);
}
// Duration + Money
const runInfoTextContainer = globalScene.add.container(0, 0);
// Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12.
const lineSpacing = i18next.resolvedLanguage === "ja" ? 12 : 3;
const runInfoText = addBBCodeTextObject(7, 0, "", TextStyle.WINDOW, {
fontSize: "50px",
lineSpacing: lineSpacing,
});
const runTime = getPlayTimeString(this.runInfo.playTime);
runInfoText.appendText(`${i18next.t("runHistory:runLength")}: ${runTime}`, false);
const runMoney = formatMoney(globalScene.moneyFormat, this.runInfo.money);
const moneyTextColor = getTextColor(TextStyle.MONEY_WINDOW, false, globalScene.uiTheme);
runInfoText.appendText(
`[color=${moneyTextColor}]${i18next.t("battleScene:moneyOwned", { formattedMoney: runMoney })}[/color]`,
);
runInfoText.setPosition(7, 70);
runInfoTextContainer.add(runInfoText);
// Luck
// Uses the parameters windowX and windowY to dynamically position the luck value neatly into the bottom right corner
const luckText = addBBCodeTextObject(0, 0, "", TextStyle.WINDOW, {
fontSize: "55px",
});
const luckValue = Phaser.Math.Clamp(
this.runInfo.party
.map(p => p.toPokemon().getLuck())
.reduce((total: number, value: number) => (total += value), 0),
0,
14,
);
let luckInfo = i18next.t("runHistory:luck") + ": " + getLuckString(luckValue);
if (luckValue < 14) {
luckInfo = "[color=#" + getLuckTextTint(luckValue).toString(16) + "]" + luckInfo + "[/color]";
} else {
luckText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969);
}
luckText.appendText("[align=right]" + luckInfo + "[/align]", false);
luckText.setPosition(windowX - luckText.displayWidth - 5, windowY - 13);
runInfoTextContainer.add(luckText);
// Player Held Items
// A max of 20 items can be displayed. A + sign will be added if the run's held items pushes past this maximum to show the user that there are more.
if (this.runInfo.modifiers.length) {
let visibleModifierIndex = 0;
const modifierIconsContainer = globalScene.add.container(
8,
this.runInfo.gameMode === GameModes.CHALLENGE ? 20 : 15,
);
modifierIconsContainer.setScale(0.45);
for (const m of this.runInfo.modifiers) {
const modifier = m.toModifier(this.modifiersModule[m.className]);
if (modifier instanceof Modifier.PokemonHeldItemModifier) {
continue;
}
const icon = modifier?.getIcon(false);
if (icon) {
const rowHeightModifier = Math.floor(visibleModifierIndex / 7);
icon.setPosition(24 * (visibleModifierIndex % 7), 20 + 35 * rowHeightModifier);
modifierIconsContainer.add(icon);
}
if (++visibleModifierIndex === 20) {
const maxItems = addTextObject(45, 90, "+", TextStyle.WINDOW);
maxItems.setPositionRelative(modifierIconsContainer, 70, 45);
this.runInfoContainer.add(maxItems);
break;
}
}
this.runInfoContainer.add(modifierIconsContainer);
}
this.runInfoContainer.add(modeText);
this.runInfoContainer.add(runInfoTextContainer);
this.runContainer.add(this.runInfoContainer);
}
/**
* This function parses the Challenges section of the Run Entry and returns a list of active challenge.
* @return string[] of active challenge names
*/
private challengeParser(): string[] {
const rules: string[] = [];
for (let i = 0; i < this.runInfo.challenges.length; i++) {
if (this.runInfo.challenges[i].value !== 0) {
switch (this.runInfo.challenges[i].id) {
case Challenges.SINGLE_GENERATION:
rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`));
break;
case Challenges.SINGLE_TYPE: {
const typeRule = PokemonType[this.runInfo.challenges[i].value - 1];
const typeTextColor = `[color=${TypeColor[typeRule]}]`;
const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`;
const typeText =
typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]";
rules.push(typeText);
break;
}
case Challenges.INVERSE_BATTLE:
rules.push(i18next.t("challenges:inverseBattle.shortName"));
break;
default: {
const localisationKey = Challenges[this.runInfo.challenges[i].id]
.split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
rules.push(i18next.t(`challenges:${localisationKey}.name`));
break;
}
}
}
}
return rules;
}
/**
* Parses and displays the run's player party.
* Default Information: Icon, Level, Nature, Ability, Passive, Shiny Status, Fusion Status, Stats, and Moves.
* B-Side Information: Icon + Held Items (Can be displayed to the user through pressing the abilityButton)
*/
private parsePartyInfo(): void {
const party = this.runInfo.party;
const currentLanguage = i18next.resolvedLanguage ?? "en";
const windowHeight = (globalScene.game.canvas.height / 6 - 23) / 6;
party.forEach((p: PokemonData, i: number) => {
const pokemonInfoWindow = new RoundRectangle(globalScene, 0, 14, this.statsBgWidth * 2 + 10, windowHeight - 2, 3);
const pokemon = p.toPokemon();
const pokemonInfoContainer = globalScene.add.container(this.statsBgWidth + 5, (windowHeight - 0.5) * i);
const types = pokemon.getTypes();
const type1 = getTypeRgb(types[0]);
const type1Color = new Phaser.Display.Color(type1[0], type1[1], type1[2]);
const bgColor = type1Color.clone().darken(45);
pokemonInfoWindow.setFillStyle(bgColor.color);
const iconContainer = globalScene.add.container(0, 0);
const icon = globalScene.addPokemonIcon(pokemon, 0, 0, 0, 0);
icon.setScale(0.75);
icon.setPosition(-99, 1);
const type2 = types[1] ? getTypeRgb(types[1]) : undefined;
const type2Color = type2 ? new Phaser.Display.Color(type2[0], type2[1], type2[2]) : undefined;
type2Color
? pokemonInfoWindow.setStrokeStyle(1, type2Color.color, 0.95)
: pokemonInfoWindow.setStrokeStyle(1, type1Color.color, 0.95);
this.getUi().bringToTop(icon);
// Contains Name, Level + Nature, Ability, Passive
const pokeInfoTextContainer = globalScene.add.container(-85, 3.5);
const textContainerFontSize = "34px";
// This checks if the Pokemon's nature has been overwritten during the run and displays the change accurately
const pNature = pokemon.getNature();
const pNatureName = getNatureName(pNature);
const pName = pokemon.getNameToRender();
//With the exception of Korean/Traditional Chinese/Simplified Chinese, the code shortens the terms for ability and passive to their first letter.
//These languages are exempted because they are already short enough.
const exemptedLanguages = ["ko", "zh_CN", "zh_TW"];
let passiveLabel = i18next.t("starterSelectUiHandler:passive") ?? "-";
let abilityLabel = i18next.t("starterSelectUiHandler:ability") ?? "-";
if (!exemptedLanguages.includes(currentLanguage)) {
passiveLabel = passiveLabel.charAt(0);
abilityLabel = abilityLabel.charAt(0);
}
const pPassiveInfo = pokemon.passive ? passiveLabel + ": " + pokemon.getPassiveAbility().name : "";
const pAbilityInfo = abilityLabel + ": " + pokemon.getAbility().name;
// Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12.
const lineSpacing = i18next.resolvedLanguage === "ja" ? 12 : 3;
const pokeInfoText = addBBCodeTextObject(0, 0, pName, TextStyle.SUMMARY, {
fontSize: textContainerFontSize,
lineSpacing: lineSpacing,
});
pokeInfoText.appendText(
`${i18next.t("saveSlotSelectUiHandler:lv")}${formatFancyLargeNumber(pokemon.level, 1)} - ${pNatureName}`,
);
pokeInfoText.appendText(pAbilityInfo);
pokeInfoText.appendText(pPassiveInfo);
pokeInfoTextContainer.add(pokeInfoText);
// Pokemon Stats
// Colored Arrows (Red/Blue) are placed by stats that are boosted from natures
const pokeStatTextContainer = globalScene.add.container(-35, 6);
const pStats: string[] = [];
pokemon.stats.forEach(element => pStats.push(formatFancyLargeNumber(element, 1)));
for (let i = 0; i < pStats.length; i++) {
const isMult = getNatureStatMultiplier(pNature, i);
pStats[i] = isMult < 1 ? pStats[i] + "[color=#40c8f8]↓[/color]" : pStats[i];
pStats[i] = isMult > 1 ? pStats[i] + "[color=#f89890]↑[/color]" : pStats[i];
}
const hp = i18next.t("pokemonInfo:Stat.HPshortened") + ": " + pStats[0];
const atk = i18next.t("pokemonInfo:Stat.ATKshortened") + ": " + pStats[1];
const def = i18next.t("pokemonInfo:Stat.DEFshortened") + ": " + pStats[2];
const spatk = i18next.t("pokemonInfo:Stat.SPATKshortened") + ": " + pStats[3];
const spdef = i18next.t("pokemonInfo:Stat.SPDEFshortened") + ": " + pStats[4];
const speedLabel =
currentLanguage === "es-ES" || currentLanguage === "pt_BR"
? i18next.t("runHistory:SPDshortened")
: i18next.t("pokemonInfo:Stat.SPDshortened");
const speed = speedLabel + ": " + pStats[5];
// Column 1: HP Atk Def
const pokeStatText1 = addBBCodeTextObject(-5, 0, hp, TextStyle.SUMMARY, {
fontSize: textContainerFontSize,
lineSpacing: lineSpacing,
});
pokeStatText1.appendText(atk);
pokeStatText1.appendText(def);
pokeStatTextContainer.add(pokeStatText1);
// Column 2: SpAtk SpDef Speed
const pokeStatText2 = addBBCodeTextObject(25, 0, spatk, TextStyle.SUMMARY, {
fontSize: textContainerFontSize,
lineSpacing: lineSpacing,
});
pokeStatText2.appendText(spdef);
pokeStatText2.appendText(speed);
pokeStatTextContainer.add(pokeStatText2);
// Shiny + Fusion Status
const marksContainer = globalScene.add.container(0, 0);
if (pokemon.fusionSpecies) {
const splicedIcon = globalScene.add.image(0, 0, "icon_spliced");
splicedIcon.setScale(0.35);
splicedIcon.setOrigin(0, 0);
pokemon.isShiny()
? splicedIcon.setPositionRelative(pokeInfoTextContainer, 35, 0)
: splicedIcon.setPositionRelative(pokeInfoTextContainer, 28, 0);
marksContainer.add(splicedIcon);
this.getUi().bringToTop(splicedIcon);
}
if (pokemon.isShiny()) {
const doubleShiny = pokemon.isFusion() && pokemon.shiny && pokemon.fusionShiny;
const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`);
shinyStar.setOrigin(0, 0);
shinyStar.setScale(0.65);
shinyStar.setPositionRelative(pokeInfoTextContainer, 28, 0);
shinyStar.setTint(getVariantTint(!doubleShiny ? pokemon.getVariant() : pokemon.variant));
marksContainer.add(shinyStar);
this.getUi().bringToTop(shinyStar);
if (doubleShiny) {
const fusionShinyStar = globalScene.add.image(0, 0, "shiny_star_small_2");
fusionShinyStar.setOrigin(0, 0);
fusionShinyStar.setScale(0.5);
fusionShinyStar.setPosition(shinyStar.x + 1, shinyStar.y + 1);
fusionShinyStar.setTint(getVariantTint(pokemon.fusionVariant));
marksContainer.add(fusionShinyStar);
this.getUi().bringToTop(fusionShinyStar);
}
}
// Pokemon Moveset
// Need to check if dynamically typed moves
const pokemonMoveset = pokemon.getMoveset();
const movesetContainer = globalScene.add.container(70, -29);
const pokemonMoveBgs: Phaser.GameObjects.NineSlice[] = [];
const pokemonMoveLabels: Phaser.GameObjects.Text[] = [];
const movePos = [
[-6.5, 35.5],
[37, 35.5],
[-6.5, 43.5],
[37, 43.5],
];
for (let m = 0; m < pokemonMoveset?.length; m++) {
const moveContainer = globalScene.add.container(movePos[m][0], movePos[m][1]);
moveContainer.setScale(0.5);
const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 85, 15, 2, 2, 2, 2);
moveBg.setOrigin(1, 0);
const moveLabel = addTextObject(-moveBg.width / 2, 2, "-", TextStyle.PARTY);
moveLabel.setOrigin(0.5, 0);
moveLabel.setName("text-move-label");
pokemonMoveBgs.push(moveBg);
pokemonMoveLabels.push(moveLabel);
moveContainer.add(moveBg);
moveContainer.add(moveLabel);
movesetContainer.add(moveContainer);
const move = pokemonMoveset[m]?.getMove();
pokemonMoveBgs[m].setFrame(PokemonType[move ? move.type : PokemonType.UNKNOWN].toString().toLowerCase());
pokemonMoveLabels[m].setText(move ? move.name : "-");
}
// Pokemon Held Items - not displayed by default
// Endless/Endless Spliced have a different scale because Pokemon tend to accumulate more items in these runs.
const heldItemsScale =
this.runInfo.gameMode === GameModes.SPLICED_ENDLESS || this.runInfo.gameMode === GameModes.ENDLESS ? 0.25 : 0.5;
const heldItemsContainer = globalScene.add.container(-82, 2);
const heldItemsList: Modifier.PokemonHeldItemModifier[] = [];
if (this.runInfo.modifiers.length) {
for (const m of this.runInfo.modifiers) {
const modifier = m.toModifier(this.modifiersModule[m.className]);
if (modifier instanceof Modifier.PokemonHeldItemModifier && modifier.pokemonId === pokemon.id) {
modifier.stackCount = m["stackCount"];
heldItemsList.push(modifier);
}
}
if (heldItemsList.length > 0) {
(heldItemsList as Modifier.PokemonHeldItemModifier[]).sort(Modifier.modifierSortFunc);
let row = 0;
for (const [index, item] of heldItemsList.entries()) {
if (index > 36) {
const overflowIcon = addTextObject(182, 4, "+", TextStyle.WINDOW);
heldItemsContainer.add(overflowIcon);
break;
}
const itemIcon = item?.getIcon(true);
if (
item?.stackCount < item?.getMaxHeldItemCount(pokemon) &&
itemIcon.list[1] instanceof Phaser.GameObjects.BitmapText
) {
itemIcon.list[1].clearTint();
}
itemIcon.setScale(heldItemsScale);
itemIcon.setPosition((index % 19) * 10, row * 10);
heldItemsContainer.add(itemIcon);
if (index !== 0 && index % 18 === 0) {
row++;
}
}
}
}
heldItemsContainer.setName("heldItems");
heldItemsContainer.setVisible(false);
// Labels are applied for future differentiation in showParty()
pokemonInfoContainer.add(pokemonInfoWindow);
iconContainer.add(icon);
pokemonInfoContainer.add(iconContainer);
marksContainer.setName("PkmnMarks");
pokemonInfoContainer.add(marksContainer);
movesetContainer.setName("PkmnMoves");
pokemonInfoContainer.add(movesetContainer);
pokeInfoTextContainer.setName("PkmnInfoText");
pokemonInfoContainer.add(pokeInfoTextContainer);
pokeStatTextContainer.setName("PkmnStatsText");
pokemonInfoContainer.add(pokeStatTextContainer);
pokemonInfoContainer.add(heldItemsContainer);
pokemonInfoContainer.setName("PkmnInfo");
this.partyContainer.add(pokemonInfoContainer);
pokemon.destroy();
});
this.runContainer.add(this.partyContainer);
}
/**
* Changes what is displayed of the Pokemon's held items
* @param partyVisible {boolean}
* True -> Shows the Pokemon's default information and hides held items
* False -> Shows the Pokemon's held items and hides default information
*/
private showParty(partyVisible: boolean): void {
const allContainers = this.partyContainer.getAll("name", "PkmnInfo");
allContainers.forEach((c: Phaser.GameObjects.Container) => {
c.getByName<Phaser.GameObjects.Container>("PkmnMoves").setVisible(partyVisible);
c.getByName<Phaser.GameObjects.Container>("PkmnInfoText").setVisible(partyVisible);
c.getByName<Phaser.GameObjects.Container>("PkmnStatsText").setVisible(partyVisible);
c.getByName<Phaser.GameObjects.Container>("PkmnMarks").setVisible(partyVisible);
c.getByName<Phaser.GameObjects.Container>("heldItems").setVisible(!partyVisible);
this.partyVisibility = partyVisible;
});
}
/**
* Shows the ending art.
*/
private createVictorySplash(): void {
this.endCardContainer = globalScene.add.container(0, 0);
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
const isFemale = genderIndex === PlayerGender.FEMALE;
const endCard = globalScene.add.image(0, 0, `end_${isFemale ? "f" : "m"}`);
endCard.setOrigin(0);
endCard.setScale(0.5);
const text = addTextObject(
globalScene.game.canvas.width / 12,
globalScene.game.canvas.height / 6 - 16,
i18next.t("battle:congratulations"),
TextStyle.SUMMARY,
{ fontSize: "128px" },
);
text.setOrigin(0.5);
this.endCardContainer.add(endCard);
this.endCardContainer.add(text);
}
/** createHallofFame() - if the run is victorious, this creates a hall of fame image for the player to view
* Overlay created by Koda (Thank you!)
* This could be adapted into a public-facing method for victory screens. Perhaps.
*/
private createHallofFame(): void {
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
const isFemale = genderIndex === PlayerGender.FEMALE;
const genderStr = PlayerGender[genderIndex].toLowerCase();
// Issue Note (08-05-2024): It seems as if fused pokemon do not appear with the averaged color b/c pokemonData's loadAsset requires there to be some active battle?
// As an alternative, the icons of the second/bottom fused Pokemon have been placed next to their fellow fused Pokemon in Hall of Fame
this.hallofFameContainer = globalScene.add.container(0, 0);
// Thank you Hayuna for the code
const endCard = globalScene.add.image(0, 0, `end_${isFemale ? "f" : "m"}`);
endCard.setOrigin(0);
endCard.setPosition(-1, -1);
endCard.setScale(0.5);
const endCardCoords = endCard.getBottomCenter();
const overlayColor = isFemale ? "red" : "blue";
const hallofFameBg = globalScene.add.image(0, 0, "hall_of_fame_" + overlayColor);
hallofFameBg.setPosition(159, 89);
hallofFameBg.setSize(globalScene.game.canvas.width, globalScene.game.canvas.height + 10);
hallofFameBg.setAlpha(0.8);
this.hallofFameContainer.add(endCard);
this.hallofFameContainer.add(hallofFameBg);
const hallofFameText = addTextObject(
0,
0,
i18next.t("runHistory:hallofFameText", { context: genderStr }),
TextStyle.WINDOW,
);
hallofFameText.setPosition(endCardCoords.x - hallofFameText.displayWidth / 2, 164);
this.hallofFameContainer.add(hallofFameText);
this.runInfo.party.forEach((p, i) => {
const pkmn = p.toPokemon();
const row = i % 2;
const id = pkmn.id;
const shiny = pkmn.shiny;
const formIndex = pkmn.formIndex;
const variant = pkmn.variant;
const species = pkmn.getSpeciesForm();
const pokemonSprite: Phaser.GameObjects.Sprite = globalScene.add.sprite(60 + 40 * i, 40 + row * 80, "pkmn__sub");
pokemonSprite.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
ignoreTimeTint: true,
});
this.hallofFameContainer.add(pokemonSprite);
const speciesLoaded: Map<SpeciesId, boolean> = new Map<SpeciesId, boolean>();
speciesLoaded.set(id, false);
const female = pkmn.gender === 1;
species.loadAssets(female, formIndex, shiny, variant, true).then(() => {
speciesLoaded.set(id, true);
pokemonSprite.play(species.getSpriteKey(female, formIndex, shiny, variant));
pokemonSprite.setPipelineData("shiny", shiny);
pokemonSprite.setPipelineData("variant", variant);
pokemonSprite.setPipelineData("spriteKey", species.getSpriteKey(female, formIndex, shiny, variant));
pokemonSprite.setVisible(true);
});
if (pkmn.isFusion()) {
const fusionIcon = globalScene.add.sprite(80 + 40 * i, 50 + row * 80, pkmn.getFusionIconAtlasKey());
fusionIcon.setName("sprite-fusion-icon");
fusionIcon.setOrigin(0.5, 0);
fusionIcon.setFrame(pkmn.getFusionIconId(true));
this.hallofFameContainer.add(fusionIcon);
}
pkmn.destroy();
});
this.hallofFameContainer.setVisible(false);
this.runContainer.add(this.hallofFameContainer);
}
/**
* Takes input from the user to perform a desired action.
* @param button - Button object to be processed
* Button.CANCEL, Button.LEFT - removes all containers related to RunInfo and returns the user to Run History
* Button.CYCLE_FORM, Button.CYCLE_SHINY, Button.CYCLE_ABILITY - runs the function buttonCycleOption()
*/
override processInput(button: Button): boolean {
const ui = this.getUi();
let success = false;
const error = false;
switch (button) {
case Button.CANCEL:
case Button.LEFT:
success = true;
if (this.pageMode === RunInfoUiMode.MAIN) {
this.runInfoContainer.removeAll(true);
this.runResultContainer.removeAll(true);
this.partyContainer.removeAll(true);
this.runContainer.removeAll(true);
if (this.isVictory) {
this.hallofFameContainer.removeAll(true);
}
super.clear();
this.runContainer.setVisible(false);
ui.revertMode();
} else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) {
this.hallofFameContainer.setVisible(false);
this.pageMode = RunInfoUiMode.MAIN;
} else if (this.pageMode === RunInfoUiMode.ENDING_ART) {
this.endCardContainer.setVisible(false);
this.runContainer.remove(this.endCardContainer);
this.pageMode = RunInfoUiMode.MAIN;
}
break;
case Button.DOWN:
case Button.UP:
break;
case Button.CYCLE_FORM:
case Button.CYCLE_SHINY:
case Button.CYCLE_ABILITY:
this.buttonCycleOption(button);
break;
}
if (success) {
ui.playSelect();
} else if (error) {
ui.playError();
}
return success || error;
}
/**
* buttonCycleOption : takes a parameter button to execute different actions in the run-info page
* The use of non-directional / A / B buttons is named in relation to functions used during starter-select.
* Button.CYCLE_FORM (F key) --> displays ending art (victory only)
* Button.CYCLE_SHINY (R key) --> displays hall of fame (victory only)
* Button.CYCLE_ABILITY (E key) --> shows pokemon held items
*/
private buttonCycleOption(button: Button) {
switch (button) {
case Button.CYCLE_FORM:
if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) {
if (!this.endCardContainer || !this.endCardContainer.visible) {
this.createVictorySplash();
this.endCardContainer.setVisible(true);
this.runContainer.add(this.endCardContainer);
this.pageMode = RunInfoUiMode.ENDING_ART;
} else {
this.endCardContainer.setVisible(false);
this.runContainer.remove(this.endCardContainer);
this.pageMode = RunInfoUiMode.MAIN;
}
}
break;
case Button.CYCLE_SHINY:
if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) {
if (!this.hallofFameContainer.visible) {
this.hallofFameContainer.setVisible(true);
this.pageMode = RunInfoUiMode.HALL_OF_FAME;
} else {
this.hallofFameContainer.setVisible(false);
this.pageMode = RunInfoUiMode.MAIN;
}
}
break;
case Button.CYCLE_ABILITY:
if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) {
if (this.partyVisibility) {
this.showParty(false);
} else {
this.showParty(true);
}
}
break;
}
}
}