diff --git a/resources/lang/en.json b/resources/lang/en.json index f0ad48559..fdb3e53b5 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -97,6 +97,8 @@ "action_build": "Open build menu", "action_emote": "Open emote menu", "action_center": "Center camera on player", + "action_pause_game": "Pause / Resume game", + "action_game_speed": "Game speed down / up (single player)", "action_zoom": "Zoom out/in", "action_move_camera": "Move camera", "action_ratio_change": "Decrease/Increase attack ratio", @@ -571,6 +573,12 @@ "build_menu_modifier_desc": "Hold this key while clicking to open the build menu.", "emoji_menu_modifier": "Emoji Menu Modifier", "emoji_menu_modifier_desc": "Hold this key while clicking to open the emoji menu.", + "pause_game": "Pause", + "pause_game_desc": "Pause or resume the game (single player and custom games for host).", + "game_speed_up": "Game Speed Up", + "game_speed_up_desc": "Cycle to next game speed (0.5, 1, 2, max). Single player only.", + "game_speed_down": "Game Speed Down", + "game_speed_down_desc": "Cycle to previous game speed. Single player only.", "attack_ratio_controls": "Attack Ratio Controls", "attack_ratio_up": "Increase Attack Ratio", "attack_ratio_up_desc": "Increase attack ratio by {amount}%", diff --git a/src/client/HelpModal.ts b/src/client/HelpModal.ts index c98ce7705..d70accd7e 100644 --- a/src/client/HelpModal.ts +++ b/src/client/HelpModal.ts @@ -58,6 +58,9 @@ export class HelpModal extends BaseModal { modifierKey: isMac ? "MetaLeft" : "ControlLeft", altKey: "AltLeft", resetGfx: "KeyR", + pauseGame: "KeyP", + gameSpeedUp: "Period", + gameSpeedDown: "Comma", ...saved, }; } @@ -81,6 +84,8 @@ export class HelpModal extends BaseModal { ArrowDown: "↓", ArrowLeft: "←", ArrowRight: "→", + Period: ">", + Comma: "<", }; if (specialLabels[code]) return specialLabels[code]; @@ -372,6 +377,25 @@ export class HelpModal extends BaseModal { ${translateText("help_modal.action_center")} + + + ${this.renderKey(keybinds.pauseGame)} + + + ${translateText("help_modal.action_pause_game")} + + + + +
+ ${this.renderKey(keybinds.gameSpeedDown)} + ${this.renderKey(keybinds.gameSpeedUp)} +
+ + + ${translateText("help_modal.action_game_speed")} + +
diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index 778d284c4..e42d62241 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -123,6 +123,12 @@ export class ReplaySpeedChangeEvent implements GameEvent { constructor(public readonly replaySpeedMultiplier: ReplaySpeedMultiplier) {} } +export class TogglePauseIntentEvent implements GameEvent {} + +export class GameSpeedUpIntentEvent implements GameEvent {} + +export class GameSpeedDownIntentEvent implements GameEvent {} + export class CenterCameraEvent implements GameEvent { constructor() {} } @@ -236,6 +242,9 @@ export class InputHandler { buildAtomBomb: "Digit8", buildHydrogenBomb: "Digit9", buildMIRV: "Digit0", + pauseGame: "KeyP", + gameSpeedUp: "Period", + gameSpeedDown: "Comma", ...saved, }; @@ -433,8 +442,20 @@ export class InputHandler { this.eventBus.emit(new SwapRocketDirectionEvent(nextDirection)); } + if (!e.repeat && e.code === this.keybinds.pauseGame) { + e.preventDefault(); + this.eventBus.emit(new TogglePauseIntentEvent()); + } + if (!e.repeat && e.code === this.keybinds.gameSpeedUp) { + e.preventDefault(); + this.eventBus.emit(new GameSpeedUpIntentEvent()); + } + if (!e.repeat && e.code === this.keybinds.gameSpeedDown) { + e.preventDefault(); + this.eventBus.emit(new GameSpeedDownIntentEvent()); + } + // Shift-D to toggle performance overlay - console.log(e.code, e.shiftKey, e.ctrlKey, e.altKey, e.metaKey); if (e.code === "KeyD" && e.shiftKey) { e.preventDefault(); console.log("TogglePerformanceOverlayEvent"); diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 58c43306c..e2805235f 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -20,12 +20,24 @@ import { } from "../core/Util"; import { getPersistentID } from "./Auth"; import { LobbyConfig } from "./ClientGameRunner"; -import { ReplaySpeedChangeEvent } from "./InputHandler"; +import { + GameSpeedDownIntentEvent, + GameSpeedUpIntentEvent, + ReplaySpeedChangeEvent, +} from "./InputHandler"; import { defaultReplaySpeedMultiplier, ReplaySpeedMultiplier, } from "./utilities/ReplaySpeedMultiplier"; +// Order: 0.5, 1, 2, max (same as ReplayPanel) +const SPEED_ORDER: ReplaySpeedMultiplier[] = [ + ReplaySpeedMultiplier.slow, + ReplaySpeedMultiplier.normal, + ReplaySpeedMultiplier.fast, + ReplaySpeedMultiplier.fastest, +]; + // build a small backlog so MAX can catch up. const MAX_REPLAY_BACKLOG_TURNS = 60; @@ -94,6 +106,26 @@ export class LocalServer { this.replaySpeedMultiplier = event.replaySpeedMultiplier; }); + if (!this.isReplay) { + this.eventBus.on(GameSpeedUpIntentEvent, () => { + const idx = SPEED_ORDER.indexOf(this.replaySpeedMultiplier); + if (idx < 0 || idx >= SPEED_ORDER.length - 1) return; + this.replaySpeedMultiplier = SPEED_ORDER[idx + 1]; + this.eventBus.emit( + new ReplaySpeedChangeEvent(this.replaySpeedMultiplier), + ); + }); + + this.eventBus.on(GameSpeedDownIntentEvent, () => { + const idx = SPEED_ORDER.indexOf(this.replaySpeedMultiplier); + if (idx <= 0) return; + this.replaySpeedMultiplier = SPEED_ORDER[idx - 1]; + this.eventBus.emit( + new ReplaySpeedChangeEvent(this.replaySpeedMultiplier), + ); + }); + } + this.startedAt = Date.now(); this.clientConnect(); if (this.lobbyConfig.gameRecord) { diff --git a/src/client/UserSettingModal.ts b/src/client/UserSettingModal.ts index 7bf6612d3..cb47ee320 100644 --- a/src/client/UserSettingModal.ts +++ b/src/client/UserSettingModal.ts @@ -47,6 +47,9 @@ const DefaultKeybinds: Record = { moveRight: "KeyD", modifierKey: isMac ? "MetaLeft" : "ControlLeft", altKey: "AltLeft", + pauseGame: "KeyP", + gameSpeedUp: "Period", + gameSpeedDown: "Comma", }; @customElement("user-setting") @@ -634,6 +637,36 @@ export class UserSettingModal extends BaseModal { @change=${this.handleKeybindChange} > + + + + + +

diff --git a/src/client/graphics/layers/GameRightSidebar.ts b/src/client/graphics/layers/GameRightSidebar.ts index 2d2f78891..f3ddc8278 100644 --- a/src/client/graphics/layers/GameRightSidebar.ts +++ b/src/client/graphics/layers/GameRightSidebar.ts @@ -4,6 +4,7 @@ import { EventBus } from "../../../core/EventBus"; import { GameType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { crazyGamesSDK } from "../../CrazyGamesSDK"; +import { TogglePauseIntentEvent } from "../../InputHandler"; import { PauseGameIntentEvent, SendWinnerEvent } from "../../Transport"; import { translateText } from "../../Utils"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; @@ -67,6 +68,14 @@ export class GameRightSidebar extends LitElement implements Layer { this.requestUpdate(); }); + this.eventBus.on(TogglePauseIntentEvent, () => { + const isReplayOrSingleplayer = + this._isSinglePlayer || this.game?.config()?.isReplay(); + if (isReplayOrSingleplayer || this.isLobbyCreator) { + this.onPauseButtonClick(); + } + }); + this.requestUpdate(); } diff --git a/src/client/graphics/layers/ReplayPanel.ts b/src/client/graphics/layers/ReplayPanel.ts index fbef9051d..8c214c2ef 100644 --- a/src/client/graphics/layers/ReplayPanel.ts +++ b/src/client/graphics/layers/ReplayPanel.ts @@ -41,6 +41,13 @@ export class ReplayPanel extends LitElement implements Layer { this.visible = event.visible; this.isSingleplayer = event.isSingleplayer; }); + this.eventBus.on( + ReplaySpeedChangeEvent, + (event: ReplaySpeedChangeEvent) => { + this._replaySpeedMultiplier = event.replaySpeedMultiplier; + this.requestUpdate(); + }, + ); } }