pokerogue/src/ui/message-ui-handler.ts
ImperialSympathizer acb2b66be4
[Feature] Add Mystery Encounters to the game (#3938)
* add .github/workflows/mystery-event.yml

* update mystery-event.yml

* mystery encounters: resolve review comments:

Lost at Sea:
-fix typo in handlePokemonGuidingYouPhase function

Mysterious Chest:
- remove obsolete commented code

mystery-encounter.ts
- remove unused `onDone` field from MysteryEncounterBuilder

* fix typo in CanLearnMoveRequirementOptions

* remove redundance from Pokemon.isAllowedInBattle()

* chore: jsdoc formatting

* fix lost-at-sea tests

* add fallback for biomeMysteryEncounters if empty

* lost-at-sea-encounter: fix and extend tests

* move "battle:fainted" into `koPlayerPokemon`

* add retries to quick-draw tests

* fix lost-at-sea-encounter tests

* clean up battle animation logic

* Update and rename mystery-event.yml to mystery-events.yml

* Update mystery-events.yml

* Fix typo

* Update mystery-events.yml

Fix debug runs

* clean up unit tests and utils

* attach github issues to all encounter jsdocs

* start dialogue refactor

* update sleeping snorlax encounter

* migrate encounters dialogue to new format

* cleanup and add jsdocs

* finish fiery fallout encounter

* fix unit test breaks

* add skeleton tests to fiery fallout

* commit latest test changes

* finish unit tests for fiery fallout

* bug fix for empty modifier shop

* stash working changes

* stash changes

* Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/data/battle-anims.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* nit updates and cleanup

* Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* add jsdocs and more cleanup

* add more jsdoc

* add the strong stuff encounter

* add the strong stuff encounter and more unit tests

* cleanup container length checks in ME ui

* add retries to tests

* add retries to tests

* fix trainer wave disable override

* add shuckle juice modifier

* add dialogue bug fixes

* add dialogue bug fixes

* add pokemon salesman encounter and affects pokedex UI display

* add unit tests for pokemon salesman

* temp stash

* add offer you can't refuse

* add unit tests for offer you can't refuse encounter

* remove unnecessary prompt handlers

* add tests for disabled encounter options

* add delibird-y encounter

* add delibird-y encounter

* add absolute avarice encounter

* finish absolute avarice encounter

* add unit tests and enhancements for item overrides in tests

* fix unit test

* cleanup absolute avarice PR

* small bug fixes with latest sync from main

* update visuals loading for safari and stat trainer visuals

* update visuals loading for safari and stat trainer visuals

* update a trainer's test encounter and add unit tests

* add Trash to Treasure encounter

* clean up trash to treasure encounter

* clean up trash to treasure encounter

* add berries abound encounter

* start clowning around encounter

* first implementation pass at clowning around

* add unit tests for clowning around

* add unit tests for clowning around

* clean up ME unit tests

* clean up unit tests

* update unit tests

* add part timer and dancing lessons encounters

* add unit tests for Dancing Lessons and Part-Timer

* reordered biome list and adjusted redirection for project and labels

* Add Weird Dream encounter and slight reworks to Berries Abound/Fight or Flight

* adjusting yml to match new labels

* fix yml whoopsie

* Expanded 'Weird Dream' banlist and fixed a bug with the BST bump range

* adds Winstrate Challenge mystery encounter

* small cleanup for winstrates

* add unit tests for Winstrate Challenge

* fix pokemon not returning after winstrate battle

* commit latest beta merge updates

* fix ME null checks and unit tests with beta update

* fix ME null checks and unit tests with beta update

* MEs to pokerogue beta branch

* test dialogue changes

* test patch fix

* test patch fix

* test patch fix

* adds teleporting hijinks encounter

* add unit tests for Teleporting Hijinks

* small change to teleporting hijinks dialogue

* migrate ME translations to json

* add retries to berries-abound.Option1: should reward the player with X berries based on wave

* add missing ME dialogue back in

* revert template changes

* add ME unique trainer dialogue to both dialogue jsons

* fix hanging comma in json

* fix broken imports

* resolve lint issues

* fix flaky test

* balance tweaks to a few MEs, updates to bug superfan

* add unit tests for Bug-Type Superfan and clean up dialogue

* Adds Fun and Games mystery encounter

* add unit tests for Fun and Games encounter

* update jsdoc

* small ME balance changes

* small ME balance changes

* Adds Uncommon Breed ME and misc. ME bug fixes

* Update getFinalSessionData() to collect Mystery Encounter data

* adds GTS encounter

* various ME bug fixes and balance changes

* latest ME bug fixes

* clean up GTS Encounter and add unit tests

* small cleanup to MEs branch

* add BGM music names for ME music

* bug fixes and balance changes for MEs

* ME data schema updates

* balance changes and bug fixes to MEs

* balance changes and bug fixes to MEs

* update tests for MEs

* add jsdoc to party exp function

* dialogue updates and test fixes for MEs

* dialogue updates and test fixes for MEs

* PR suggestions and fixees

* stash PR feedback and bugfixes

* fix all tests for MEs and cleanup

* PR feedback

* update flaky ME test

* update tests, bug fix MEs, and sprite assets

* remove unintentional console log

* re-enable stubbed function for Phaser text styling

* handle undefined introVisuals properly

* PR feedback from NightKev

* disable Uncommon Breed tests

* locales updates and bug fixes for safari zone

* more PR feedback and update field trip with Rarer Candy

* fix unit test

* Change how reroll button gets disabled in Modifier Shop Phase

* update continue button text logic

* Update src/ui/modifier-select-ui-handler.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* fix money formatting and some nits

* more nits

* more nits

* update ME tsdocs with links

* update ME tsdocs with links

---------

Co-authored-by: Felix Staud <felix.staud@headwire.com>
Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
Co-authored-by: ImperialSympathizer <imperialsympathizer@gmail.com>
Co-authored-by: InnocentGameDev <asdargmng@gmail.com>
Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
2024-09-14 03:05:58 +01:00

218 lines
7.3 KiB
TypeScript

import BattleScene from "../battle-scene";
import AwaitableUiHandler from "./awaitable-ui-handler";
import { Mode } from "./ui";
import * as Utils from "../utils";
export default abstract class MessageUiHandler extends AwaitableUiHandler {
protected textTimer: Phaser.Time.TimerEvent | null;
protected textCallbackTimer: Phaser.Time.TimerEvent | null;
public pendingPrompt: boolean;
public message: Phaser.GameObjects.Text;
public prompt: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, mode: Mode | null = null) {
super(scene, mode);
this.pendingPrompt = false;
}
showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) {
this.showTextInternal(text, delay, callback, callbackDelay, prompt, promptDelay);
}
showDialogue(text: string, name?: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) {
this.showTextInternal(text, delay, callback, callbackDelay, prompt, promptDelay);
}
private showTextInternal(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) {
if (delay === null || delay === undefined) {
delay = 20;
}
// Pattern matching regex that checks for @c{}, @f{}, @s{}, and @f{} patterns within message text and parses them to their respective behaviors.
const charVarMap = new Map<integer, string>();
const delayMap = new Map<integer, integer>();
const soundMap = new Map<integer, string>();
const fadeMap = new Map<integer, integer>();
const actionPattern = /@(c|d|s|f)\{(.*?)\}/;
let actionMatch: RegExpExecArray | null;
while ((actionMatch = actionPattern.exec(text))) {
switch (actionMatch[1]) {
case "c":
charVarMap.set(actionMatch.index, actionMatch[2]);
break;
case "d":
delayMap.set(actionMatch.index, parseInt(actionMatch[2]));
break;
case "s":
soundMap.set(actionMatch.index, actionMatch[2]);
break;
case "f":
fadeMap.set(actionMatch.index, parseInt(actionMatch[2]));
break;
}
text = text.slice(0, actionMatch.index) + text.slice(actionMatch.index + actionMatch[2].length + 4);
}
if (text) {
// Predetermine overflow line breaks to avoid words breaking while displaying
const textWords = text.split(" ");
let lastLineCount = 1;
let newText = "";
for (let w = 0; w < textWords.length; w++) {
const nextWordText = newText ? `${newText} ${textWords[w]}` : textWords[w];
if (textWords[w].includes("\n")) {
newText = nextWordText;
lastLineCount++;
} else {
const lineCount = this.message.runWordWrap(nextWordText).split(/\n/g).length;
if (lineCount > lastLineCount) {
lastLineCount = lineCount;
newText = `${newText}\n${textWords[w]}`;
} else {
newText = nextWordText;
}
}
}
text = newText;
}
if (this.textTimer) {
this.textTimer.remove();
if (this.textCallbackTimer) {
this.textCallbackTimer.callback();
}
}
if (prompt) {
const originalCallback = callback;
callback = () => {
const showPrompt = () => this.showPrompt(originalCallback, callbackDelay);
if (promptDelay) {
this.scene.time.delayedCall(promptDelay, showPrompt);
} else {
showPrompt();
}
};
}
if (delay) {
this.clearText();
if (prompt) {
this.pendingPrompt = true;
}
this.textTimer = this.scene.time.addEvent({
delay: delay,
callback: () => {
const charIndex = text.length - (this.textTimer?.repeatCount!); // TODO: is this bang correct?
const charVar = charVarMap.get(charIndex);
const charSound = soundMap.get(charIndex);
const charDelay = delayMap.get(charIndex);
const charFade = fadeMap.get(charIndex);
this.message.setText(text.slice(0, charIndex));
const advance = () => {
if (charVar) {
this.scene.charSprite.setVariant(charVar);
}
if (charSound) {
this.scene.playSound(charSound);
}
if (callback && !this.textTimer?.repeatCount) {
if (callbackDelay && !prompt) {
this.textCallbackTimer = this.scene.time.delayedCall(callbackDelay, () => {
if (this.textCallbackTimer) {
this.textCallbackTimer.destroy();
this.textCallbackTimer = null;
}
callback();
});
} else {
callback();
}
}
};
if (charDelay) {
this.textTimer!.paused = true; // TODO: is the bang correct?
this.scene.tweens.addCounter({
duration: Utils.getFrameMs(charDelay),
onComplete: () => {
this.textTimer!.paused = false; // TODO: is the bang correct?
advance();
}
});
} else if (charFade) {
this.textTimer!.paused = true;
this.scene.time.delayedCall(150, () => {
this.scene.ui.fadeOut(750).then(() => {
const delay = Utils.getFrameMs(charFade);
this.scene.time.delayedCall(delay, () => {
this.scene.ui.fadeIn(500).then(() => {
this.textTimer!.paused = false;
advance();
});
});
});
});
} else {
advance();
}
},
repeat: text.length
});
} else {
this.message.setText(text);
if (prompt) {
this.pendingPrompt = true;
}
if (callback) {
callback();
}
}
}
showPrompt(callback?: Function | null, callbackDelay?: integer | null) {
const wrappedTextLines = this.message.runWordWrap(this.message.text).split(/\n/g);
const textLinesCount = wrappedTextLines.length;
const lastTextLine = wrappedTextLines[wrappedTextLines.length - 1];
const lastLineTest = this.scene.add.text(0, 0, lastTextLine, { font: "96px emerald" });
lastLineTest.setScale(this.message.scale);
const lastLineWidth = lastLineTest.displayWidth;
lastLineTest.destroy();
if (this.prompt) {
this.prompt.setPosition(lastLineWidth + 2, (textLinesCount - 1) * 18 + 2);
this.prompt.play("prompt");
}
this.pendingPrompt = false;
this.awaitingActionInput = true;
this.onActionInput = () => {
if (this.prompt) {
this.prompt.anims.stop();
this.prompt.setVisible(false);
}
if (callback) {
if (callbackDelay) {
this.textCallbackTimer = this.scene.time.delayedCall(callbackDelay, () => {
if (this.textCallbackTimer) {
this.textCallbackTimer.destroy();
this.textCallbackTimer = null;
}
callback();
});
} else {
callback();
}
}
};
}
clearText() {
this.message.setText("");
this.pendingPrompt = false;
}
clear() {
super.clear();
}
}