[QoL] [ui] Make tutorials darken background (#4283)

* [ui] add prompt icon to the message boxes that don't have it

* [ui] add background overlay during tutorials

* add missing doc

* Improve documentation based on suggestions

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

---------

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
MokaStitcher 2024-09-16 21:19:34 +02:00 committed by GitHub
parent 4605ed4c4f
commit 128df1b6d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 140 additions and 35 deletions

View File

@ -70,6 +70,8 @@ class DefaultOverrides {
[PokeballType.MASTER_BALL]: 0, [PokeballType.MASTER_BALL]: 0,
}, },
}; };
/** Set to `true` to show all tutorials */
readonly BYPASS_TUTORIAL_SKIP: boolean = false;
// ---------------- // ----------------
// PLAYER OVERRIDES // PLAYER OVERRIDES

View File

@ -1,7 +1,9 @@
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import AwaitableUiHandler from "./ui/awaitable-ui-handler"; import AwaitableUiHandler from "./ui/awaitable-ui-handler";
import UiHandler from "./ui/ui-handler";
import { Mode } from "./ui/ui"; import { Mode } from "./ui/ui";
import i18next from "i18next"; import i18next from "i18next";
import Overrides from "#app/overrides";
export enum Tutorial { export enum Tutorial {
Intro = "INTRO", Intro = "INTRO",
@ -63,26 +65,87 @@ const tutorialHandlers = {
}, },
}; };
export function handleTutorial(scene: BattleScene, tutorial: Tutorial): Promise<boolean> { /**
return new Promise<boolean>(resolve => { * Run through the specified tutorial if it hasn't been seen before and mark it as seen once done
if (!scene.enableTutorials) { * This will show a tutorial overlay if defined in the current {@linkcode AwaitableUiHandler}
return resolve(false); * The main menu will also get disabled while the tutorial is running
* @param scene the current {@linkcode BattleScene}
* @param tutorial the {@linkcode Tutorial} to play
* @returns a promise with result `true` if the tutorial was run and finished, `false` otherwise
*/
export async function handleTutorial(scene: BattleScene, tutorial: Tutorial): Promise<boolean> {
if (!scene.enableTutorials && !Overrides.BYPASS_TUTORIAL_SKIP) {
return false;
} }
if (scene.gameData.getTutorialFlags()[tutorial]) { if (scene.gameData.getTutorialFlags()[tutorial] && !Overrides.BYPASS_TUTORIAL_SKIP) {
return resolve(false); return false;
} }
const handler = scene.ui.getHandler(); const handler = scene.ui.getHandler();
const isMenuDisabled = scene.disableMenu;
// starting tutorial, disable menu
scene.disableMenu = true;
if (handler instanceof AwaitableUiHandler) { if (handler instanceof AwaitableUiHandler) {
handler.tutorialActive = true; handler.tutorialActive = true;
} }
tutorialHandlers[tutorial](scene).then(() => {
await showTutorialOverlay(scene, handler);
await tutorialHandlers[tutorial](scene);
await hideTutorialOverlay(scene, handler);
// tutorial finished and overlay gone, re-enable menu, save tutorial as seen
scene.disableMenu = isMenuDisabled;
scene.gameData.saveTutorialFlag(tutorial, true); scene.gameData.saveTutorialFlag(tutorial, true);
if (handler instanceof AwaitableUiHandler) { if (handler instanceof AwaitableUiHandler) {
handler.tutorialActive = false; handler.tutorialActive = false;
} }
resolve(true);
}); return true;
});
} }
/**
* Show the tutorial overlay if there is one
* @param scene the current BattleScene
* @param handler the current UiHandler
* @returns `true` once the overlay has finished appearing, or if there is no overlay
*/
async function showTutorialOverlay(scene: BattleScene, handler: UiHandler) {
if (handler instanceof AwaitableUiHandler && handler.tutorialOverlay) {
scene.tweens.add({
targets: handler.tutorialOverlay,
alpha: 0.5,
duration: 750,
ease: "Sine.easeOut",
onComplete: () => {
return true;
}
});
} else {
return true;
}
}
/**
* Hide the tutorial overlay if there is one
* @param scene the current BattleScene
* @param handler the current UiHandler
* @returns `true` once the overlay has finished disappearing, or if there is no overlay
*/
async function hideTutorialOverlay(scene: BattleScene, handler: UiHandler) {
if (handler instanceof AwaitableUiHandler && handler.tutorialOverlay) {
scene.tweens.add({
targets: handler.tutorialOverlay,
alpha: 0,
duration: 500,
ease: "Sine.easeOut",
onComplete: () => {
return true;
}
});
} else {
return true;
}
}

View File

@ -7,6 +7,7 @@ export default abstract class AwaitableUiHandler extends UiHandler {
protected awaitingActionInput: boolean; protected awaitingActionInput: boolean;
protected onActionInput: Function | null; protected onActionInput: Function | null;
public tutorialActive: boolean = false; public tutorialActive: boolean = false;
public tutorialOverlay: Phaser.GameObjects.Rectangle;
constructor(scene: BattleScene, mode: Mode | null = null) { constructor(scene: BattleScene, mode: Mode | null = null) {
super(scene, mode); super(scene, mode);
@ -24,4 +25,21 @@ export default abstract class AwaitableUiHandler extends UiHandler {
return false; return false;
} }
/**
* Create a semi transparent overlay that will get shown during tutorials
* @param container the container to add the overlay to
*/
initTutorialOverlay(container: Phaser.GameObjects.Container) {
if (!this.tutorialOverlay) {
this.tutorialOverlay = new Phaser.GameObjects.Rectangle(this.scene, -1, -1, this.scene.scaledCanvas.width, this.scene.scaledCanvas.height, 0x070707);
this.tutorialOverlay.setName("tutorial-overlay");
this.tutorialOverlay.setOrigin(0, 0);
this.tutorialOverlay.setAlpha(0);
}
if (container) {
container.add(this.tutorialOverlay);
}
}
} }

View File

@ -83,12 +83,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
this.nameBoxContainer.add(this.nameText); this.nameBoxContainer.add(this.nameText);
messageContainer.add(this.nameBoxContainer); messageContainer.add(this.nameBoxContainer);
const prompt = this.scene.add.sprite(0, 0, "prompt"); this.initPromptSprite(messageContainer);
prompt.setVisible(false);
prompt.setOrigin(0, 0);
messageContainer.add(prompt);
this.prompt = prompt;
const levelUpStatsContainer = this.scene.add.container(0, 0); const levelUpStatsContainer = this.scene.add.container(0, 0);
levelUpStatsContainer.setVisible(false); levelUpStatsContainer.setVisible(false);

View File

@ -287,7 +287,6 @@ export default class EggGachaUiHandler extends MessageUiHandler {
this.eggGachaContainer.add(this.eggGachaSummaryContainer); this.eggGachaContainer.add(this.eggGachaSummaryContainer);
const gachaMessageBoxContainer = this.scene.add.container(0, 148); const gachaMessageBoxContainer = this.scene.add.container(0, 148);
this.eggGachaContainer.add(gachaMessageBoxContainer);
const gachaMessageBox = addWindow(this.scene, 0, 0, 320, 32); const gachaMessageBox = addWindow(this.scene, 0, 0, 320, 32);
gachaMessageBox.setOrigin(0, 0); gachaMessageBox.setOrigin(0, 0);
@ -301,8 +300,11 @@ export default class EggGachaUiHandler extends MessageUiHandler {
this.message = gachaMessageText; this.message = gachaMessageText;
this.initTutorialOverlay(this.eggGachaContainer);
this.eggGachaContainer.add(gachaMessageBoxContainer); this.eggGachaContainer.add(gachaMessageBoxContainer);
this.initPromptSprite(gachaMessageBoxContainer);
this.setCursor(0); this.setCursor(0);
} }

View File

@ -45,12 +45,7 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
this.message = message; this.message = message;
const prompt = this.scene.add.sprite(0, 0, "prompt"); this.initPromptSprite(this.messageContainer);
prompt.setVisible(false);
prompt.setOrigin(0, 0);
this.messageContainer.add(prompt);
this.prompt = prompt;
} }
show(_args: any[]): boolean { show(_args: any[]): boolean {

View File

@ -157,6 +157,9 @@ export default class MenuUiHandler extends MessageUiHandler {
menuMessageText.setOrigin(0, 0); menuMessageText.setOrigin(0, 0);
this.menuMessageBoxContainer.add(menuMessageText); this.menuMessageBoxContainer.add(menuMessageText);
this.initTutorialOverlay(this.menuContainer);
this.initPromptSprite(this.menuMessageBoxContainer);
this.message = menuMessageText; this.message = menuMessageText;
// By default we use the general purpose message window // By default we use the general purpose message window
@ -433,6 +436,9 @@ export default class MenuUiHandler extends MessageUiHandler {
this.scene.playSound("ui/menu_open"); this.scene.playSound("ui/menu_open");
// Make sure the tutorial overlay sits above everything, but below the message box
this.menuContainer.bringToTop(this.tutorialOverlay);
this.menuContainer.bringToTop(this.menuMessageBoxContainer);
handleTutorial(this.scene, Tutorial.Menu); handleTutorial(this.scene, Tutorial.Menu);
this.bgmBar.toggleBgmBar(true); this.bgmBar.toggleBgmBar(true);

View File

@ -17,6 +17,23 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
this.pendingPrompt = false; this.pendingPrompt = false;
} }
/**
* Add the sprite to be displayed at the end of messages with prompts
* @param container the container to add the sprite to
*/
initPromptSprite(container: Phaser.GameObjects.Container) {
if (!this.prompt) {
const promptSprite = this.scene.add.sprite(0, 0, "prompt");
promptSprite.setVisible(false);
promptSprite.setOrigin(0, 0);
this.prompt = promptSprite;
}
if (container) {
container.add(this.prompt);
}
}
showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { 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); this.showTextInternal(text, delay, callback, callbackDelay, prompt, promptDelay);
} }
@ -180,7 +197,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
const lastLineWidth = lastLineTest.displayWidth; const lastLineWidth = lastLineTest.displayWidth;
lastLineTest.destroy(); lastLineTest.destroy();
if (this.prompt) { if (this.prompt) {
this.prompt.setPosition(lastLineWidth + 2, (textLinesCount - 1) * 18 + 2); this.prompt.setPosition(this.message.x + lastLineWidth + 2, this.message.y + (textLinesCount - 1) * 18 + 2);
this.prompt.play("prompt"); this.prompt.play("prompt");
} }
this.pendingPrompt = false; this.pendingPrompt = false;

View File

@ -894,6 +894,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.message.setOrigin(0, 0); this.message.setOrigin(0, 0);
this.starterSelectMessageBoxContainer.add(this.message); this.starterSelectMessageBoxContainer.add(this.message);
// arrow icon for the message box
this.initPromptSprite(this.starterSelectMessageBoxContainer);
this.statsContainer = new StatsContainer(this.scene, 6, 16); this.statsContainer = new StatsContainer(this.scene, 6, 16);
this.scene.add.existing(this.statsContainer); this.scene.add.existing(this.statsContainer);
@ -911,7 +914,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29,
}); });
this.starterSelectContainer.add(this.moveInfoOverlay); this.starterSelectContainer.add(this.moveInfoOverlay);
// Filter bar sits above everything, except the tutorial overlay and message box
this.starterSelectContainer.bringToTop(this.filterBarContainer); this.starterSelectContainer.bringToTop(this.filterBarContainer);
this.initTutorialOverlay(this.starterSelectContainer);
this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer);
this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e)); this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e));