mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 02:55:40 +00:00
1cfeaf8c2a
## Description: PR for https://github.com/openfrontio/OpenFrontIO/issues/1105 This pull request introduces a new replay-panel under options-menu to control singleplayer and replay speed.  User can select 4 different speed multipliers based on the turnIntervalMs config. I locally tested the feature in singleplayer mode. I couldn't find a way to test replay mode. Tested with the pause button, working as you would expect. Here is an example: [replay-speed.webm](https://github.com/user-attachments/assets/7b3a7616-5f8f-4fbb-88ba-0b2414c6f2ea) ## 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 ⚠️ I need help to test this feature in real conditions with a replayed game in dev env. - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: ghisloufou --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: evanpelle <evanpelle@gmail.com>
244 lines
7.1 KiB
TypeScript
244 lines
7.1 KiB
TypeScript
import { html, LitElement } from "lit";
|
|
import { customElement, state } from "lit/decorators.js";
|
|
import { EventBus } from "../../../core/EventBus";
|
|
import { GameType } from "../../../core/game/Game";
|
|
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
|
import { GameView } from "../../../core/game/GameView";
|
|
import { UserSettings } from "../../../core/game/UserSettings";
|
|
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
|
|
import { PauseGameEvent } from "../../Transport";
|
|
import { Layer } from "./Layer";
|
|
|
|
const button = ({
|
|
classes = "",
|
|
onClick = () => {},
|
|
title = "",
|
|
children,
|
|
}) => html`
|
|
<button
|
|
class="flex items-center justify-center p-1
|
|
bg-opacity-70 bg-gray-700 text-opacity-90 text-white
|
|
border-none rounded cursor-pointer
|
|
hover:bg-opacity-60 hover:bg-gray-600
|
|
transition-colors duration-200
|
|
text-sm lg:text-xl ${classes}"
|
|
@click=${onClick}
|
|
aria-label=${title}
|
|
title=${title}
|
|
>
|
|
${children}
|
|
</button>
|
|
`;
|
|
|
|
const secondsToHms = (d: number): string => {
|
|
const h = Math.floor(d / 3600);
|
|
const m = Math.floor((d % 3600) / 60);
|
|
const s = Math.floor((d % 3600) % 60);
|
|
let time = d === 0 ? "-" : `${s}s`;
|
|
if (m > 0) time = `${m}m` + time;
|
|
if (h > 0) time = `${h}h` + time;
|
|
return time;
|
|
};
|
|
|
|
@customElement("options-menu")
|
|
export class OptionsMenu extends LitElement implements Layer {
|
|
public game: GameView;
|
|
public eventBus: EventBus;
|
|
private userSettings: UserSettings = new UserSettings();
|
|
|
|
@state()
|
|
private showPauseButton: boolean = true;
|
|
|
|
@state()
|
|
private isPaused: boolean = false;
|
|
|
|
@state()
|
|
private timer: number = 0;
|
|
|
|
@state()
|
|
private showSettings: boolean = false;
|
|
|
|
private isVisible = false;
|
|
|
|
private hasWinner = false;
|
|
|
|
@state()
|
|
private alternateView: boolean = false;
|
|
|
|
private onTerrainButtonClick() {
|
|
this.alternateView = !this.alternateView;
|
|
this.eventBus.emit(new AlternateViewEvent(this.alternateView));
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private onExitButtonClick() {
|
|
const isAlive = this.game.myPlayer()?.isAlive();
|
|
if (isAlive) {
|
|
const isConfirmed = confirm("Are you sure you want to exit the game?");
|
|
if (!isConfirmed) return;
|
|
}
|
|
// redirect to the home page
|
|
window.location.href = "/";
|
|
}
|
|
|
|
createRenderRoot() {
|
|
return this;
|
|
}
|
|
|
|
private onSettingsButtonClick() {
|
|
this.showSettings = !this.showSettings;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private onPauseButtonClick() {
|
|
this.isPaused = !this.isPaused;
|
|
this.eventBus.emit(new PauseGameEvent(this.isPaused));
|
|
}
|
|
|
|
private onToggleEmojisButtonClick() {
|
|
this.userSettings.toggleEmojis();
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private onToggleSpecialEffectsButtonClick() {
|
|
this.userSettings.toggleFxLayer();
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private onToggleDarkModeButtonClick() {
|
|
this.userSettings.toggleDarkMode();
|
|
this.requestUpdate();
|
|
this.eventBus.emit(new RefreshGraphicsEvent());
|
|
}
|
|
|
|
private onToggleRandomNameModeButtonClick() {
|
|
this.userSettings.toggleRandomName();
|
|
}
|
|
|
|
private onToggleFocusLockedButtonClick() {
|
|
this.userSettings.toggleFocusLocked();
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private onToggleLeftClickOpensMenu() {
|
|
this.userSettings.toggleLeftClickOpenMenu();
|
|
}
|
|
|
|
init() {
|
|
console.log("init called from OptionsMenu");
|
|
this.showPauseButton =
|
|
this.game.config().gameConfig().gameType === GameType.Singleplayer ||
|
|
this.game.config().isReplay();
|
|
this.isVisible = true;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
tick() {
|
|
const updates = this.game.updatesSinceLastTick();
|
|
if (updates) {
|
|
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
|
|
}
|
|
if (this.game.inSpawnPhase()) {
|
|
this.timer = 0;
|
|
} else if (!this.hasWinner && this.game.ticks() % 10 === 0) {
|
|
this.timer++;
|
|
}
|
|
this.isVisible = true;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
render() {
|
|
if (!this.isVisible) {
|
|
return html``;
|
|
}
|
|
return html`
|
|
<div
|
|
class="top-0 lg:top-4 right-0 lg:right-4 z-50 pointer-events-auto"
|
|
@contextmenu=${(e) => e.preventDefault()}
|
|
>
|
|
<div
|
|
class="bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-es-sm lg:rounded-lg backdrop-blur-md"
|
|
>
|
|
<div class="flex items-stretch gap-1 lg:gap-2">
|
|
${button({
|
|
classes: !this.showPauseButton ? "hidden" : "",
|
|
onClick: this.onPauseButtonClick,
|
|
title: this.isPaused ? "Resume game" : "Pause game",
|
|
children: this.isPaused ? "▶️" : "⏸",
|
|
})}
|
|
<div
|
|
class="w-15 h-8 lg:w-24 lg:h-10 flex items-center justify-center w-full
|
|
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
|
rounded text-sm lg:text-xl"
|
|
>
|
|
${secondsToHms(this.timer)}
|
|
</div>
|
|
${button({
|
|
onClick: this.onExitButtonClick,
|
|
title: "Exit game",
|
|
children: "❌",
|
|
})}
|
|
${button({
|
|
onClick: this.onSettingsButtonClick,
|
|
title: "Settings",
|
|
children: "⚙️",
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="options-menu flex flex-col justify-around gap-y-3 mt-2 bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-lg backdrop-blur-md ${!this
|
|
.showSettings
|
|
? "hidden"
|
|
: ""}"
|
|
>
|
|
${button({
|
|
onClick: this.onTerrainButtonClick,
|
|
title: "Toggle Terrain",
|
|
children: "🌲: " + (this.alternateView ? "On" : "Off"),
|
|
})}
|
|
${button({
|
|
onClick: this.onToggleEmojisButtonClick,
|
|
title: "Toggle Emojis",
|
|
children: "🙂: " + (this.userSettings.emojis() ? "On" : "Off"),
|
|
})}
|
|
${button({
|
|
onClick: this.onToggleSpecialEffectsButtonClick,
|
|
title: "Toggle Special effects",
|
|
children: "💥: " + (this.userSettings.fxLayer() ? "On" : "Off"),
|
|
})}
|
|
${button({
|
|
onClick: this.onToggleDarkModeButtonClick,
|
|
title: "Dark Mode",
|
|
children: "🌙: " + (this.userSettings.darkMode() ? "On" : "Off"),
|
|
})}
|
|
${button({
|
|
onClick: this.onToggleRandomNameModeButtonClick,
|
|
title: "Random name mode",
|
|
children:
|
|
"🥷: " + (this.userSettings.anonymousNames() ? "On" : "Off"),
|
|
})}
|
|
${button({
|
|
onClick: this.onToggleLeftClickOpensMenu,
|
|
title: "Left click",
|
|
children:
|
|
"🖱️: " +
|
|
(this.userSettings.leftClickOpensMenu()
|
|
? "Opens menu"
|
|
: "Attack"),
|
|
})}
|
|
<!-- ${button({
|
|
onClick: this.onToggleFocusLockedButtonClick,
|
|
title: "Lock Focus",
|
|
children:
|
|
"🗺: " +
|
|
(this.userSettings.focusLocked()
|
|
? "Focus locked"
|
|
: "Hover focus"),
|
|
})} -->
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|