diff --git a/src/overrides.ts b/src/overrides.ts index 6b550d152c2..ec0577ceb3d 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -70,6 +70,8 @@ class DefaultOverrides { [PokeballType.MASTER_BALL]: 0, }, }; + /** Set to `true` to show all tutorials */ + readonly BYPASS_TUTORIAL_SKIP: boolean = false; // ---------------- // PLAYER OVERRIDES diff --git a/src/tutorial.ts b/src/tutorial.ts index c4482839779..18d8291d227 100644 --- a/src/tutorial.ts +++ b/src/tutorial.ts @@ -1,7 +1,9 @@ import BattleScene from "./battle-scene"; import AwaitableUiHandler from "./ui/awaitable-ui-handler"; +import UiHandler from "./ui/ui-handler"; import { Mode } from "./ui/ui"; import i18next from "i18next"; +import Overrides from "#app/overrides"; export enum Tutorial { Intro = "INTRO", @@ -39,7 +41,7 @@ const tutorialHandlers = { scene.ui.showText(i18next.t("tutorial:starterSelect"), null, () => scene.ui.showText("", null, () => resolve()), null, true); }); }, - [Tutorial.Pokerus]: (scene: BattleScene) => { + [Tutorial.Pokerus]: (scene: BattleScene) => { return new Promise(resolve => { scene.ui.showText(i18next.t("tutorial:pokerus"), null, () => scene.ui.showText("", null, () => resolve()), null, true); }); @@ -63,26 +65,87 @@ const tutorialHandlers = { }, }; -export function handleTutorial(scene: BattleScene, tutorial: Tutorial): Promise { - return new Promise(resolve => { - if (!scene.enableTutorials) { - return resolve(false); - } +/** + * Run through the specified tutorial if it hasn't been seen before and mark it as seen once done + * This will show a tutorial overlay if defined in the current {@linkcode AwaitableUiHandler} + * 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 { + if (!scene.enableTutorials && !Overrides.BYPASS_TUTORIAL_SKIP) { + return false; + } - if (scene.gameData.getTutorialFlags()[tutorial]) { - return resolve(false); - } + if (scene.gameData.getTutorialFlags()[tutorial] && !Overrides.BYPASS_TUTORIAL_SKIP) { + return false; + } - const handler = scene.ui.getHandler(); - if (handler instanceof AwaitableUiHandler) { - handler.tutorialActive = true; - } - tutorialHandlers[tutorial](scene).then(() => { - scene.gameData.saveTutorialFlag(tutorial, true); - if (handler instanceof AwaitableUiHandler) { - handler.tutorialActive = false; - } - resolve(true); - }); - }); + const handler = scene.ui.getHandler(); + const isMenuDisabled = scene.disableMenu; + + // starting tutorial, disable menu + scene.disableMenu = true; + if (handler instanceof AwaitableUiHandler) { + handler.tutorialActive = true; + } + + 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); + if (handler instanceof AwaitableUiHandler) { + handler.tutorialActive = false; + } + + 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; + } +} + diff --git a/src/ui/awaitable-ui-handler.ts b/src/ui/awaitable-ui-handler.ts index 2052c6e2ade..c6dc717aa3a 100644 --- a/src/ui/awaitable-ui-handler.ts +++ b/src/ui/awaitable-ui-handler.ts @@ -7,6 +7,7 @@ export default abstract class AwaitableUiHandler extends UiHandler { protected awaitingActionInput: boolean; protected onActionInput: Function | null; public tutorialActive: boolean = false; + public tutorialOverlay: Phaser.GameObjects.Rectangle; constructor(scene: BattleScene, mode: Mode | null = null) { super(scene, mode); @@ -24,4 +25,21 @@ export default abstract class AwaitableUiHandler extends UiHandler { 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); + } + } } diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 9a694d50b29..c27c6974192 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -83,12 +83,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { this.nameBoxContainer.add(this.nameText); messageContainer.add(this.nameBoxContainer); - const prompt = this.scene.add.sprite(0, 0, "prompt"); - prompt.setVisible(false); - prompt.setOrigin(0, 0); - messageContainer.add(prompt); - - this.prompt = prompt; + this.initPromptSprite(messageContainer); const levelUpStatsContainer = this.scene.add.container(0, 0); levelUpStatsContainer.setVisible(false); diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 9497dfe58c6..b109eda5370 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -287,7 +287,6 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.add(this.eggGachaSummaryContainer); const gachaMessageBoxContainer = this.scene.add.container(0, 148); - this.eggGachaContainer.add(gachaMessageBoxContainer); const gachaMessageBox = addWindow(this.scene, 0, 0, 320, 32); gachaMessageBox.setOrigin(0, 0); @@ -301,8 +300,11 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.message = gachaMessageText; + this.initTutorialOverlay(this.eggGachaContainer); this.eggGachaContainer.add(gachaMessageBoxContainer); + this.initPromptSprite(gachaMessageBoxContainer); + this.setCursor(0); } diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts index ffbd06afde3..76d148d083e 100644 --- a/src/ui/evolution-scene-handler.ts +++ b/src/ui/evolution-scene-handler.ts @@ -45,12 +45,7 @@ export default class EvolutionSceneHandler extends MessageUiHandler { this.message = message; - const prompt = this.scene.add.sprite(0, 0, "prompt"); - prompt.setVisible(false); - prompt.setOrigin(0, 0); - this.messageContainer.add(prompt); - - this.prompt = prompt; + this.initPromptSprite(this.messageContainer); } show(_args: any[]): boolean { diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index b8c3cfd1364..0af527e518f 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -157,6 +157,9 @@ export default class MenuUiHandler extends MessageUiHandler { menuMessageText.setOrigin(0, 0); this.menuMessageBoxContainer.add(menuMessageText); + this.initTutorialOverlay(this.menuContainer); + this.initPromptSprite(this.menuMessageBoxContainer); + this.message = menuMessageText; // 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"); + // 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); this.bgmBar.toggleBgmBar(true); diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index 93e00cb6b70..54965a590fc 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -17,6 +17,23 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { 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) { this.showTextInternal(text, delay, callback, callbackDelay, prompt, promptDelay); } @@ -180,7 +197,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { const lastLineWidth = lastLineTest.displayWidth; lastLineTest.destroy(); 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.pendingPrompt = false; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 308614887e9..0e101d30ce7 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -894,6 +894,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.message.setOrigin(0, 0); this.starterSelectMessageBoxContainer.add(this.message); + // arrow icon for the message box + this.initPromptSprite(this.starterSelectMessageBoxContainer); + this.statsContainer = new StatsContainer(this.scene, 6, 16); 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, }); this.starterSelectContainer.add(this.moveInfoOverlay); + + // Filter bar sits above everything, except the tutorial overlay and message box 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));