mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 10:53:31 +00:00
Perf/Refactor(UserSettings): caching makes it 10-20x faster (#3481)
## Description: Skip slow and blocking LocalStorage reads, replace by a Map. Also some refactoring. ### Contains - No out-of-sync issue between main and worker thread: Earlier PRs got a comment from evan about main & worker.worker thread having their own version of usersettings and possibly getting out-of-sync (see https://github.com/openfrontio/OpenFrontIO/pull/760#pullrequestreview-2845155737, https://github.com/openfrontio/OpenFrontIO/pull/896#pullrequestreview-2871836979 and https://github.com/openfrontio/OpenFrontIO/pull/1266. But userSettings is not used in files ran by worker.worker, not even 10 months after evan's first comment about it. In GameRunner, createGameRunner sends NULL to getConfig as argument for userSettings. And DefaultConfig guards against userSettings being null by throwing an error, but it has never been thrown which points to worker.worker thread not using userSettings. So we do not need to worry about syncing between the threads currently. (If needed in the future after all, we could quite easily sync it, by loading the userSettings cache on worker.worker and listening to the "user-settings-changed" event @scamiv to keep it synced (changes in WorkerMessages and WorkerClient etc would be needed to handle this). - Went with cache in UserSettings, not with listening to "user-settings-changed" event: "user-settings-changed" was added by @scamiv and is used in PerformanceOverlay. Which is great for single files that need the very best performance. But having to add that same system to any file reading settings, scales poorly and would lead to messy code. Also, a developer could make the mistake of not listening to the event and it would end up just reading LocalStorage again just like now. Also a developer might forget removing the listener or so etc. The cache is a central solution and fast, without changes to other files needed and future-proof. - Make sure each setting is cached: UserSettingsModal was using LocalStorage directly by itself for some things. Made it use the central UserSettings methods instead so we avoid LocalStorage reads as much as possible. For this, changed get() and set() in UserSettings to getBool() and setBool(), to introduce a getString() and setString() for use in UserSettingsModal while keeping getCached() and setCached() private within UserSettings. - Remove unused 'focusLocked' and 'toggleFocusLocked' from UserSettings: was last changed 11 months ago to just return false. Since then we've moved to different ways of highlighting and this setting isn't used anymore. No existing references or callers are left. - Other files: -- Have callers call the renamed functions (see point above) -- Remove userSettings from UILayer and Territorylayer: the variable is unused in those files. Also remove from GameRenderer when it calls TerritoryLayer. -- Cache calls to defaultconfig Theme (which in turn calls dark mode setting)/Config better in: GameView and Terrainlayer. ### Update on Contents later on It wasn't really in scope of this PR but further consolidation was called for. These changes could also pave the way for UserSettingsModal (main menu) perhaps being partly mergable with SettingsModal (in-game) one day as it begins to look more like it. Even though UserSettingsModal still does things its own way, and does console.log where SettingsModal doesn't, etc. They both have partially different content and settings but also have a large overlap. - UserSettings: Removed localStorage call from clearFlag() and setFlag() which were added after creation of this PR, and were neatly merged in silence without merge conflicts so i wasn't aware of them yet until now. - UserSettings: added key constants, exported to use both inside UserSettings and in files that listen to its events. - UserSettings 'emitChange': now done from setCached, removed from setBool, setFlag etc. Also removed from the new setFlag. And from setPattern even though it emitted "pattern" instead of key name "territoryPattern"; now it emits the default "territoryPattern" from PATTERN_KEY which is re-used in Store, TerritoryPatternsModal and PatternInput. - UserSettingsModal: made UserSettingsModal call existing toggle functions in UserSettings, or new or existing getter or setter. We do not need CustomEvent: checked anymore. In UserSettingsModal, its toggle functions did not all actually toggle, some like toggleLeftClickOpensMenu actually just set a value. Based on the 'checked' value of the CustomEvent. But we don't need that 'checked' value anymore and none of the checks for it inside the toggle functions in UserSettingsModal, now that we just directly call toggleLeftClickOpensMenu and others in UserSettings. - SettingToggle: continuing about not needing CustomEvent anymore: the old way actually fired two events. The native change event from <input> and our own CustomEvent from handleChange in SettingToggle. It prevented handling both events by checking e.detail?.checked === undefined. But now, the native <input> event is all we need to show the visual toggle change and trigger @changed in UserSettingsModal which calls the toggle function. - Use the toggle functions too from CopyButton and PerformanceOverlay.ts. In PerformanceOverlay, change in onUserSettingsChanged was needed because of how setBool works. - UserSettingsModal 'toggleDarkMode': in UserSettingsModal, removed the event from toggleDarkMode in UserSettingsModal; nothing is listening to this event anymore after DarkModeButton.ts was removed some time ago. Also both UserSettingsModal an UserSettings added/removed "dark" from the document element. Now that UserSettingsModal calls toggleDarkMode in UserSettings, we could centralize that. But UserSettings is in core, not in client like UserSettingsModal. But now that we emit "user-settings-changed", we could handle it even more centralized and not have UserSettingsModal or UserSettings touch the element directly. Instead have Main.ts listen to the event and change it dark mode from there. - UserSettings: added claryfing comment to attackRatioIncrement and the new attackRatio setters/getters, to explain their difference. Noticed a small omitment in its description and fixed that right away in en.json: you can change attack ratio increment by shift+mouse wheel scroll or by hotkey. So made "How much the attack ratio keybinds change per press" also mention "/scroll." **BEFORE** (with getDisplayName added back to NameLayer as a fix i will do soon) get > getItem in UserSettings  renderLayer in NameLayer (with getDisplayName added back to NameLayer as a fix i will do soon)  **AFTER** (with getDisplayName added back to NameLayer as a fix i will do soon) getCached in UserSettings  renderLayer in NameLayer (with getDisplayName added back to NameLayer as a fix i will do soon)  ## 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: tryout33
This commit is contained in:
@@ -611,7 +611,7 @@
|
||||
"attack_ratio_down": "Decrease Attack Ratio",
|
||||
"attack_ratio_down_desc": "Decrease attack ratio by {amount}%",
|
||||
"attack_ratio_increment_label": "Attack Ratio Keybind Increment",
|
||||
"attack_ratio_increment_desc": "How much the attack ratio keybinds change per press.",
|
||||
"attack_ratio_increment_desc": "How much the attack ratio keybinds change per press/scroll.",
|
||||
"attack_keybinds": "Attack Keybinds",
|
||||
"boat_attack": "Boat Attack",
|
||||
"boat_attack_desc": "Send a boat attack to the tile under your cursor.",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { FlagName } from "../core/Schemas";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import {
|
||||
FLAG_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../core/game/UserSettings";
|
||||
import { resolveFlagUrl } from "./Cosmetics";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
@@ -41,7 +45,7 @@ export class FlagInput extends LitElement {
|
||||
super.connectedCallback();
|
||||
this.flag = new UserSettings().getFlag() ?? "";
|
||||
window.addEventListener(
|
||||
"event:user-settings-changed:flag",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${FLAG_KEY}`,
|
||||
this.updateFlag as EventListener,
|
||||
);
|
||||
}
|
||||
@@ -49,7 +53,7 @@ export class FlagInput extends LitElement {
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(
|
||||
"event:user-settings-changed:flag",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${FLAG_KEY}`,
|
||||
this.updateFlag as EventListener,
|
||||
);
|
||||
}
|
||||
|
||||
+22
-6
@@ -11,7 +11,11 @@ import {
|
||||
import { GameEnv } from "../core/configuration/Config";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import {
|
||||
DARK_MODE_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../core/game/UserSettings";
|
||||
import "./AccountModal";
|
||||
import { getUserMe } from "./Api";
|
||||
import { userAuth } from "./Auth";
|
||||
@@ -478,11 +482,23 @@ class Client {
|
||||
this.joinModal.eventBus = this.eventBus;
|
||||
}
|
||||
|
||||
if (this.userSettings.darkMode()) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
const applyDarkMode = (isDark: boolean) => {
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
|
||||
applyDarkMode(this.userSettings.darkMode());
|
||||
|
||||
globalThis.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${DARK_MODE_KEY}`,
|
||||
(e: CustomEvent<string>) => {
|
||||
const isDark = e.detail === "true";
|
||||
applyDarkMode(isDark);
|
||||
},
|
||||
);
|
||||
|
||||
// Attempt to join lobby
|
||||
if (document.readyState === "loading") {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import {
|
||||
PATTERN_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
} from "../core/game/UserSettings";
|
||||
import { PlayerPattern } from "../core/Schemas";
|
||||
import { renderPatternPreview } from "./components/PatternButton";
|
||||
import { getPlayerCosmetics } from "./Cosmetics";
|
||||
@@ -47,7 +51,7 @@ export class PatternInput extends LitElement {
|
||||
if (!this.isConnected) return;
|
||||
this.isLoading = false;
|
||||
window.addEventListener(
|
||||
"event:user-settings-changed:pattern",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
{
|
||||
signal: this._abortController.signal,
|
||||
|
||||
+7
-3
@@ -3,7 +3,11 @@ import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { ColorPalette, Cosmetics, Pattern } from "../core/CosmeticSchemas";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import {
|
||||
PATTERN_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../core/game/UserSettings";
|
||||
import { PlayerPattern } from "../core/Schemas";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/FlagButton";
|
||||
@@ -45,7 +49,7 @@ export class StoreModal extends BaseModal {
|
||||
},
|
||||
);
|
||||
window.addEventListener(
|
||||
"event:user-settings-changed:pattern",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
);
|
||||
}
|
||||
@@ -53,7 +57,7 @@ export class StoreModal extends BaseModal {
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(
|
||||
"event:user-settings-changed:pattern",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { Cosmetics, Pattern } from "../core/CosmeticSchemas";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import {
|
||||
PATTERN_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../core/game/UserSettings";
|
||||
import { PlayerPattern } from "../core/Schemas";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/NotLoggedInWarning";
|
||||
@@ -42,7 +46,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
},
|
||||
);
|
||||
window.addEventListener(
|
||||
"event:user-settings-changed:pattern",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
);
|
||||
}
|
||||
@@ -50,7 +54,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(
|
||||
"event:user-settings-changed:pattern",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export class UserSettingModal extends BaseModal {
|
||||
}
|
||||
|
||||
private loadKeybindsFromStorage() {
|
||||
const savedKeybinds = localStorage.getItem("settings.keybinds");
|
||||
const savedKeybinds = this.userSettings.keybinds();
|
||||
if (!savedKeybinds) return;
|
||||
|
||||
try {
|
||||
@@ -199,7 +199,7 @@ export class UserSettingModal extends BaseModal {
|
||||
}
|
||||
|
||||
this.keybinds = { ...this.keybinds, [action]: { value: value, key: key } };
|
||||
localStorage.setItem("settings.keybinds", JSON.stringify(this.keybinds));
|
||||
this.userSettings.setKeybinds(JSON.stringify(this.keybinds));
|
||||
}
|
||||
|
||||
private getKeyValue(action: string): string | undefined {
|
||||
@@ -251,101 +251,77 @@ export class UserSettingModal extends BaseModal {
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
toggleDarkMode(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
toggleDarkMode() {
|
||||
this.userSettings.toggleDarkMode();
|
||||
|
||||
if (typeof enabled !== "boolean") {
|
||||
console.warn("Unexpected toggle event payload", e);
|
||||
return;
|
||||
}
|
||||
console.log("🌙 Dark Mode:", this.userSettings.darkMode() ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
this.userSettings.set("settings.darkMode", enabled);
|
||||
private toggleEmojis() {
|
||||
this.userSettings.toggleEmojis();
|
||||
|
||||
if (enabled) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
console.log("🤡 Emojis:", this.userSettings.emojis() ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("dark-mode-changed", {
|
||||
detail: { darkMode: enabled },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
private toggleAlertFrame() {
|
||||
this.userSettings.toggleAlertFrame();
|
||||
|
||||
console.log(
|
||||
"🚨 Alert frame:",
|
||||
this.userSettings.alertFrame() ? "ON" : "OFF",
|
||||
);
|
||||
|
||||
console.log("🌙 Dark Mode:", enabled ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
private toggleEmojis(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
private toggleFxLayer() {
|
||||
this.userSettings.toggleFxLayer();
|
||||
|
||||
this.userSettings.set("settings.emojis", enabled);
|
||||
|
||||
console.log("🤡 Emojis:", enabled ? "ON" : "OFF");
|
||||
console.log(
|
||||
"💥 Special effects:",
|
||||
this.userSettings.fxLayer() ? "ON" : "OFF",
|
||||
);
|
||||
}
|
||||
|
||||
private toggleAlertFrame(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
private toggleStructureSprites() {
|
||||
this.userSettings.toggleStructureSprites();
|
||||
|
||||
this.userSettings.set("settings.alertFrame", enabled);
|
||||
|
||||
console.log("🚨 Alert frame:", enabled ? "ON" : "OFF");
|
||||
console.log(
|
||||
"🏠 Structure sprites:",
|
||||
this.userSettings.structureSprites() ? "ON" : "OFF",
|
||||
);
|
||||
}
|
||||
|
||||
private toggleFxLayer(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
private toggleCursorCostLabel() {
|
||||
this.userSettings.toggleCursorCostLabel();
|
||||
|
||||
this.userSettings.set("settings.specialEffects", enabled);
|
||||
|
||||
console.log("💥 Special effects:", enabled ? "ON" : "OFF");
|
||||
console.log(
|
||||
"💰 Cursor build cost:",
|
||||
this.userSettings.cursorCostLabel() ? "ON" : "OFF",
|
||||
);
|
||||
}
|
||||
|
||||
private toggleStructureSprites(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
private toggleAnonymousNames() {
|
||||
this.userSettings.toggleRandomName();
|
||||
|
||||
this.userSettings.set("settings.structureSprites", enabled);
|
||||
|
||||
console.log("🏠 Structure sprites:", enabled ? "ON" : "OFF");
|
||||
console.log(
|
||||
"🙈 Anonymous Names:",
|
||||
this.userSettings.anonymousNames() ? "ON" : "OFF",
|
||||
);
|
||||
}
|
||||
|
||||
private toggleCursorCostLabel(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
|
||||
this.userSettings.set("settings.cursorCostLabel", enabled);
|
||||
|
||||
console.log("💰 Cursor build cost:", enabled ? "ON" : "OFF");
|
||||
private toggleLobbyIdVisibility() {
|
||||
this.userSettings.toggleLobbyIdVisibility();
|
||||
console.log(
|
||||
"👁️ Hidden Lobby IDs:",
|
||||
!this.userSettings.lobbyIdVisibility() ? "ON" : "OFF",
|
||||
);
|
||||
}
|
||||
|
||||
private toggleAnonymousNames(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
|
||||
this.userSettings.set("settings.anonymousNames", enabled);
|
||||
|
||||
console.log("🙈 Anonymous Names:", enabled ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
private toggleLobbyIdVisibility(e: CustomEvent<{ checked: boolean }>) {
|
||||
const hideIds = e.detail?.checked;
|
||||
if (typeof hideIds !== "boolean") return;
|
||||
|
||||
this.userSettings.set("settings.lobbyIdVisibility", !hideIds); // Invert because checked=hide
|
||||
console.log("👁️ Hidden Lobby IDs:", hideIds ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
private toggleLeftClickOpensMenu(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
|
||||
this.userSettings.set("settings.leftClickOpensMenu", enabled);
|
||||
console.log("🖱️ Left Click Opens Menu:", enabled ? "ON" : "OFF");
|
||||
private toggleLeftClickOpensMenu() {
|
||||
this.userSettings.toggleLeftClickOpenMenu();
|
||||
console.log(
|
||||
"🖱️ Left Click Opens Menu:",
|
||||
this.userSettings.leftClickOpensMenu() ? "ON" : "OFF",
|
||||
);
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
@@ -354,7 +330,7 @@ export class UserSettingModal extends BaseModal {
|
||||
const value = e.detail?.value;
|
||||
if (typeof value === "number") {
|
||||
const ratio = value / 100;
|
||||
localStorage.setItem("settings.attackRatio", ratio.toString());
|
||||
this.userSettings.setAttackRatio(ratio);
|
||||
} else {
|
||||
console.warn("Slider event missing detail.value", e);
|
||||
}
|
||||
@@ -370,27 +346,21 @@ export class UserSettingModal extends BaseModal {
|
||||
console.warn("Select event missing detail.value", e);
|
||||
return;
|
||||
}
|
||||
this.userSettings.setFloat(
|
||||
"settings.attackRatioIncrement",
|
||||
Math.round(value),
|
||||
);
|
||||
this.userSettings.setAttackRatioIncrement(Math.round(value));
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private toggleTerritoryPatterns(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
private toggleTerritoryPatterns() {
|
||||
this.userSettings.toggleTerritoryPatterns();
|
||||
|
||||
this.userSettings.set("settings.territoryPatterns", enabled);
|
||||
|
||||
console.log("🏳️ Territory Patterns:", enabled ? "ON" : "OFF");
|
||||
console.log(
|
||||
"🏳️ Territory Patterns:",
|
||||
this.userSettings.territoryPatterns() ? "ON" : "OFF",
|
||||
);
|
||||
}
|
||||
|
||||
private togglePerformanceOverlay(e: CustomEvent<{ checked: boolean }>) {
|
||||
const enabled = e.detail?.checked;
|
||||
if (typeof enabled !== "boolean") return;
|
||||
|
||||
this.userSettings.set("settings.performanceOverlay", enabled);
|
||||
private togglePerformanceOverlay() {
|
||||
this.userSettings.togglePerformanceOverlay();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -809,8 +779,7 @@ export class UserSettingModal extends BaseModal {
|
||||
description="${translateText("user_setting.dark_mode_desc")}"
|
||||
id="dark-mode-toggle"
|
||||
.checked=${this.userSettings.darkMode()}
|
||||
@change=${(e: CustomEvent<{ checked: boolean }>) =>
|
||||
this.toggleDarkMode(e)}
|
||||
@change=${this.toggleDarkMode}
|
||||
></setting-toggle>
|
||||
|
||||
<!-- 😊 Emojis -->
|
||||
@@ -881,7 +850,7 @@ export class UserSettingModal extends BaseModal {
|
||||
label="${translateText("user_setting.lobby_id_visibility_label")}"
|
||||
description="${translateText("user_setting.lobby_id_visibility_desc")}"
|
||||
id="lobby-id-visibility-toggle"
|
||||
.checked=${!this.userSettings.get("settings.lobbyIdVisibility", true)}
|
||||
.checked=${!this.userSettings.lobbyIdVisibility()}
|
||||
@change=${this.toggleLobbyIdVisibility}
|
||||
></setting-toggle>
|
||||
|
||||
@@ -909,8 +878,7 @@ export class UserSettingModal extends BaseModal {
|
||||
description="${translateText("user_setting.attack_ratio_desc")}"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${Number(localStorage.getItem("settings.attackRatio") ?? "0.2") *
|
||||
100}
|
||||
.value=${this.userSettings.attackRatio() * 100}
|
||||
@change=${this.sliderAttackRatio}
|
||||
></setting-slider>
|
||||
|
||||
|
||||
@@ -33,10 +33,7 @@ export class CopyButton extends LitElement {
|
||||
changedProperties: Map<string | number | symbol, unknown>,
|
||||
) {
|
||||
if (changedProperties.has("lobbyId")) {
|
||||
this.lobbyIdVisible = this.userSettings.get(
|
||||
"settings.lobbyIdVisibility",
|
||||
true,
|
||||
);
|
||||
this.lobbyIdVisible = this.userSettings.lobbyIdVisibility();
|
||||
this.copySuccess = false;
|
||||
}
|
||||
if (changedProperties.has("copyText")) {
|
||||
|
||||
@@ -16,13 +16,6 @@ export class SettingToggle extends LitElement {
|
||||
private handleChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
this.checked = input.checked;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: { checked: this.checked },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -273,7 +273,7 @@ export function createRenderer(
|
||||
// Not grouping the layers may cause excessive calls to context.save() and context.restore().
|
||||
const layers: Layer[] = [
|
||||
new TerrainLayer(game, transformHandler),
|
||||
new TerritoryLayer(game, eventBus, transformHandler, userSettings),
|
||||
new TerritoryLayer(game, eventBus, transformHandler),
|
||||
new RailroadLayer(game, eventBus, transformHandler, uiState),
|
||||
new CoordinateGridLayer(game, eventBus, transformHandler),
|
||||
structureLayer,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import {
|
||||
PERFORMANCE_OVERLAY_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../../../core/game/UserSettings";
|
||||
import {
|
||||
TickMetricsEvent,
|
||||
TogglePerformanceOverlayEvent,
|
||||
@@ -469,15 +473,15 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
) => {
|
||||
const nextVisible = !this.isVisible;
|
||||
this.setVisible(nextVisible);
|
||||
this.userSettings.set("settings.performanceOverlay", nextVisible);
|
||||
this.userSettings.setPerformanceOverlay(nextVisible);
|
||||
};
|
||||
|
||||
private onTickMetricsEvent = (event: TickMetricsEvent) => {
|
||||
this.updateTickMetrics(event.tickExecutionDuration, event.tickDelay);
|
||||
};
|
||||
|
||||
private onUserSettingsChanged = (event: CustomEvent) => {
|
||||
const nextVisible = (event.detail as boolean) === true;
|
||||
private onUserSettingsChanged = (event: CustomEvent<string>) => {
|
||||
const nextVisible = event.detail === "true";
|
||||
if (this.isVisible === nextVisible) return;
|
||||
this.setVisible(nextVisible);
|
||||
};
|
||||
@@ -505,7 +509,7 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
|
||||
if (!this.isUserSettingsListenerAttached) {
|
||||
globalThis.addEventListener(
|
||||
"event:user-settings-changed:settings.performanceOverlay",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PERFORMANCE_OVERLAY_KEY}`,
|
||||
this.onUserSettingsChanged,
|
||||
);
|
||||
this.isUserSettingsListenerAttached = true;
|
||||
@@ -517,7 +521,7 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
|
||||
if (this.isUserSettingsListenerAttached) {
|
||||
globalThis.removeEventListener(
|
||||
"event:user-settings-changed:settings.performanceOverlay",
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PERFORMANCE_OVERLAY_KEY}`,
|
||||
this.onUserSettingsChanged,
|
||||
);
|
||||
this.isUserSettingsListenerAttached = false;
|
||||
@@ -576,7 +580,7 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
private handleClose() {
|
||||
const nextVisible = false;
|
||||
this.setVisible(nextVisible);
|
||||
this.userSettings.set("settings.performanceOverlay", nextVisible);
|
||||
this.userSettings.setPerformanceOverlay(nextVisible);
|
||||
}
|
||||
|
||||
private onDragPointerMove = (e: PointerEvent) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Theme } from "../../../core/configuration/Config";
|
||||
import { Config, Theme } from "../../../core/configuration/Config";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
@@ -8,16 +8,19 @@ export class TerrainLayer implements Layer {
|
||||
private context: CanvasRenderingContext2D;
|
||||
private imageData: ImageData;
|
||||
private theme: Theme;
|
||||
private config: Config;
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
private transformHandler: TransformHandler,
|
||||
) {}
|
||||
) {
|
||||
this.config = this.game.config();
|
||||
}
|
||||
shouldTransform(): boolean {
|
||||
return true;
|
||||
}
|
||||
tick() {
|
||||
if (this.game.config().theme() !== this.theme) {
|
||||
if (this.config.theme() !== this.theme) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
@@ -46,7 +49,7 @@ export class TerrainLayer implements Layer {
|
||||
}
|
||||
|
||||
initImageData() {
|
||||
this.theme = this.game.config().theme();
|
||||
this.theme = this.config.theme();
|
||||
this.game.forEachTile((tile) => {
|
||||
const terrainColor = this.theme.terrainColor(this.game, tile);
|
||||
// TODO: isn't tileref and index the same?
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import { euclDistFN, TileRef } from "../../../core/game/GameMap";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import { PseudoRandom } from "../../../core/PseudoRandom";
|
||||
import {
|
||||
AlternateViewEvent,
|
||||
@@ -24,7 +23,6 @@ import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
export class TerritoryLayer implements Layer {
|
||||
private userSettings: UserSettings;
|
||||
private canvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D;
|
||||
private imageData: ImageData;
|
||||
@@ -62,9 +60,7 @@ export class TerritoryLayer implements Layer {
|
||||
private game: GameView,
|
||||
private eventBus: EventBus,
|
||||
private transformHandler: TransformHandler,
|
||||
userSettings: UserSettings,
|
||||
) {
|
||||
this.userSettings = userSettings;
|
||||
this.theme = game.config().theme();
|
||||
this.cachedTerritoryPatternsEnabled = undefined;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Theme } from "../../../core/configuration/Config";
|
||||
import { UnitType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView, UnitView } from "../../../core/game/GameView";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import { UnitSelectionEvent } from "../../InputHandler";
|
||||
import { ProgressBar } from "../ProgressBar";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
@@ -28,7 +27,6 @@ export class UILayer implements Layer {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D | null;
|
||||
private theme: Theme | null = null;
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
private selectionAnimTime = 0;
|
||||
private allProgressBars: Map<
|
||||
number,
|
||||
|
||||
@@ -228,14 +228,10 @@ export class PlayerView {
|
||||
);
|
||||
}
|
||||
|
||||
const defaultTerritoryColor = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.territoryColor(this);
|
||||
const defaultBorderColor = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.borderColor(defaultTerritoryColor);
|
||||
const theme = this.game.config().theme();
|
||||
|
||||
const defaultTerritoryColor = theme.territoryColor(this);
|
||||
const defaultBorderColor = theme.borderColor(defaultTerritoryColor);
|
||||
|
||||
const pattern = userSettings.territoryPatterns()
|
||||
? this.cosmetics.pattern
|
||||
@@ -258,14 +254,11 @@ export class PlayerView {
|
||||
this._territoryColor = defaultTerritoryColor;
|
||||
}
|
||||
|
||||
this._structureColors = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.structureColors(this._territoryColor);
|
||||
this._structureColors = theme.structureColors(this._territoryColor);
|
||||
|
||||
const maybeFocusedBorderColor =
|
||||
this.game.myClientID() === this.data.clientID
|
||||
? this.game.config().theme().focusedBorderColor()
|
||||
? theme.focusedBorderColor()
|
||||
: defaultBorderColor;
|
||||
|
||||
this._borderColor = new Colord(
|
||||
@@ -275,7 +268,6 @@ export class PlayerView {
|
||||
);
|
||||
|
||||
// Pre-compute all border color variants once
|
||||
const theme = this.game.config().theme();
|
||||
const baseRgb = this._borderColor.toRgb();
|
||||
|
||||
// Neutral is just the base color
|
||||
|
||||
+116
-71
@@ -1,15 +1,22 @@
|
||||
import { Cosmetics } from "../CosmeticSchemas";
|
||||
import { PlayerPattern } from "../Schemas";
|
||||
|
||||
const PATTERN_KEY = "territoryPattern";
|
||||
export const USER_SETTINGS_CHANGED_EVENT = "event:user-settings-changed";
|
||||
export const PATTERN_KEY = "territoryPattern";
|
||||
export const FLAG_KEY = "flag";
|
||||
export const COLOR_KEY = "settings.territoryColor";
|
||||
export const DARK_MODE_KEY = "settings.darkMode";
|
||||
export const PERFORMANCE_OVERLAY_KEY = "settings.performanceOverlay";
|
||||
|
||||
export class UserSettings {
|
||||
private static cache = new Map<string, string | null>();
|
||||
|
||||
private emitChange(key: string, value: any): void {
|
||||
try {
|
||||
const maybeDispatch = (globalThis as any)?.dispatchEvent;
|
||||
if (typeof maybeDispatch !== "function") return;
|
||||
(globalThis as any).dispatchEvent(
|
||||
new CustomEvent(`event:user-settings-changed:${key}`, {
|
||||
new CustomEvent(`${USER_SETTINGS_CHANGED_EVENT}:${key}`, {
|
||||
detail: value,
|
||||
}),
|
||||
);
|
||||
@@ -18,147 +25,167 @@ export class UserSettings {
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string, defaultValue: boolean): boolean {
|
||||
const value = localStorage.getItem(key);
|
||||
private getCached(key: string): string | null {
|
||||
if (!UserSettings.cache.has(key)) {
|
||||
UserSettings.cache.set(key, localStorage.getItem(key));
|
||||
}
|
||||
return UserSettings.cache.get(key) ?? null;
|
||||
}
|
||||
|
||||
private setCached(key: string, value: string, emitChange: boolean = true) {
|
||||
localStorage.setItem(key, value);
|
||||
UserSettings.cache.set(key, value);
|
||||
if (emitChange) {
|
||||
this.emitChange(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private removeCached(key: string, emitChange: boolean = true) {
|
||||
localStorage.removeItem(key);
|
||||
UserSettings.cache.set(key, null);
|
||||
if (emitChange) {
|
||||
this.emitChange(key, null);
|
||||
}
|
||||
}
|
||||
|
||||
private getBool(key: string, defaultValue: boolean): boolean {
|
||||
const value = this.getCached(key);
|
||||
if (!value) return defaultValue;
|
||||
|
||||
if (value === "true") return true;
|
||||
|
||||
if (value === "false") return false;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
set(key: string, value: boolean) {
|
||||
localStorage.setItem(key, value ? "true" : "false");
|
||||
this.emitChange(key, value);
|
||||
private setBool(key: string, value: boolean) {
|
||||
this.setCached(key, value ? "true" : "false");
|
||||
}
|
||||
|
||||
getFloat(key: string, defaultValue: number): number {
|
||||
const value = localStorage.getItem(key);
|
||||
private getString(key: string, defaultValue: string = ""): string {
|
||||
const value = this.getCached(key);
|
||||
if (value === null) return defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
private setString(key: string, value: string) {
|
||||
this.setCached(key, value);
|
||||
}
|
||||
|
||||
private getFloat(key: string, defaultValue: number): number {
|
||||
const value = this.getCached(key);
|
||||
if (!value) return defaultValue;
|
||||
|
||||
const floatValue = parseFloat(value);
|
||||
if (isNaN(floatValue)) return defaultValue;
|
||||
|
||||
return floatValue;
|
||||
}
|
||||
|
||||
setFloat(key: string, value: number) {
|
||||
localStorage.setItem(key, value.toString());
|
||||
this.emitChange(key, value);
|
||||
private setFloat(key: string, value: number) {
|
||||
this.setCached(key, value.toString());
|
||||
}
|
||||
|
||||
emojis() {
|
||||
return this.get("settings.emojis", true);
|
||||
return this.getBool("settings.emojis", true);
|
||||
}
|
||||
|
||||
performanceOverlay() {
|
||||
return this.get("settings.performanceOverlay", false);
|
||||
return this.getBool(PERFORMANCE_OVERLAY_KEY, false);
|
||||
}
|
||||
|
||||
alertFrame() {
|
||||
return this.get("settings.alertFrame", true);
|
||||
return this.getBool("settings.alertFrame", true);
|
||||
}
|
||||
|
||||
anonymousNames() {
|
||||
return this.get("settings.anonymousNames", false);
|
||||
return this.getBool("settings.anonymousNames", false);
|
||||
}
|
||||
|
||||
lobbyIdVisibility() {
|
||||
return this.get("settings.lobbyIdVisibility", true);
|
||||
return this.getBool("settings.lobbyIdVisibility", true);
|
||||
}
|
||||
|
||||
fxLayer() {
|
||||
return this.get("settings.specialEffects", true);
|
||||
return this.getBool("settings.specialEffects", true);
|
||||
}
|
||||
|
||||
structureSprites() {
|
||||
return this.get("settings.structureSprites", true);
|
||||
return this.getBool("settings.structureSprites", true);
|
||||
}
|
||||
|
||||
darkMode() {
|
||||
return this.get("settings.darkMode", false);
|
||||
return this.getBool(DARK_MODE_KEY, false);
|
||||
}
|
||||
|
||||
leftClickOpensMenu() {
|
||||
return this.get("settings.leftClickOpensMenu", false);
|
||||
return this.getBool("settings.leftClickOpensMenu", false);
|
||||
}
|
||||
|
||||
territoryPatterns() {
|
||||
return this.get("settings.territoryPatterns", true);
|
||||
return this.getBool("settings.territoryPatterns", true);
|
||||
}
|
||||
|
||||
attackingTroopsOverlay() {
|
||||
return this.get("settings.attackingTroopsOverlay", true);
|
||||
return this.getBool("settings.attackingTroopsOverlay", true);
|
||||
}
|
||||
|
||||
toggleAttackingTroopsOverlay() {
|
||||
this.set("settings.attackingTroopsOverlay", !this.attackingTroopsOverlay());
|
||||
this.setBool(
|
||||
"settings.attackingTroopsOverlay",
|
||||
!this.attackingTroopsOverlay(),
|
||||
);
|
||||
}
|
||||
|
||||
cursorCostLabel() {
|
||||
const legacy = this.get("settings.ghostPricePill", true);
|
||||
return this.get("settings.cursorCostLabel", legacy);
|
||||
}
|
||||
|
||||
focusLocked() {
|
||||
return false;
|
||||
// TODO: re-enable when performance issues are fixed.
|
||||
this.get("settings.focusLocked", true);
|
||||
const legacy = this.getBool("settings.ghostPricePill", true);
|
||||
return this.getBool("settings.cursorCostLabel", legacy);
|
||||
}
|
||||
|
||||
toggleLeftClickOpenMenu() {
|
||||
this.set("settings.leftClickOpensMenu", !this.leftClickOpensMenu());
|
||||
}
|
||||
|
||||
toggleFocusLocked() {
|
||||
this.set("settings.focusLocked", !this.focusLocked());
|
||||
this.setBool("settings.leftClickOpensMenu", !this.leftClickOpensMenu());
|
||||
}
|
||||
|
||||
toggleEmojis() {
|
||||
this.set("settings.emojis", !this.emojis());
|
||||
this.setBool("settings.emojis", !this.emojis());
|
||||
}
|
||||
|
||||
// Performance overlay specifically needs a direct setter for Shift-D
|
||||
setPerformanceOverlay(value: boolean) {
|
||||
this.setBool(PERFORMANCE_OVERLAY_KEY, value);
|
||||
}
|
||||
|
||||
togglePerformanceOverlay() {
|
||||
this.set("settings.performanceOverlay", !this.performanceOverlay());
|
||||
this.setBool(PERFORMANCE_OVERLAY_KEY, !this.performanceOverlay());
|
||||
}
|
||||
|
||||
toggleAlertFrame() {
|
||||
this.set("settings.alertFrame", !this.alertFrame());
|
||||
this.setBool("settings.alertFrame", !this.alertFrame());
|
||||
}
|
||||
|
||||
toggleRandomName() {
|
||||
this.set("settings.anonymousNames", !this.anonymousNames());
|
||||
this.setBool("settings.anonymousNames", !this.anonymousNames());
|
||||
}
|
||||
|
||||
toggleLobbyIdVisibility() {
|
||||
this.set("settings.lobbyIdVisibility", !this.lobbyIdVisibility());
|
||||
this.setBool("settings.lobbyIdVisibility", !this.lobbyIdVisibility());
|
||||
}
|
||||
|
||||
toggleFxLayer() {
|
||||
this.set("settings.specialEffects", !this.fxLayer());
|
||||
this.setBool("settings.specialEffects", !this.fxLayer());
|
||||
}
|
||||
|
||||
toggleStructureSprites() {
|
||||
this.set("settings.structureSprites", !this.structureSprites());
|
||||
this.setBool("settings.structureSprites", !this.structureSprites());
|
||||
}
|
||||
|
||||
toggleCursorCostLabel() {
|
||||
this.set("settings.cursorCostLabel", !this.cursorCostLabel());
|
||||
this.setBool("settings.cursorCostLabel", !this.cursorCostLabel());
|
||||
}
|
||||
|
||||
toggleTerritoryPatterns() {
|
||||
this.set("settings.territoryPatterns", !this.territoryPatterns());
|
||||
this.setBool("settings.territoryPatterns", !this.territoryPatterns());
|
||||
}
|
||||
|
||||
toggleDarkMode() {
|
||||
this.set("settings.darkMode", !this.darkMode());
|
||||
if (this.darkMode()) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
this.setBool(DARK_MODE_KEY, !this.darkMode());
|
||||
}
|
||||
|
||||
// For development only. Used for testing patterns, set in the console manually.
|
||||
@@ -178,7 +205,7 @@ export class UserSettings {
|
||||
|
||||
getSelectedPatternName(cosmetics: Cosmetics | null): PlayerPattern | null {
|
||||
if (cosmetics === null) return null;
|
||||
let data = localStorage.getItem(PATTERN_KEY) ?? null;
|
||||
let data = this.getCached(PATTERN_KEY);
|
||||
if (data === null) return null;
|
||||
const patternPrefix = "pattern:";
|
||||
if (data.startsWith(patternPrefix)) {
|
||||
@@ -196,34 +223,32 @@ export class UserSettings {
|
||||
|
||||
setSelectedPatternName(patternName: string | undefined): void {
|
||||
if (patternName === undefined) {
|
||||
localStorage.removeItem(PATTERN_KEY);
|
||||
this.removeCached(PATTERN_KEY);
|
||||
} else {
|
||||
localStorage.setItem(PATTERN_KEY, patternName);
|
||||
this.setCached(PATTERN_KEY, patternName);
|
||||
}
|
||||
this.emitChange("pattern", patternName);
|
||||
}
|
||||
|
||||
getSelectedColor(): string | undefined {
|
||||
const data = localStorage.getItem("settings.territoryColor") ?? undefined;
|
||||
if (data === undefined) return undefined;
|
||||
return data;
|
||||
return this.getCached(COLOR_KEY) ?? undefined;
|
||||
}
|
||||
|
||||
setSelectedColor(color: string | undefined): void {
|
||||
if (color === undefined) {
|
||||
localStorage.removeItem("settings.territoryColor");
|
||||
this.removeCached(COLOR_KEY);
|
||||
} else {
|
||||
localStorage.setItem("settings.territoryColor", color);
|
||||
this.setCached(COLOR_KEY, color);
|
||||
}
|
||||
}
|
||||
|
||||
getFlag(): string | null {
|
||||
let flag = localStorage.getItem("flag");
|
||||
let flag = this.getCached(FLAG_KEY);
|
||||
if (!flag) return null;
|
||||
// Migrate bare country codes to country: prefix
|
||||
if (!flag.startsWith("flag:") && !flag.startsWith("country:")) {
|
||||
flag = `country:${flag}`;
|
||||
localStorage.setItem("flag", flag);
|
||||
// Silent migration: don't emit change event for FlagInput
|
||||
this.setCached(FLAG_KEY, flag, false);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
@@ -232,14 +257,12 @@ export class UserSettings {
|
||||
if (flag === "country:xx") {
|
||||
this.clearFlag();
|
||||
} else {
|
||||
localStorage.setItem("flag", flag);
|
||||
this.setCached(FLAG_KEY, flag);
|
||||
}
|
||||
console.log("emitting change!");
|
||||
this.emitChange("flag", flag);
|
||||
}
|
||||
|
||||
clearFlag(): void {
|
||||
localStorage.removeItem("flag");
|
||||
this.removeCached(FLAG_KEY);
|
||||
}
|
||||
|
||||
backgroundMusicVolume(): number {
|
||||
@@ -250,6 +273,7 @@ export class UserSettings {
|
||||
this.setFloat("settings.backgroundMusicVolume", volume);
|
||||
}
|
||||
|
||||
// What % attack ratio increments per click/scroll
|
||||
attackRatioIncrement(): number {
|
||||
const increment = Math.round(
|
||||
this.getFloat("settings.attackRatioIncrement", 10),
|
||||
@@ -258,6 +282,27 @@ export class UserSettings {
|
||||
return increment;
|
||||
}
|
||||
|
||||
setAttackRatioIncrement(value: number): void {
|
||||
this.setFloat("settings.attackRatioIncrement", value);
|
||||
}
|
||||
|
||||
// What % attack ratio is set to
|
||||
attackRatio(): number {
|
||||
return this.getFloat("settings.attackRatio", 0.2);
|
||||
}
|
||||
|
||||
setAttackRatio(value: number): void {
|
||||
this.setFloat("settings.attackRatio", value);
|
||||
}
|
||||
|
||||
keybinds(): string {
|
||||
return this.getString("settings.keybinds", "");
|
||||
}
|
||||
|
||||
setKeybinds(value: string): void {
|
||||
this.setString("settings.keybinds", value);
|
||||
}
|
||||
|
||||
soundEffectsVolume(): number {
|
||||
return this.getFloat("settings.soundEffectsVolume", 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user