Table of Contents

Class ClientSettings

Namespace
FishMMO.Client
Assembly
FishMMO.Client.dll

The client's single owner of GlobalSettings: it creates the store, names every key exactly once, clamps everything read out of it, and owns the one debounced write that puts it back on disk.

public static class ClientSettings
Inheritance
ClientSettings
Inherited Members

Remarks

Why one owner. The store used to be created lazily by whichever of two unrelated places asked for it first — LauncherSettings.EnsureLoaded in the launcher scene, and UITKOptions.EnsureConfigurationLoaded the first time the player opened the settings panel. In a client started past the launcher, neither ran, so Configuration.GlobalSettings stayed null through the whole of boot: keybinding overrides were skipped without a word (PlayerInputController.LoadBindingOverrides returns early on a null store), panel positions were never restored, and the theme loaded from nothing. Every one of those failures looked like a setting that had not been saved rather than one that had never been read.

Why the keys live here. A configuration key is a string shared between the control that writes it and the code that applies it, and the two are usually in different files. Naming them in one place is what makes it impossible for a panel to write "Brightness" while the applier reads "brightness" — and lets a setting be applied at boot by code that has never heard of the options panel.

Why every read clamps. Configuration.cfg is a plain text file: a player can edit it, a crash can truncate it mid-write, and a build from a different machine can leave values this one cannot honour. Values reach RenderSettings.ambientLight, Screen.fullScreenMode and AudioListener.volume, none of which validate what they are given. Clamping on the way in is the only place it can be done once.

Why writes are debounced. Save() serialises and rewrites the entire file. A slider bound straight to it rewrites the file once per frame for as long as it is held. Everything here coalesces onto a short quiet period and is flushed on panel close, on quit, and on demand.

And why there is exactly one of them. UITKPanelPositions used to keep a second pending flag and a second deadline over the same file. Both wrote the whole of it, so a player who dragged a window and changed a setting in the same breath got two full serialisations of identical content — and each timer could fire inside the other's quiet period, which is the case a debounce exists to prevent. The two intervals also disagreed, a second against three quarters of one, so which of two simultaneous changes reached the disk first depended on which subsystem had last been touched. Panel positions now request and flush through here like every other setting.

Fields

AudioMuteUnfocusedKey

Configuration key for muting the client while its window is not focused.

public const string AudioMuteUnfocusedKey = "Audio.MuteWhenUnfocused"

Field Value

string

AudioVolumePrefix

Configuration key prefix for the per-channel audio volumes.

public const string AudioVolumePrefix = "Audio.Volume."

Field Value

string

Remarks

The full key is this prefix followed by the AudioChannel name.

BrightnessKey

Configuration key for the brightness setting.

public const string BrightnessKey = "Brightness"

Field Value

string

FrameRateKey

Configuration key for the render frame rate cap, in frames per second.

public const string FrameRateKey = "Frame Rate Limit"

Field Value

string

FullscreenKey

Configuration key for the fullscreen mode, stored as the UnityEngine.FullScreenMode value.

public const string FullscreenKey = "Fullscreen"

Field Value

string

GameplayToggles

Every gameplay toggle: its configuration key, the label the options panel shows, and the value a fresh install gets.

public static readonly (string Key, string Label, bool Default)[] GameplayToggles

Field Value

(string Key, string Label, bool Default)[]

Remarks

The default belongs here rather than at each call site, because the panel and the code that acts on a setting have to agree about it. They did not: ClientCombatDisplay treated a missing key as false while the panel showed the same missing key as a ticked box, so a fresh install displayed no damage numbers while its own settings screen said it did — and ticking the box off and on again was the only way to make the display match the UI.

IgnoreGuildInvitesKey

Configuration key for suppressing guild invitations.

public const string IgnoreGuildInvitesKey = "IgnoreGuildInvites"

Field Value

string

IgnorePartyInvitesKey

Configuration key for suppressing party invitations.

public const string IgnorePartyInvitesKey = "IgnorePartyInvites"

Field Value

string

InputBindingOverridesKey

Configuration key for the keybinding override blob written by the input system.

public const string InputBindingOverridesKey = "InputBindingOverrides"

Field Value

string

MapShowCoordinatesKey

Configuration key for whether the maps show numeric coordinates.

public const string MapShowCoordinatesKey = "Map.ShowCoordinates"

Field Value

string

MaximumUIScale

Largest interface scale offered, as a multiplier of the authored size.

public const float MaximumUIScale = 1.5

Field Value

float

MinimapFrameRateDefault

Default minimap refresh, in frames per second.

public const int MinimapFrameRateDefault = 30

Field Value

int

MinimapFrameRateKey

Configuration key for how many times a second the minimap is re-rendered.

public const string MinimapFrameRateKey = "Map.MinimapFrameRate"

Field Value

string

Remarks

Exposed to the player because it is a real, adjustable cost: the minimap is an extra overhead render of the world, and a machine that is struggling gets a meaningful frame back by dropping it to fifteen. Thirty is the default because below that the map visibly stutters as the character moves, and above it nothing looks better.

MinimapFrameRateMaximum

Fastest minimap refresh the player may choose, in frames per second.

public const int MinimapFrameRateMaximum = 60

Field Value

int

Remarks

Capped rather than left open. Beyond sixty the setting buys nothing — the UI cannot present frames faster than it draws them — and it turns a performance control into a way to spend the frame budget on a two-hundred-pixel square.

MinimapFrameRateMinimum

Slowest minimap refresh the player may choose, in frames per second.

public const int MinimapFrameRateMinimum = 5

Field Value

int

MinimapRotatesKey

Configuration key for whether the minimap turns with the character.

public const string MinimapRotatesKey = "Map.MinimapRotates"

Field Value

string

MinimapZoomKey

Configuration key for the minimap's zoom, as the view's half-extent in metres.

public const string MinimapZoomKey = "Map.MinimapZoom"

Field Value

string

MinimumUIScale

Smallest interface scale offered, as a multiplier of the authored size.

public const float MinimumUIScale = 0.75

Field Value

float

QualityLevelKey

Configuration key for the quality level, stored as its name rather than its index.

public const string QualityLevelKey = "Quality Level"

Field Value

string

Remarks

The name and not the index: QualitySettings levels can be reordered or inserted between builds, and an index saved against the old order silently selects a different level. A name that no longer exists is simply ignored.

RefreshRateKey

Configuration key for the display refresh rate, in hertz.

public const string RefreshRateKey = "Refresh Rate"

Field Value

string

ResolutionHeightKey

Configuration key for the resolution height setting.

public const string ResolutionHeightKey = "Resolution Height"

Field Value

string

ResolutionWidthKey

Configuration key for the resolution width setting.

public const string ResolutionWidthKey = "Resolution Width"

Field Value

string

ShowAchievementsKey

Configuration key for showing achievement completion popups.

public const string ShowAchievementsKey = "ShowAchievementCompletion"

Field Value

string

Remarks

The key is ShowAchievementCompletion and not ShowAchievements, which is what the options panel used to write. Nothing read that name: the only consumer, ClientCombatDisplay, has always read ShowAchievementCompletion, so the toggle wrote one key and the popups read another and the setting did nothing at all. Naming it once, here, is what stops that recurring.

ShowDamageKey

Configuration key for showing floating damage numbers.

public const string ShowDamageKey = "ShowDamage"

Field Value

string

ShowHealsKey

Configuration key for showing floating healing numbers.

public const string ShowHealsKey = "ShowHeals"

Field Value

string

UIScaleKey

Configuration key for the interface scale multiplier.

public const string UIScaleKey = "UI.Scale"

Field Value

string

VSyncKey

Configuration key for the VSync setting.

public const string VSyncKey = "VSync"

Field Value

string

Properties

IsLoaded

True when the configuration store has been created and loaded.

public static bool IsLoaded { get; }

Property Value

bool

MapShowCoordinates

Whether the maps show numeric coordinates, subject to the Cartography tier.

public static bool MapShowCoordinates { get; }

Property Value

bool

MinimapFrameRate

How many times a second the minimap is re-rendered, clamped into range.

public static int MinimapFrameRate { get; }

Property Value

int

MinimapRotates

Whether the minimap turns with the character rather than staying north-up.

public static bool MinimapRotates { get; }

Property Value

bool

UIScale

The interface scale multiplier the player has chosen.

public static float UIScale { get; set; }

Property Value

float

Methods

ApplyAll()

Applies every setting that has a global effect, from the client's boot phase.

public static void ApplyAll()

Remarks

This is what makes a saved setting take effect without the player opening the options panel. Before it existed, the panel's own OnStarting was the only code that applied VSync, brightness or the frame-rate cap — and that panel ships closed, so its OnStarting did not run until the player opened it. A player who had capped their frame rate got the bootstrap default every session until they visited the menu.

Each step is isolated. Boot must complete even if one setting cannot be applied on this hardware; a client that fails to start because of a display mode is unrecoverable without hand-editing the file it failed on.

EnsureLoaded()

Creates and loads GlobalSettings if nothing has yet.

public static bool EnsureLoaded()

Returns

bool

True when a usable store exists afterwards.

Remarks

Safe to call from anywhere, at any point, any number of times — which is the point. Callers that merely need the store to exist should call this rather than constructing one, because a second Configuration pointed at the same file is how two halves of the client end up disagreeing about what the player chose.

A file that is present but unreadable is deliberately not overwritten. Only a genuinely absent file gets defaults written back; a load that failed for any other reason — a locked file, a permissions problem — leaves whatever is on disk alone, so a transient error cannot destroy a player's settings.

Flush()

Writes the configuration to disk immediately, if anything is owed.

public static void Flush()

Remarks

Not in the editor: GetWorkingDirectory() resolves to the repository root there rather than to an install directory, so a play-mode session would rewrite the developer's checked-out configuration. The in-memory values still apply, so settings behave normally while playing in the editor; only the cross-session part is skipped.

WebGL does write. It used to be excluded alongside the editor, which meant a browser client applied every setting correctly and persisted none of them. GetWorkingDirectory() already resolves to Application.persistentDataPath there, which is a real writable filesystem — it is just backed by IndexedDB rather than by a disk, and reaches IndexedDB only when the mount is synced. Sync() is what does that, and is an empty method everywhere else.

GetBool(string, bool)

Reads a boolean setting, or its fallback when the store is unavailable.

public static bool GetBool(string key, bool fallback)

Parameters

key string
fallback bool

Returns

bool

GetFloat(string, float, float, float)

Reads a float setting, clamped into range and with non-finite values rejected.

public static float GetFloat(string key, float fallback, float minimum, float maximum)

Parameters

key string
fallback float
minimum float
maximum float

Returns

float

Remarks

NaN is checked separately because it compares false against every bound — Mathf.Clamp passes it straight through, and a NaN reaching a slider or a colour channel corrupts everything downstream of it.

GetGameplayToggle(string)

Reads a gameplay toggle, using the default declared in GameplayToggles.

public static bool GetGameplayToggle(string key)

Parameters

key string

One of the keys in GameplayToggles.

Returns

bool

GetInt(string, int)

Reads an integer setting, or its fallback when the store is unavailable.

public static int GetInt(string key, int fallback)

Parameters

key string
fallback int

Returns

int

GetString(string, string)

Reads a string setting, or its fallback when the store is unavailable.

public static string GetString(string key, string fallback)

Parameters

key string
fallback string

Returns

string

Pump()

Flushes an owed write once its quiet period has elapsed.

public static void Pump()

Remarks

Driven once per frame by FishMMO.Client.ClientSettingsPump. The early-out is a single bool read.

Remove(string)

Deletes a setting and schedules a debounced save.

public static bool Remove(string key)

Parameters

key string

The setting to remove.

Returns

bool

True when the setting existed and was removed.

Remarks

A save is scheduled only when something actually changed. Panel positions are cleared one key at a time across every registered panel, and requesting a write for each of the forty that had nothing stored is forty pointless requests — each of which pushes the debounce deadline further out and so delays the write that is actually owed.

RequestSave()

Marks the configuration as owing a write, to be flushed once the player settles.

public static void RequestSave()

Remarks

Each call pushes the deadline out, so a slider held for a minute writes once, when it is released, rather than sixty times a second. The pump that watches that deadline is created here rather than assumed to exist — see FishMMO.Client.ClientSettingsPump — so owing a write is what guarantees something is running to discharge it.

SetGameplayToggle(string, bool)

Writes a gameplay toggle, schedules a save, and notifies consumers.

public static void SetGameplayToggle(string key, bool value)

Parameters

key string
value bool

SetString(string, string)

Writes a string setting and schedules a debounced save.

public static void SetString(string key, string value)

Parameters

key string
value string

Set<T>(string, T)

Writes a setting and schedules a debounced save.

public static void Set<T>(string key, T value)

Parameters

key string
value T

Type Parameters

T

Remarks

Floats are safe through this generic path: Set<T>(string, T) formats every value with the invariant culture and gives float and double the round-trip format specifically. That is load-bearing rather than tidy — the readers above all parse invariantly, so a value formatted with the machine's own locale came back as a different number on any comma-decimal system.

Events

OnGameplayChanged

Raised when any gameplay toggle changes.

public static event Action OnGameplayChanged

Event Type

Action

Remarks

Consumers cache these values — they are read on the client's hottest path, once per damage event per character in view — and a cache with nothing to invalidate it is a setting that appears not to work until the client is restarted.