mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:20:17 +00:00
Feat: Game Speed + Pause keybinds (#3397)
## Description: Can't tell you how many times I've been playing solo, I try to go change the speed from `Max` to `x1` and before I've opened the speed controls and clicked on one the AI completely wipes me. But not to worry, we now have a pause and speed toggle up/down keybinds! https://github.com/user-attachments/assets/48692c27-888f-40fb-837a-45e26f262441 Keybinds were added to "Menu Shortcuts" submenu: <img width="1750" height="1099" alt="image" src="https://github.com/user-attachments/assets/8c4500d5-f43e-4a1c-9940-04db75bf18cf" /> Tested on Solo, custom match, and public lobbies. Pause intent works correctly on solo and private match (only if you are host), and neither feature works in public matches, as expected. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx
This commit is contained in:
@@ -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}%",
|
||||
|
||||
@@ -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")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white/5 transition-colors">
|
||||
<td class="py-3 pl-4 border-b border-white/5">
|
||||
${this.renderKey(keybinds.pauseGame)}
|
||||
</td>
|
||||
<td class="py-3 border-b border-white/5 text-white/70">
|
||||
${translateText("help_modal.action_pause_game")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white/5 transition-colors">
|
||||
<td class="py-3 pl-4 border-b border-white/5">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
${this.renderKey(keybinds.gameSpeedDown)}
|
||||
${this.renderKey(keybinds.gameSpeedUp)}
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 border-b border-white/5 text-white/70">
|
||||
${translateText("help_modal.action_game_speed")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white/5 transition-colors">
|
||||
<td class="py-3 pl-4 border-b border-white/5">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -47,6 +47,9 @@ const DefaultKeybinds: Record<string, string> = {
|
||||
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}
|
||||
></setting-keybind>
|
||||
|
||||
<setting-keybind
|
||||
action="pauseGame"
|
||||
label=${translateText("user_setting.pause_game")}
|
||||
description=${translateText("user_setting.pause_game_desc")}
|
||||
.defaultKey=${DefaultKeybinds.pauseGame}
|
||||
.value=${this.getKeyValue("pauseGame")}
|
||||
.display=${this.getKeyChar("pauseGame")}
|
||||
@change=${this.handleKeybindChange}
|
||||
></setting-keybind>
|
||||
|
||||
<setting-keybind
|
||||
action="gameSpeedUp"
|
||||
label=${translateText("user_setting.game_speed_up")}
|
||||
description=${translateText("user_setting.game_speed_up_desc")}
|
||||
.defaultKey=${DefaultKeybinds.gameSpeedUp}
|
||||
.value=${this.getKeyValue("gameSpeedUp")}
|
||||
.display=${this.getKeyChar("gameSpeedUp")}
|
||||
@change=${this.handleKeybindChange}
|
||||
></setting-keybind>
|
||||
|
||||
<setting-keybind
|
||||
action="gameSpeedDown"
|
||||
label=${translateText("user_setting.game_speed_down")}
|
||||
description=${translateText("user_setting.game_speed_down_desc")}
|
||||
.defaultKey=${DefaultKeybinds.gameSpeedDown}
|
||||
.value=${this.getKeyValue("gameSpeedDown")}
|
||||
.display=${this.getKeyChar("gameSpeedDown")}
|
||||
@change=${this.handleKeybindChange}
|
||||
></setting-keybind>
|
||||
|
||||
<h2
|
||||
class="text-blue-200 text-xl font-bold mt-8 mb-3 border-b border-white/10 pb-2"
|
||||
>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user