pokerogue/src/ui/challenges-select-ui-handler.ts

399 lines
15 KiB
TypeScript

import BattleScene from "../battle-scene";
import { TextStyle, addTextObject } from "./text";
import { Mode } from "./ui";
import UiHandler from "./ui-handler";
import { addWindow } from "./ui-theme";
import {Button} from "#enums/buttons";
import i18next from "i18next";
import { SelectStarterPhase, TitlePhase } from "#app/phases.js";
import { Challenge } from "#app/data/challenge.js";
import * as Utils from "../utils";
import { Challenges } from "#app/enums/challenges.js";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { Color, ShadowColor } from "#app/enums/color.js";
/**
* Handles all the UI for choosing optional challenges.
*/
export default class GameChallengesUiHandler extends UiHandler {
private challengesContainer: Phaser.GameObjects.Container;
private valuesContainer: Phaser.GameObjects.Container;
private scrollCursor: integer;
private optionsBg: Phaser.GameObjects.NineSlice;
// private difficultyText: Phaser.GameObjects.Text;
private descriptionText: BBCodeText;
private challengeLabels: Array<{ label: Phaser.GameObjects.Text, value: Phaser.GameObjects.Text }>;
private monoTypeValue: Phaser.GameObjects.Sprite;
private cursorObj: Phaser.GameObjects.NineSlice | null;
private startCursor: Phaser.GameObjects.NineSlice;
constructor(scene: BattleScene, mode: Mode | null = null) {
super(scene, mode);
}
setup() {
const ui = this.getUi();
this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1);
this.challengesContainer.setName("challenges");
this.challengesContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
const bgOverlay = this.scene.add.rectangle(-1, -1, this.scene.scaledCanvas.width, this.scene.scaledCanvas.height, 0x424242, 0.8);
bgOverlay.setName("rect-challenge-overlay");
bgOverlay.setOrigin(0, 0);
this.challengesContainer.add(bgOverlay);
// TODO: Change this back to /9 when adding in difficulty
const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6), 24);
headerBg.setName("window-header-bg");
headerBg.setOrigin(0, 0);
const headerText = addTextObject(this.scene, 0, 0, i18next.t("challenges:title"), TextStyle.SETTINGS_LABEL);
headerText.setName("text-header");
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
// const difficultyBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 18) - 2, 24);
// difficultyBg.setOrigin(0, 0);
// difficultyBg.setPositionRelative(headerBg, headerBg.width, 0);
// this.difficultyText = addTextObject(this.scene, 0, 0, "0", TextStyle.SETTINGS_LABEL);
// this.difficultyText.setOrigin(0, 0);
// this.difficultyText.setPositionRelative(difficultyBg, 8, 4);
// const difficultyName = addTextObject(this.scene, 0, 0, i18next.t("challenges:points"), TextStyle.SETTINGS_LABEL);
// difficultyName.setOrigin(0, 0);
// difficultyName.setPositionRelative(difficultyBg, difficultyBg.width - difficultyName.displayWidth - 8, 4);
this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 9), (this.scene.game.canvas.height / 6) - headerBg.height - 2);
this.optionsBg.setName("window-options-bg");
this.optionsBg.setOrigin(0, 0);
const descriptionBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 18) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 26);
descriptionBg.setName("window-desc-bg");
descriptionBg.setOrigin(0, 0);
descriptionBg.setPositionRelative(this.optionsBg, this.optionsBg.width, 0);
this.descriptionText = new BBCodeText(this.scene, descriptionBg.x + 6, descriptionBg.y + 4, "", {
fontFamily: "emerald",
fontSize: 96,
color: Color.ORANGE,
padding: {
bottom: 6
},
wrap: {
mode: "word",
width: (descriptionBg.width - 12) * 6,
}
});
this.descriptionText.setName("text-desc");
this.scene.add.existing(this.descriptionText);
this.descriptionText.setScale(1/6);
this.descriptionText.setShadow(4, 5, ShadowColor.ORANGE);
this.descriptionText.setOrigin(0, 0);
const startBg = addWindow(this.scene, 0, 0, descriptionBg.width, 24);
startBg.setName("window-start-bg");
startBg.setOrigin(0, 0);
startBg.setPositionRelative(descriptionBg, 0, descriptionBg.height);
const startText = addTextObject(this.scene, 0, 0, i18next.t("common:start"), TextStyle.SETTINGS_LABEL);
startText.setName("text-start");
startText.setOrigin(0, 0);
startText.setPositionRelative(startBg, 8, 4);
this.startCursor = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, (this.scene.game.canvas.width / 18) - 10, 16, 1, 1, 1, 1);
this.startCursor.setName("9s-start-cursor");
this.startCursor.setOrigin(0, 0);
this.startCursor.setPositionRelative(startBg, 4, 4);
this.startCursor.setVisible(false);
this.valuesContainer = this.scene.add.container(0, 0);
this.valuesContainer.setName("values");
this.challengeLabels = [];
for (let i = 0; i < 9; i++) {
const label = addTextObject(this.scene, 8, 28 + i * 16, "", TextStyle.SETTINGS_LABEL);
label.setName(`text-challenge-label-${i}`);
label.setOrigin(0, 0);
this.valuesContainer.add(label);
const value = addTextObject(this.scene, 0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL);
value.setName(`challenge-value-text-${i}`);
value.setPositionRelative(label, 100, 0);
this.valuesContainer.add(value);
this.challengeLabels[i] = {
label: label,
value: value
};
}
this.monoTypeValue = this.scene.add.sprite(8, 98, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`);
this.monoTypeValue.setName("challenge-value-monotype-sprite");
this.monoTypeValue.setScale(0.86);
this.monoTypeValue.setVisible(false);
this.valuesContainer.add(this.monoTypeValue);
this.challengesContainer.add(headerBg);
this.challengesContainer.add(headerText);
// this.challengesContainer.add(difficultyBg);
// this.challengesContainer.add(this.difficultyText);
// this.challengesContainer.add(difficultyName);
this.challengesContainer.add(this.optionsBg);
this.challengesContainer.add(descriptionBg);
this.challengesContainer.add(this.descriptionText);
this.challengesContainer.add(startBg);
this.challengesContainer.add(startText);
this.challengesContainer.add(this.startCursor);
this.challengesContainer.add(this.valuesContainer);
ui.add(this.challengesContainer);
this.setCursor(0);
this.setScrollCursor(0);
this.challengesContainer.setVisible(false);
}
/**
* Adds the default text color to the description text
* @param text text to set to the BBCode description
*/
setDescription(text: string): void {
this.descriptionText.setText(`[color=${Color.ORANGE}][shadow=${ShadowColor.ORANGE}]${text}`);
}
/**
* initLabels
* init all challenge labels
*/
initLabels(): void {
this.setDescription(this.scene.gameMode.challenges[0].getDescription());
for (let i = 0; i < 9; i++) {
if (i < this.scene.gameMode.challenges.length) {
this.challengeLabels[i].label.setVisible(true);
this.challengeLabels[i].value.setVisible(true);
}
}
}
/**
* update the text the cursor is on
*/
updateText(): void {
this.setDescription(this.getActiveChallenge().getDescription());
let monoTypeVisible = false;
for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); i++) {
const challenge = this.scene.gameMode.challenges[this.scrollCursor + i];
this.challengeLabels[i].label.setText(challenge.getName());
if (challenge.id === Challenges.SINGLE_TYPE) {
this.monoTypeValue.setPositionRelative(this.challengeLabels[i].label, 113, 8);
this.monoTypeValue.setFrame(challenge.getValue());
this.monoTypeValue.setVisible(true);
this.challengeLabels[i].value.setVisible(false);
monoTypeVisible = true;
} else {
this.challengeLabels[i].value.setText(challenge.getValue());
this.challengeLabels[i].value.setVisible(true);
}
}
if (!monoTypeVisible) {
this.monoTypeValue.setVisible(false);
}
// const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0);
// const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0);
// this.difficultyText.text = `${totalDifficulty}` + (totalMinDifficulty ? `/${totalMinDifficulty}` : "");
// this.difficultyText.updateText();
}
show(args: any[]): boolean {
super.show(args);
this.startCursor.setVisible(false);
this.challengesContainer.setVisible(true);
this.setCursor(0);
this.initLabels();
this.updateText();
this.getUi().moveTo(this.challengesContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
return true;
}
/**
* Processes input from a specified button.
* This method handles navigation through a UI menu, including movement through menu items
* and handling special actions like cancellation. Each button press may adjust the cursor
* position or the menu scroll, and plays a sound effect if the action was successful.
*
* @param button - The button pressed by the user.
* @returns `true` if the action associated with the button was successfully processed, `false` otherwise.
*/
processInput(button: Button): boolean {
const ui = this.getUi();
// Defines the maximum number of rows that can be displayed on the screen.
const rowsToDisplay = 9;
let success = false;
if (button === Button.CANCEL) {
if (this.startCursor.visible) {
this.startCursor.setVisible(false);
this.cursorObj?.setVisible(true);
} else {
this.scene.clearPhaseQueue();
this.scene.pushPhase(new TitlePhase(this.scene));
this.scene.getCurrentPhase()?.end();
}
success = true;
} else if (button === Button.SUBMIT || button === Button.ACTION) {
if (this.startCursor.visible) {
const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0);
const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0);
if (totalDifficulty >= totalMinDifficulty) {
this.scene.unshiftPhase(new SelectStarterPhase(this.scene));
this.scene.getCurrentPhase()?.end();
success = true;
} else {
success = false;
}
} else {
this.startCursor.setVisible(true);
this.cursorObj?.setVisible(false);
success = true;
}
} else {
switch (button) {
case Button.UP:
if (this.cursor === 0) {
if (this.scrollCursor === 0) {
// When at the top of the menu and pressing UP, move to the bottommost item.
if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom
// First, set the cursor to the last visible element, preparing for the scroll to the end.
const successA = this.setCursor(rowsToDisplay - 1);
// Then, adjust the scroll to display the bottommost elements of the menu.
const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay);
success = successA && successB; // success is just there to play the little validation sound effect
} else { // If there are 9 or less challenges, just move to the bottom one
success = this.setCursor(this.scene.gameMode.challenges.length - 1);
}
} else {
success = this.setScrollCursor(this.scrollCursor - 1);
}
} else {
success = this.setCursor(this.cursor - 1);
}
if (success) {
this.updateText();
}
break;
case Button.DOWN:
if (this.cursor === rowsToDisplay - 1) {
if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) {
// When at the bottom and pressing DOWN, scroll if possible.
success = this.setScrollCursor(this.scrollCursor + 1);
} else {
// When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item.
// First, set the cursor to the first visible element, preparing for the scroll to the top.
const successA = this.setCursor(0);
// Then, adjust the scroll to display the topmost elements of the menu.
const successB = this.setScrollCursor(0);
success = successA && successB; // success is just there to play the little validation sound effect
}
} else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) {
// When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item.
success = this.setCursor(0);
} else {
success = this.setCursor(this.cursor + 1);
}
if (success) {
this.updateText();
}
break;
case Button.LEFT:
// Moves the option cursor left, if possible.
success = this.getActiveChallenge().decreaseValue();
if (success) {
this.updateText();
}
break;
case Button.RIGHT:
// Moves the option cursor right, if possible.
success = this.getActiveChallenge().increaseValue();
if (success) {
this.updateText();
}
break;
}
}
// Plays a select sound effect if an action was successfully processed.
if (success) {
ui.playSelect();
}
return success;
}
setCursor(cursor: integer): boolean {
let ret = super.setCursor(cursor);
if (!this.cursorObj) {
this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, (this.scene.game.canvas.width / 9) - 10, 16, 1, 1, 1, 1);
this.cursorObj.setOrigin(0, 0);
this.valuesContainer.add(this.cursorObj);
}
ret ||= !this.cursorObj.visible;
this.cursorObj.setVisible(true);
this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16);
return ret;
}
setScrollCursor(scrollCursor: integer): boolean {
if (scrollCursor === this.scrollCursor) {
return false;
}
this.scrollCursor = scrollCursor;
this.setCursor(this.cursor);
return true;
}
getActiveChallenge(): Challenge {
return this.scene.gameMode.challenges[this.cursor + this.scrollCursor];
}
clear() {
super.clear();
this.challengesContainer.setVisible(false);
this.eraseCursor();
}
eraseCursor() {
if (this.cursorObj) {
this.cursorObj.destroy();
}
this.cursorObj = null;
}
}