pokerogue/src/ui/mystery-encounter-ui-handler.ts

492 lines
17 KiB
TypeScript
Raw Normal View History

import BattleScene from "../battle-scene";
import { addBBCodeTextObject, getBBCodeFrag, TextStyle } from "./text";
import { Mode } from "./ui";
import UiHandler from "./ui-handler";
import { Button } from "#enums/buttons";
import { addWindow, WindowVariant } from "./ui-theme";
import { MysteryEncounterPhase } from "../phases/mystery-encounter-phase";
import { PartyUiMode } from "./party-ui-handler";
2024-07-09 23:19:44 -04:00
import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option";
import * as Utils from "../utils";
import { isNullOrUndefined } from "../utils";
import { getPokeballAtlasKey } from "../data/pokeball";
import { getEncounterText } from "#app/data/mystery-encounters/mystery-encounter-utils";
export default class MysteryEncounterUiHandler extends UiHandler {
private cursorContainer: Phaser.GameObjects.Container;
private cursorObj: Phaser.GameObjects.Image;
private optionsContainer: Phaser.GameObjects.Container;
private tooltipWindow: Phaser.GameObjects.NineSlice;
private tooltipContainer: Phaser.GameObjects.Container;
private tooltipScrollTween: Phaser.Tweens.Tween;
private descriptionWindow: Phaser.GameObjects.NineSlice;
private descriptionContainer: Phaser.GameObjects.Container;
private descriptionScrollTween: Phaser.Tweens.Tween;
private rarityBall: Phaser.GameObjects.Sprite;
private filteredEncounterOptions: MysteryEncounterOption[] = [];
private optionsMeetsReqs: boolean[];
protected viewPartyIndex: integer = 0;
protected blockInput: boolean = true;
constructor(scene: BattleScene) {
super(scene, Mode.MYSTERY_ENCOUNTER);
}
setup() {
const ui = this.getUi();
this.cursorContainer = this.scene.add.container(18, -38.7);
this.cursorContainer.setVisible(false);
ui.add(this.cursorContainer);
this.optionsContainer = this.scene.add.container(12, -38.7);
this.optionsContainer.setVisible(false);
ui.add(this.optionsContainer);
this.descriptionContainer = this.scene.add.container(0, -152);
this.descriptionContainer.setVisible(false);
ui.add(this.descriptionContainer);
this.tooltipContainer = this.scene.add.container(210, -48);
this.tooltipContainer.setVisible(false);
ui.add(this.tooltipContainer);
this.setCursor(this.getCursor());
this.descriptionWindow = addWindow(this.scene, 0, 0, 150, 105, false, false, 0, 0, WindowVariant.THIN);
this.descriptionContainer.add(this.descriptionWindow);
this.tooltipWindow = addWindow(this.scene, 0, 0, 110, 48, false, false, 0, 0, WindowVariant.THIN);
this.tooltipContainer.add(this.tooltipWindow);
this.rarityBall = this.scene.add.sprite(141, 9, "pb");
this.rarityBall.setScale(0.75);
this.descriptionContainer.add(this.rarityBall);
}
show(args: any[]): boolean {
super.show(args);
this.cursorContainer.setVisible(true);
this.descriptionContainer.setVisible(true);
this.optionsContainer.setVisible(true);
this.displayEncounterOptions(!(args[0] as boolean || false));
const cursor = this.getCursor();
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
// Always resets cursor on view party button if it was last there
this.setCursor(cursor);
} else {
this.setCursor(0);
}
if (this.blockInput) {
setTimeout(() => {
this.unblockInput();
}, 1500);
}
this.displayOptionTooltip();
return true;
}
processInput(button: Button): boolean {
const ui = this.getUi();
let success = false;
const cursor = this.getCursor();
if (button === Button.CANCEL || button === Button.ACTION) {
if (button === Button.ACTION) {
if (cursor === this.viewPartyIndex) {
// Handle view party
success = true;
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, true);
setTimeout(() => {
this.setCursor(this.viewPartyIndex);
this.unblockInput();
}, 300);
});
} else if (this.blockInput || !this.optionsMeetsReqs[cursor]) {
success = false;
} else {
const selected = this.filteredEncounterOptions[cursor];
if ((this.scene.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)) {
success = true;
} else {
ui.playError();
}
}
} else {
// TODO: If we need to handle cancel option? Maybe default logic to leave/run from encounter idk
}
} else {
switch (this.optionsContainer.length) {
case 3:
success = this.handleTwoOptionMoveInput(button);
break;
case 4:
success = this.handleThreeOptionMoveInput(button);
break;
case 5:
success = this.handleFourOptionMoveInput(button);
break;
}
this.displayOptionTooltip();
}
if (success) {
ui.playSelect();
}
return success;
}
handleTwoOptionMoveInput(button: Button): boolean {
let success = false;
const cursor = this.getCursor();
switch (button) {
case Button.UP:
if (cursor < this.viewPartyIndex) {
success = this.setCursor(this.viewPartyIndex);
}
break;
case Button.DOWN:
if (cursor === this.viewPartyIndex) {
success = this.setCursor(1);
}
break;
case Button.LEFT:
if (cursor > 0) {
success = this.setCursor(cursor - 1);
}
break;
case Button.RIGHT:
if (cursor < this.viewPartyIndex) {
success = this.setCursor(cursor + 1);
}
break;
}
return success;
}
handleThreeOptionMoveInput(button: Button): boolean {
let success = false;
const cursor = this.getCursor();
switch (button) {
case Button.UP:
if (cursor === 2) {
success = this.setCursor(cursor - 2);
} else {
success = this.setCursor(this.viewPartyIndex);
}
break;
case Button.DOWN:
if (cursor === this.viewPartyIndex) {
success = this.setCursor(1);
} else {
success = this.setCursor(2);
}
break;
case Button.LEFT:
if (cursor === this.viewPartyIndex) {
success = this.setCursor(1);
} else if (cursor === 1) {
success = this.setCursor(cursor - 1);
}
break;
case Button.RIGHT:
if (cursor === 1) {
success = this.setCursor(this.viewPartyIndex);
} else if (cursor < 1) {
success = this.setCursor(cursor + 1);
}
break;
}
return success;
}
handleFourOptionMoveInput(button: Button): boolean {
let success = false;
const cursor = this.getCursor();
switch (button) {
case Button.UP:
if (cursor >= 2 && cursor !== this.viewPartyIndex) {
success = this.setCursor(cursor - 2);
} else {
success = this.setCursor(this.viewPartyIndex);
}
break;
case Button.DOWN:
if (cursor <= 1) {
success = this.setCursor(cursor + 2);
} else if (cursor === this.viewPartyIndex) {
success = this.setCursor(1);
}
break;
case Button.LEFT:
if (cursor === this.viewPartyIndex) {
success = this.setCursor(1);
} else if (cursor % 2 === 1) {
success = this.setCursor(cursor - 1);
}
break;
case Button.RIGHT:
if (cursor === 1) {
success = this.setCursor(this.viewPartyIndex);
} else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) {
success = this.setCursor(cursor + 1);
}
break;
}
return success;
}
unblockInput() {
if (this.blockInput) {
this.blockInput = false;
for (let i = 0; i < this.optionsContainer.length - 1; i++) {
if (!this.optionsMeetsReqs[i]) {
continue;
}
(this.optionsContainer.getAt(i) as Phaser.GameObjects.Text).setAlpha(1);
}
}
}
getCursor(): integer {
return this.cursor ? this.cursor : 0;
}
setCursor(cursor: integer): boolean {
const prevCursor = this.getCursor();
const changed = prevCursor !== cursor;
if (changed) {
this.cursor = cursor;
}
this.viewPartyIndex = this.optionsContainer.length - 1;
if (!this.cursorObj) {
this.cursorObj = this.scene.add.image(0, 0, "cursor");
this.cursorContainer.add(this.cursorObj);
}
if (cursor === this.viewPartyIndex) {
this.cursorObj.setPosition(246, -17);
} else if (this.optionsContainer.length === 3) { // 2 Options
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 15);
} else if (this.optionsContainer.length === 4) { // 3 Options
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 7 + (cursor > 1 ? 16 : 0));
} else if (this.optionsContainer.length === 5) { // 4 Options
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 7 + (cursor > 1 ? 16 : 0));
}
return changed;
}
displayEncounterOptions(slideInDescription: boolean = true): void {
2024-07-05 18:09:32 -04:00
this.getUi().clearText();
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter;
this.filteredEncounterOptions = mysteryEncounter.options;
this.optionsMeetsReqs = [];
const titleText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
const descriptionText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.description, TextStyle.TOOLTIP_CONTENT);
const queryText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT);
// Clear options container (except cursor)
this.optionsContainer.removeAll();
// Options Window
for (let i = 0; i < this.filteredEncounterOptions.length; i++) {
let optionText;
switch (this.filteredEncounterOptions.length) {
case 2:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
break;
case 3:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
break;
case 4:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
break;
}
this.optionsMeetsReqs.push(this.filteredEncounterOptions[i].meetsRequirements(this.scene));
const optionDialogue = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i];
let text;
if (this.filteredEncounterOptions[i].hasRequirements() && this.optionsMeetsReqs[i]) {
// Options with special requirements that are met are automatically colored green
text = getEncounterText(this.scene, optionDialogue.buttonLabel, TextStyle.SUMMARY_GREEN);
} else {
text = getEncounterText(this.scene, optionDialogue.buttonLabel, optionDialogue.style ? optionDialogue.style : TextStyle.WINDOW);
}
if (text) {
optionText.setText(text);
}
if (!this.optionsMeetsReqs[i]) {
optionText.setAlpha(0.5);
}
if (this.blockInput) {
optionText.setAlpha(0.5);
}
this.optionsContainer.add(optionText);
}
// View Party Button
const viewPartyText = addBBCodeTextObject(this.scene, 256, -24, getBBCodeFrag("View Party", TextStyle.PARTY), TextStyle.PARTY);
this.optionsContainer.add(viewPartyText);
// Description Window
const titleTextObject = addBBCodeTextObject(this.scene, 0, 0, titleText, TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 });
this.descriptionContainer.add(titleTextObject);
titleTextObject.setPosition(72 - titleTextObject.displayWidth / 2, 5.5);
// Rarity of encounter
const ballType = getPokeballAtlasKey(mysteryEncounter.encounterTier as number);
this.rarityBall.setTexture("pb", ballType);
const descriptionTextObject = addBBCodeTextObject(this.scene, 6, 25, descriptionText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
// Sets up the mask that hides the description text to give an illusion of scrolling
const descriptionTextMaskRect = this.scene.make.graphics({});
descriptionTextMaskRect.setScale(6);
descriptionTextMaskRect.fillStyle(0xFFFFFF);
descriptionTextMaskRect.beginPath();
descriptionTextMaskRect.fillRect(6, 54, 206, 60);
const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask();
descriptionTextObject.setMask(abilityDescriptionTextMask);
const descriptionLineCount = Math.floor(descriptionTextObject.displayHeight / 10);
if (this.descriptionScrollTween) {
this.descriptionScrollTween.remove();
this.descriptionScrollTween = null;
}
// Animates the description text moving upwards
if (descriptionLineCount > 6) {
this.descriptionScrollTween = this.scene.tweens.add({
targets: descriptionTextObject,
delay: Utils.fixedInt(2000),
loop: -1,
hold: Utils.fixedInt(2000),
duration: Utils.fixedInt((descriptionLineCount - 6) * 2000),
y: `-=${10 * (descriptionLineCount - 6)}`
});
}
this.descriptionContainer.add(descriptionTextObject);
const queryTextObject = addBBCodeTextObject(this.scene, 0, 0, queryText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
this.descriptionContainer.add(queryTextObject);
queryTextObject.setPosition(75 - queryTextObject.displayWidth / 2, 90);
// Slide in description container
if (slideInDescription) {
this.descriptionContainer.x -= 150;
this.scene.tweens.add({
targets: this.descriptionContainer,
x: "+=150",
ease: "Sine.easeInOut",
duration: 1000
});
}
}
displayOptionTooltip() {
const cursor = this.getCursor();
// Clear tooltip box
if (this.tooltipContainer.length > 1) {
this.tooltipContainer.removeBetween(1, this.tooltipContainer.length, true);
}
this.tooltipContainer.setVisible(true);
2024-07-05 18:09:32 -04:00
if (isNullOrUndefined(cursor) || cursor > this.optionsContainer.length - 2) {
// Ignore hovers on view party button
return;
}
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter;
let text;
const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor];
if (!this.optionsMeetsReqs[cursor] && option.disabledTooltip) {
text = getEncounterText(this.scene, option.disabledTooltip, TextStyle.TOOLTIP_CONTENT);
} else {
text = getEncounterText(this.scene, option.buttonTooltip, TextStyle.TOOLTIP_CONTENT);
}
// Auto-color options green/blue for good/bad by looking for (+)/(-)
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0];
text = text.replace(/(\(\+\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) + "[/color][/shadow]" + primaryStyleString);
text = text.replace(/(\(\-\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) + "[/color][/shadow]" + primaryStyleString);
if (text) {
const tooltipTextObject = addBBCodeTextObject(this.scene, 6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" });
this.tooltipContainer.add(tooltipTextObject);
// Sets up the mask that hides the description text to give an illusion of scrolling
const tooltipTextMaskRect = this.scene.make.graphics({});
tooltipTextMaskRect.setScale(6);
tooltipTextMaskRect.fillStyle(0xFFFFFF);
tooltipTextMaskRect.beginPath();
tooltipTextMaskRect.fillRect(this.tooltipContainer.x, this.tooltipContainer.y + 188.5, 150, 32);
const textMask = tooltipTextMaskRect.createGeometryMask();
tooltipTextObject.setMask(textMask);
const tooltipLineCount = Math.floor(tooltipTextObject.displayHeight / 11.2);
if (this.tooltipScrollTween) {
this.tooltipScrollTween.remove();
this.tooltipScrollTween = null;
}
// Animates the tooltip text moving upwards
if (tooltipLineCount > 3) {
this.tooltipScrollTween = this.scene.tweens.add({
targets: tooltipTextObject,
delay: Utils.fixedInt(1200),
loop: -1,
hold: Utils.fixedInt(1200),
duration: Utils.fixedInt((tooltipLineCount - 3) * 1200),
y: `-=${11.2 * (tooltipLineCount - 3)}`
});
}
}
}
clear(): void {
super.clear();
this.optionsContainer.setVisible(false);
this.optionsContainer.removeAll(true);
this.descriptionContainer.setVisible(false);
this.tooltipContainer.setVisible(false);
// Keeps container background and pokeball
this.descriptionContainer.removeBetween(2, this.descriptionContainer.length, true);
this.getUi().getMessageHandler().clearText();
this.eraseCursor();
}
eraseCursor(): void {
if (this.cursorObj) {
this.cursorObj.destroy();
}
this.cursorObj = null;
}
}