diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 87fb65878e..e6283e4bc6 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -1,6 +1,8 @@ import SoundAssets from '../assets/SoundAssets'; import { ItemId } from '../commons/CommonTypes'; import { promptWithChoices } from '../effects/Prompt'; +import { keyboardShortcuts } from '../input/GameInputConstants'; +import GameInputManager from '../input/GameInputManager'; import { Layer } from '../layer/GameLayerTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import SourceAcademyGame from '../SourceAcademyGame'; @@ -14,10 +16,14 @@ import DialogueSpeakerRenderer from './GameDialogueSpeakerRenderer'; * It displays the lines, speakers, and performs actions * whenever players click on the dialogue box */ + export default class DialogueManager { private speakerRenderer?: DialogueSpeakerRenderer; private dialogueRenderer?: DialogueRenderer; private dialogueGenerator?: DialogueGenerator; + private gameInputManager?: GameInputManager = new GameInputManager( + GameGlobalAPI.getInstance().getGameManager() + ); /** * @param dialogueId the dialogue Id of the dialogue you want to play @@ -44,9 +50,20 @@ export default class DialogueManager { private async playWholeDialogue(resolve: () => void) { await this.showNextLine(resolve); + // add keyboard listener for dialogue box + this.getInputManager().registerKeyboardListener(keyboardShortcuts.Next, 'up', async () => { + // show the next line if dashboard or escape menu are not displayed + if ( + !GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal() + ) { + await this.showNextLine(resolve); + } + }); this.getDialogueRenderer() .getDialogueBox() - .on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, async () => await this.showNextLine(resolve)); + .on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, async () => { + await this.showNextLine(resolve); + }); } private async showNextLine(resolve: () => void) { @@ -62,23 +79,33 @@ export default class DialogueManager { // Disable interactions while processing actions GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), false); + if (prompt) { + // disable keyboard input to prevent continue dialogue + this.getInputManager().enableKeyboardInput(false); const response = await promptWithChoices( GameGlobalAPI.getInstance().getGameManager(), prompt.promptTitle, prompt.choices.map(choice => choice[0]) ); + + this.getInputManager().enableKeyboardInput(true); this.getDialogueGenerator().updateCurrPart(prompt.choices[response][1]); } await GameGlobalAPI.getInstance().processGameActionsInSamePhase(actionIds); GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), true); - if (!line) resolve(); + if (!line) { + // clear keyboard listeners when dialogue ends + this.getInputManager().clearKeyboardListeners([keyboardShortcuts.Next]); + resolve(); + } } private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; + private getInputManager = () => this.gameInputManager as GameInputManager; public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; } diff --git a/src/features/game/effects/Notification.ts b/src/features/game/effects/Notification.ts index 7a9557cab6..eae51d4a10 100644 --- a/src/features/game/effects/Notification.ts +++ b/src/features/game/effects/Notification.ts @@ -1,11 +1,12 @@ -import { Layer } from 'src/features/game/layer/GameLayerTypes'; - import FontAssets from '../assets/FontAssets'; import SoundAssets from '../assets/SoundAssets'; import { Constants, screenCenter } from '../commons/CommonConstants'; import { BitmapFontStyle, IBaseScene } from '../commons/CommonTypes'; import dialogueConstants from '../dialogue/GameDialogueConstants'; import DialogueRenderer from '../dialogue/GameDialogueRenderer'; +import { keyboardShortcuts } from '../input/GameInputConstants'; +import GameInputManager from '../input/GameInputManager'; +import { Layer } from '../layer/GameLayerTypes'; import SourceAcademyGame from '../SourceAcademyGame'; import { sleep } from '../utils/GameUtils'; import { createBitmapText } from '../utils/TextUtils'; @@ -47,11 +48,24 @@ export async function displayNotification(scene: IBaseScene, message: string): P // Wait for fade in to finish await sleep(Constants.fadeDuration * 2); + const gameInputManager = new GameInputManager(scene); + + const dissolveNotification = () => { + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); + fadeAndDestroy(scene, notifText, { fadeDuration: Constants.fadeDuration / 4 }); + dialogueRenderer.destroy(); + }; + const showNotification = new Promise(resolve => { + // using the same binding as dialogue shortcut + gameInputManager.registerKeyboardListener(keyboardShortcuts.Notif, 'up', async () => { + gameInputManager.clearKeyboardListeners([keyboardShortcuts.Notif]); + dissolveNotification(); + resolve(); + }); + dialogueRenderer.getDialogueBox().on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, () => { - SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); - fadeAndDestroy(scene, notifText, { fadeDuration: Constants.fadeDuration / 4 }); - dialogueRenderer.destroy(); + dissolveNotification(); resolve(); }); }); diff --git a/src/features/game/input/GameInputConstants.ts b/src/features/game/input/GameInputConstants.ts new file mode 100644 index 0000000000..8ea777584d --- /dev/null +++ b/src/features/game/input/GameInputConstants.ts @@ -0,0 +1,15 @@ +export const keyboardShortcuts = { + Dashboard: Phaser.Input.Keyboard.KeyCodes.TAB, + Menu: Phaser.Input.Keyboard.KeyCodes.ESC, + Next: Phaser.Input.Keyboard.KeyCodes.SPACE, + Notif: Phaser.Input.Keyboard.KeyCodes.SPACE, + Explore: Phaser.Input.Keyboard.KeyCodes.E, + Move: Phaser.Input.Keyboard.KeyCodes.V, + Talk: Phaser.Input.Keyboard.KeyCodes.T, + Options: [ + Phaser.Input.Keyboard.KeyCodes.ONE, + Phaser.Input.Keyboard.KeyCodes.TWO, + Phaser.Input.Keyboard.KeyCodes.THREE, + Phaser.Input.Keyboard.KeyCodes.FOUR + ] +}; diff --git a/src/features/game/input/GameInputManager.ts b/src/features/game/input/GameInputManager.ts index db7fe499a5..2ab78d792a 100644 --- a/src/features/game/input/GameInputManager.ts +++ b/src/features/game/input/GameInputManager.ts @@ -81,6 +81,17 @@ class GameInputManager { this.keyboardListeners.forEach(keyboardListener => keyboardListener.removeAllListeners()); this.eventListeners.forEach(eventListener => eventListener.removeAllListeners()); } + + /** + * Clear specific keyboard listeners. + */ + public clearKeyboardListeners(keycodes: number[]) { + this.keyboardListeners.forEach(keyboardListener => { + if (keycodes.includes(keyboardListener.keyCode)) { + keyboardListener.removeAllListeners(); + } + }); + } } export default GameInputManager; diff --git a/src/features/game/mode/menu/GameModeMenu.ts b/src/features/game/mode/menu/GameModeMenu.ts index fb262207d9..74aec648aa 100644 --- a/src/features/game/mode/menu/GameModeMenu.ts +++ b/src/features/game/mode/menu/GameModeMenu.ts @@ -11,7 +11,7 @@ import { createButton } from '../../utils/ButtonUtils'; import { sleep } from '../../utils/GameUtils'; import { calcTableFormatPos } from '../../utils/StyleUtils'; import { GameMode, gameModeToPhase } from '../GameModeTypes'; -import MenuModeConstants, { modeButtonStyle } from './GameModeMenuConstants'; +import MenuModeConstants, { MenuLineConstants, modeButtonStyle } from './GameModeMenuConstants'; /** * The class in charge of showing the "Menu" mode UI @@ -63,6 +63,14 @@ class GameModeMenu implements IGameUI { numOfItems: buttons.length }); + const lineList: Phaser.GameObjects.Line[] = buttons.map((button, index) => + this.createLine( + buttonPositions[index][0], + buttonPositions[index][1] + MenuModeConstants.button.yOffset + MenuLineConstants.yOffset, + button + ) + ); + modeMenuContainer.add( buttons.map((button, index) => this.createModeButton( @@ -73,9 +81,44 @@ class GameModeMenu implements IGameUI { ) ) ); + + modeMenuContainer.add(lineList); + return modeMenuContainer; } + /** + * Create underline for each button. + */ + private createLine( + xPos: number, + yPos: number, + button: { + text: GameMode; + callback: () => Promise; + } + ) { + const gameManager = GameGlobalAPI.getInstance().getGameManager(); + if (button.text === GameMode.Explore) { + xPos += MenuLineConstants.exploreOffset; + } else if (button.text === GameMode.Move) { + xPos += MenuLineConstants.moveOffset; + } else { + xPos += MenuLineConstants.talkOffset; + } + const line: Phaser.GameObjects.Line = gameManager.add.line( + MenuLineConstants.x, + MenuLineConstants.y, + xPos, + yPos, + xPos + MenuLineConstants.lineLength, + yPos, + MenuLineConstants.color + ); + line.setLineWidth(MenuLineConstants.lineWidth); + return line; + } + /** * Get the mode buttons preset to be formatted later. * The preset includes the text to be displayed on the button and diff --git a/src/features/game/mode/menu/GameModeMenuConstants.ts b/src/features/game/mode/menu/GameModeMenuConstants.ts index 4e09b82f8f..66c2cc7426 100644 --- a/src/features/game/mode/menu/GameModeMenuConstants.ts +++ b/src/features/game/mode/menu/GameModeMenuConstants.ts @@ -23,3 +23,15 @@ const MenuModeConstants = { }; export default MenuModeConstants; + +export const MenuLineConstants = { + x: 0, + y: 15, + lineLength: 20, + lineWidth: 4, + exploreOffset: -68, + moveOffset: 20, + talkOffset: -30, + yOffset: 20, + color: 0xbce7da +}; diff --git a/src/features/game/mode/move/GameModeMove.ts b/src/features/game/mode/move/GameModeMove.ts index 686a0693e4..6ff324a4da 100644 --- a/src/features/game/mode/move/GameModeMove.ts +++ b/src/features/game/mode/move/GameModeMove.ts @@ -3,13 +3,15 @@ import SoundAssets from '../../assets/SoundAssets'; import CommonBackButton from '../../commons/CommonBackButton'; import { screenCenter, screenSize } from '../../commons/CommonConstants'; import { IGameUI } from '../../commons/CommonTypes'; +import { ItemId } from '../../commons/CommonTypes'; import { fadeAndDestroy } from '../../effects/FadeEffect'; import { entryTweenProps, exitTweenProps } from '../../effects/FlyEffect'; +import { keyboardShortcuts } from '../../input/GameInputConstants'; import { Layer } from '../../layer/GameLayerTypes'; import { GameItemType, LocationId } from '../../location/GameMapTypes'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; import GameGlobalAPI from '../../scenes/gameManager/GameGlobalAPI'; -import { createButton } from '../../utils/ButtonUtils'; +import { createButton, createButtonText } from '../../utils/ButtonUtils'; import { sleep } from '../../utils/GameUtils'; import { calcTableFormatPos } from '../../utils/StyleUtils'; import MoveModeConstants, { moveButtonStyle } from './GameModeMoveConstants'; @@ -38,7 +40,7 @@ class GameModeMove implements IGameUI { /** * Fetches the navigations of the current location id. */ - private getLatestNavigations() { + private getLatestNavigations(): ItemId[] { return GameGlobalAPI.getInstance().getGameItemsInLocation( GameItemType.navigation, GameGlobalAPI.getInstance().getCurrLocId() @@ -79,16 +81,16 @@ class GameModeMove implements IGameUI { }); moveMenuContainer.add( - buttons.map((button, index) => - this.createMoveButton( - button.text, + buttons.map((button, index) => { + return this.createMoveButton( + createButtonText(index + 1, button.text), buttonPositions[index][0] + MoveModeConstants.button.xOffSet, buttonPositions[index][1], button.callback, button.onHover, button.onOut - ) - ) + ); + }) ); const backButton = new CommonBackButton( @@ -161,6 +163,22 @@ class GameModeMove implements IGameUI { }).setPosition(xPos, yPos); } + /** + * Register keyboard listeners for location selection. + * Called by activateUI function. + */ + private registerKeyboardListener(): void { + const inputManager = GameGlobalAPI.getInstance().getGameManager().getInputManager(); + const navList: string[] = this.getLatestNavigations(); + + navList.forEach((nav, index) => { + inputManager.registerKeyboardListener(keyboardShortcuts.Options[index], 'up', async () => { + await GameGlobalAPI.getInstance().swapPhase(GamePhaseType.Sequence); + await GameGlobalAPI.getInstance().changeLocationTo(nav); + }); + }); + } + /** * Activate the 'Move' mode UI. * @@ -172,8 +190,9 @@ class GameModeMove implements IGameUI { this.uiContainer = this.createUIContainer(); GameGlobalAPI.getInstance().addToLayer(Layer.UI, this.uiContainer); - this.uiContainer.setPosition(this.uiContainer.x, -screenSize.y); + this.registerKeyboardListener(); + this.uiContainer.setPosition(this.uiContainer.x, -screenSize.y); gameManager.tweens.add({ targets: this.uiContainer, ...entryTweenProps @@ -181,6 +200,15 @@ class GameModeMove implements IGameUI { GameGlobalAPI.getInstance().playSound(SoundAssets.modeEnter.key); } + /** + * Remove keyboard listners for location selection + * when Move mode is transitioned out. + */ + private removeKeyboardListener(): void { + const inputManager = GameGlobalAPI.getInstance().getGameManager().getInputManager(); + inputManager.clearKeyboardListeners(keyboardShortcuts.Options); + } + /** * Deactivate the 'Move' mode UI. * @@ -189,7 +217,7 @@ class GameModeMove implements IGameUI { */ public async deactivateUI(): Promise { const gameManager = GameGlobalAPI.getInstance().getGameManager(); - + this.removeKeyboardListener(); if (this.uiContainer) { this.uiContainer.setPosition(this.uiContainer.x, 0); diff --git a/src/features/game/mode/talk/GameModeTalk.ts b/src/features/game/mode/talk/GameModeTalk.ts index 66f98fccdd..82772c4bf6 100644 --- a/src/features/game/mode/talk/GameModeTalk.ts +++ b/src/features/game/mode/talk/GameModeTalk.ts @@ -1,5 +1,3 @@ -import GameGlobalAPI from 'src/features/game/scenes/gameManager/GameGlobalAPI'; - import ImageAssets from '../../assets/ImageAssets'; import SoundAssets from '../../assets/SoundAssets'; import CommonBackButton from '../../commons/CommonBackButton'; @@ -7,10 +5,12 @@ import { screenSize } from '../../commons/CommonConstants'; import { IGameUI, ItemId } from '../../commons/CommonTypes'; import { fadeAndDestroy } from '../../effects/FadeEffect'; import { entryTweenProps, exitTweenProps } from '../../effects/FlyEffect'; +import { keyboardShortcuts } from '../../input/GameInputConstants'; import { Layer } from '../../layer/GameLayerTypes'; import { GameItemType } from '../../location/GameMapTypes'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; -import { createButton } from '../../utils/ButtonUtils'; +import GameGlobalAPI from '../../scenes/gameManager/GameGlobalAPI'; +import { createButton, createButtonText } from '../../utils/ButtonUtils'; import { mandatory, sleep } from '../../utils/GameUtils'; import { calcTableFormatPos, Direction } from '../../utils/StyleUtils'; import TalkModeConstants, { talkButtonStyle } from './GameModeTalkConstants'; @@ -54,14 +54,13 @@ class GameModeTalk implements IGameUI { talkMenuContainer.add( buttons.map((button, index) => this.createTalkTopicButton( - button.text, + createButtonText(index + 1, button.text), buttonPositions[index][0], buttonPositions[index][1], button.callback ) ) ); - // Add check for interacted talk topics buttons.forEach((button, index) => { const checkedSprite = new Phaser.GameObjects.Sprite( @@ -129,6 +128,21 @@ class GameModeTalk implements IGameUI { }).setPosition(xPos, yPos); } + /** + * Register keyboard listners for talk topic selection. + * Called by the activeUI function. + */ + private registerKeyboardListener(): void { + const talkTopics: ItemId[] = this.getLatestTalkTopics(); + const inputManager = GameGlobalAPI.getInstance().getGameManager().getInputManager(); + talkTopics.forEach((dialogueId: ItemId, index) => { + inputManager.registerKeyboardListener(keyboardShortcuts.Options[index], 'up', async () => { + GameGlobalAPI.getInstance().triggerInteraction(dialogueId); + await GameGlobalAPI.getInstance().showDialogue(dialogueId); + }); + }); + } + /** * Activate the 'Talk' mode UI. * @@ -140,6 +154,8 @@ class GameModeTalk implements IGameUI { this.uiContainer = this.createUIContainer(); GameGlobalAPI.getInstance().addToLayer(Layer.UI, this.uiContainer); + this.registerKeyboardListener(); + this.uiContainer.setPosition(this.uiContainer.x, -screenSize.y); gameManager.tweens.add({ @@ -149,6 +165,15 @@ class GameModeTalk implements IGameUI { GameGlobalAPI.getInstance().playSound(SoundAssets.modeEnter.key); } + /** + * Remove keyboard listners for topic selection. + * Called by the deactiveUI function. + */ + private removeKeyboardListener(): void { + const inputManager = GameGlobalAPI.getInstance().getGameManager().getInputManager(); + inputManager.clearKeyboardListeners(keyboardShortcuts.Options); + } + /** * Deactivate the 'Talk' mode UI. * @@ -157,6 +182,7 @@ class GameModeTalk implements IGameUI { */ public async deactivateUI(): Promise { const gameManager = GameGlobalAPI.getInstance().getGameManager(); + this.removeKeyboardListener(); if (this.uiContainer) { this.uiContainer.setPosition(this.uiContainer.x, 0); diff --git a/src/features/game/scenes/bindings/Bindings.ts b/src/features/game/scenes/bindings/Bindings.ts index f955cfcf19..ae69b18f2e 100644 --- a/src/features/game/scenes/bindings/Bindings.ts +++ b/src/features/game/scenes/bindings/Bindings.ts @@ -67,14 +67,18 @@ class Bindings extends Phaser.Scene { }); bindingsContainer.add( - bindings.map((binding, index) => - this.createBinding( + bindings.map((binding, index) => { + return this.createBinding( binding.key, binding.text, - bindingPositions[index][0], - bindingPositions[index][1] + BindingConstants.key.yStart - ) - ) + bindingPositions[index][1] < 450 + ? bindingPositions[index][0] - BindingConstants.key.xOffset + : bindingPositions[index][0] + BindingConstants.key.xOffset, + bindingPositions[index][1] < 450 + ? bindingPositions[index][1] + BindingConstants.key.yOffset1 + : bindingPositions[index][1] + BindingConstants.key.yOffset2 + ); + }) ); const backButton = new CommonBackButton(this, () => { this.getLayerManager().clearAllLayers(); @@ -97,6 +101,26 @@ class Bindings extends Phaser.Scene { { key: 'Tab', text: 'Dashboard' + }, + { + key: 'Space', + text: 'Next dialogue' + }, + { + key: 'E', + text: 'Explore mode' + }, + { + key: 'V', + text: 'Move mode' + }, + { + key: 'T', + text: 'Talk mode' + }, + { + key: '', + text: ' Select topics/locations' } ]; } @@ -127,6 +151,20 @@ class Bindings extends Phaser.Scene { default: break; } + switch (key) { + case 'Space': + keyIcon.setTexture(ImageAssets.medKeyboardIcon.key); + break; + default: + break; + } + switch (key) { + case '': + keyIcon.setTexture(ImageAssets.medKeyboardIcon.key); + break; + default: + break; + } const keyText = createBitmapText(this, key, BindingConstants.keyTextConfig, keyStyle); const keyDesc = createBitmapText(this, desc, BindingConstants.keyDescTextConfig, keyDescStyle); diff --git a/src/features/game/scenes/bindings/BindingsConstants.ts b/src/features/game/scenes/bindings/BindingsConstants.ts index 62a0a62929..573a43f79d 100644 --- a/src/features/game/scenes/bindings/BindingsConstants.ts +++ b/src/features/game/scenes/bindings/BindingsConstants.ts @@ -1,11 +1,16 @@ import FontAssets from '../../assets/FontAssets'; -import { screenCenter } from '../../commons/CommonConstants'; +import { screenCenter, screenSize } from '../../commons/CommonConstants'; import { BitmapFontStyle } from '../../commons/CommonTypes'; export const BindingConstants = { keyTextConfig: { x: screenCenter.x - 100, y: 0, oriX: 0.5, oriY: 0.5 }, keyDescTextConfig: { x: screenCenter.x + 100, y: 0, oriX: 0.5, oriY: 0.5 }, - key: { yStart: screenCenter.y * 0.9, yInterval: 150 }, + key: { + xOffset: screenSize.x / 5, + yOffset1: screenCenter.y - screenSize.y / 4, + yOffset2: screenCenter.y - screenSize.y / 1.5, + yInterval: 150 + }, icon: { x: screenCenter.x - 100 } }; diff --git a/src/features/game/scenes/gameManager/GameManager.ts b/src/features/game/scenes/gameManager/GameManager.ts index a02542df79..125e077fd1 100644 --- a/src/features/game/scenes/gameManager/GameManager.ts +++ b/src/features/game/scenes/gameManager/GameManager.ts @@ -15,11 +15,14 @@ import GameDialogueStorageManager from '../../dialogue/GameDialogueStorageManage import { blackFade, blackScreen, fadeIn } from '../../effects/FadeEffect'; import { addLoadingScreen } from '../../effects/LoadingScreen'; import GameEscapeManager from '../../escape/GameEscapeManager'; +import { keyboardShortcuts } from '../../input/GameInputConstants'; import GameInputManager from '../../input/GameInputManager'; import GameLayerManager from '../../layer/GameLayerManager'; import { Layer } from '../../layer/GameLayerTypes'; import { LocationId } from '../../location/GameMapTypes'; +import { GameItemType } from '../../location/GameMapTypes'; import GameLogManager from '../../log/GameLogManager'; +import { GameMode } from '../../mode/GameModeTypes'; import GameObjectManager from '../../objects/GameObjectManager'; import GamePhaseManager from '../../phase/GamePhaseManager'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; @@ -261,33 +264,65 @@ class GameManager extends Phaser.Scene { } /** - * Bind escape menu and dashboard to keyboard triggers. + * Bind escape menu, dashboard, and mode selections to keyboard triggers. */ private bindKeyboardTriggers() { - this.getInputManager().registerKeyboardListener( - Phaser.Input.Keyboard.KeyCodes.ESC, - 'up', - async () => { - if (this.getPhaseManager().isCurrentPhaseTerminal()) { - await this.getPhaseManager().popPhase(); - } else { - await this.getPhaseManager().pushPhase(GamePhaseType.EscapeMenu); - } + this.getInputManager().registerKeyboardListener(keyboardShortcuts.Menu, 'up', async () => { + if (this.getPhaseManager().isCurrentPhaseTerminal()) { + await this.getPhaseManager().popPhase(); + } else { + await this.getPhaseManager().pushPhase(GamePhaseType.EscapeMenu); } + }); + this.getInputManager().registerKeyboardListener(keyboardShortcuts.Dashboard, 'up', async () => { + if (this.getPhaseManager().isCurrentPhase(GamePhaseType.Dashboard)) { + await this.getPhaseManager().popPhase(); + } else if (this.getPhaseManager().isCurrentPhaseTerminal()) { + await this.getPhaseManager().swapPhase(GamePhaseType.Dashboard); + } else { + await this.getPhaseManager().pushPhase(GamePhaseType.Dashboard); + } + }); + this.registerMenuKeyboardListener( + keyboardShortcuts.Explore, + GameMode.Explore, + GamePhaseType.Explore ); - this.getInputManager().registerKeyboardListener( - Phaser.Input.Keyboard.KeyCodes.TAB, - 'up', - async () => { - if (this.getPhaseManager().isCurrentPhase(GamePhaseType.Dashboard)) { - await this.getPhaseManager().popPhase(); - } else if (this.getPhaseManager().isCurrentPhaseTerminal()) { - await this.getPhaseManager().swapPhase(GamePhaseType.Dashboard); - } else { - await this.getPhaseManager().pushPhase(GamePhaseType.Dashboard); - } + this.registerMenuKeyboardListener(keyboardShortcuts.Move, GameMode.Move, GamePhaseType.Move); + this.registerMenuKeyboardListener(keyboardShortcuts.Talk, GameMode.Talk, GamePhaseType.Talk); + } + + /** + * Helper function to register keyboard listeners for mode selections. + */ + private registerMenuKeyboardListener(shortcut: number, mode: GameMode, phase: GamePhaseType) { + this.getInputManager().registerKeyboardListener(shortcut, 'up', async () => { + const modes = this.getCurrentLocationModes(); + if (modes.includes(mode) && this.getPhaseManager().isCurrentPhase(GamePhaseType.Menu)) { + await this.getPhaseManager().pushPhase(phase); + } else if (this.getPhaseManager().isCurrentPhase(phase)) { + await this.getPhaseManager().swapPhase(GamePhaseType.Menu); } + }); + } + + /** + * the same method from GameModeMenu to get the available modes under current location + */ + private getCurrentLocationModes() { + const currLocId = this.currentLocationId; + let latestModesInLoc = this.getStateManager().getLocationModes(currLocId); + const talkTopics = GameGlobalAPI.getInstance().getGameItemsInLocation( + GameItemType.talkTopics, + currLocId ); + + // Remove talk mode if there is no talk topics + if (talkTopics.length === 0) { + latestModesInLoc = latestModesInLoc.filter(mode => mode !== GameMode.Talk); + } + + return latestModesInLoc; } /** diff --git a/src/features/game/utils/ButtonUtils.ts b/src/features/game/utils/ButtonUtils.ts index e3f8cb361d..a4cd911b24 100644 --- a/src/features/game/utils/ButtonUtils.ts +++ b/src/features/game/utils/ButtonUtils.ts @@ -22,6 +22,15 @@ type ButtonConfig = { const onHoverAlpha = 1.0; const offHoverAlpha = 0.9; +/** + * Create an index for the button + * @param index: index of the button + * @param text: text of the button + */ +export function createButtonText(index: number, text: string): string { + return '[ ' + index + ' ] ' + text; +} + /** * Create a button with basic functionalities. *