Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## v3.24.49 — EQ Client Settings overhaul (Phases 2–4, 6): four more sub-windows on the shared engine + MaxFPS fix (2026-06-06)

**Continues the v3.24.48 overhaul. Four of the five EQ Client Settings sub-windows — Chat Spam, Luclin Models, Particles, and Key Mappings — now read and write `eqclient.ini` through the same single descriptor engine as the main window.** So they show your real current values (including changes you made in-game), save only the controls you actually change, and no longer overwrite your settings on every launch. (Video Mode is the last window still on its own code — it follows next; all six windows stay labelled "— EXPERIMENTAL" until the rebuild finishes. Nothing is broken in the meantime.)

**Chat Spam / Models / Particles / Keymaps — now well-behaved:**
- **Display = a live read** of `eqclient.ini` every time the window opens, so in-game changes are always reflected.
- **Save = only the controls you changed**, each written to one section — no duplicate/ghost entries, no clobbering settings you didn't touch.
- **No more launch re-stamp** — settings you change in-game survive (eqgame wins). Chat Spam in particular used to rewrite all 22 filters on every save *and* every launch; that's fixed.

**Under the hood:** the read / save / touch-gate logic is now one shared engine (`EqClientBindings`) that every window binds onto, instead of five hand-synced copies that could silently drift — the entire point of the overhaul. Net result is roughly 400 fewer lines across the forms.

**MaxFPS fix completed:** v3.24.48 raised the MaxFPS/MaxBGFPS cap 99→999 in only two of six places, so `MaxFPS=100` (EQ's stock default) silently snapped back to 99 on the next app start — or the instant you opened the Process Manager. Every cap site now reads one `EqClientIniSchema.MaxFpsCap` constant, so the value sticks. (Also: the now-vestigial `AANoConfirm` first-run import was aligned to the corrected polarity.)

Verified: clean build; six self-tests pass (`--test-eqclient-schema` / `-inidoc` / `-save` / `-enforce` / `-chatspam-save` / `-particles-save`); the DPI-baseline guard passes; all six EQ Client Settings windows render correctly off-screen.

## v3.24.48 — EQ Client Settings overhaul Phase 1: main window on a single descriptor engine (2026-06-06)

**The 6-window EQ Client Settings subsystem is being rebuilt onto one declarative descriptor table so the UI can't drift from `eqclient.ini`, leave ghost (duplicate) keys, or clobber settings you changed in-game. This release lands the foundation + the main window. The five sub-forms (Models, Chat Spam, Particles, Video Mode, Keymaps) are unchanged and fully functional — their rebuild is Phases 2–6 — and all six window titles now read "— EXPERIMENTAL" while the rebuild is in progress.**
Expand Down
25 changes: 13 additions & 12 deletions Config/AppConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,12 +666,13 @@ static string NormQuickLogin(string? value, ref bool mut)
if (EQClientIni.CPUAffinitySlots is not { Length: 6 }) EQClientIni.CPUAffinitySlots = new[] { 1, 2, 3, 1, 2, 3 };
for (int i = 0; i < 6; i++)
EQClientIni.CPUAffinitySlots[i] = Math.Clamp(EQClientIni.CPUAffinitySlots[i], 0, 31);
// v3.24.28: cap FPS authoritatively. Both UI forms cap at 99 but the JSON
// config path was unclamped — a hand-edited/legacy MaxFPS>=100 would write to
// eqclient.ini despite the UI showing 99 (UI-vs-backend drift the verifier
// pass flagged). 0 = "don't set" sentinel preserved (clamp floor is 0).
EQClientIni.MaxFPS = Math.Clamp(EQClientIni.MaxFPS, 0, 99);
EQClientIni.MaxBGFPS = Math.Clamp(EQClientIni.MaxBGFPS, 0, 99);
// v3.24.49: cap FPS authoritatively against the single schema ceiling. Fresh EQ ships
// MaxFPS=100; the historical 99 here (and in ProcessManager + SeedFromIni) silently snapped
// the stock value back to 99 on every load — even after v3.24.48 raised the EQ-Client NUD +
// schema to 999. All cap sites now reference EqClientIniSchema.MaxFpsCap (one source, no drift).
// 0 = "don't set" sentinel preserved (clamp floor is 0).
EQClientIni.MaxFPS = Math.Clamp(EQClientIni.MaxFPS, 0, EqClientIniSchema.MaxFpsCap);
EQClientIni.MaxBGFPS = Math.Clamp(EQClientIni.MaxBGFPS, 0, EqClientIniSchema.MaxFpsCap);

// LoginAccount field validation (legacy — operates on v3 LegacyAccounts list).
// v4 Account type has no CharacterSlot field; per-character slot moves to
Expand Down Expand Up @@ -1730,7 +1731,7 @@ public static EQClientIniConfig SeedFromIni(string iniPath)
cfg.RaidInviteConfirm = val == "1";
break;
case "aanoconfirm":
cfg.AANoConfirm = val == "0";
cfg.AANoConfirm = val == "1"; // 1 = no-confirm (schema-aligned; was inverted "0")
break;
case "chatserverport":
cfg.DisableChatServer = val == "0";
Expand All @@ -1756,11 +1757,11 @@ public static EQClientIniConfig SeedFromIni(string iniPath)
break;
case "maxfps":
if (int.TryParse(val, out int fps))
cfg.MaxFPS = Math.Clamp(fps, 0, 99);
cfg.MaxFPS = Math.Clamp(fps, 0, EqClientIniSchema.MaxFpsCap);
break;
case "maxbgfps":
if (int.TryParse(val, out int bgfps))
cfg.MaxBGFPS = Math.Clamp(bgfps, 0, 99);
cfg.MaxBGFPS = Math.Clamp(bgfps, 0, EqClientIniSchema.MaxFpsCap);
break;
case "maximized":
cfg.MaximizeWindow = val == "1";
Expand Down Expand Up @@ -1805,11 +1806,11 @@ public static EQClientIniConfig SeedFromIni(string iniPath)
break;
case "maxfps":
if (int.TryParse(val, out int optFps))
cfg.MaxFPS = Math.Clamp(optFps, 0, 99);
cfg.MaxFPS = Math.Clamp(optFps, 0, EqClientIniSchema.MaxFpsCap);
break;
case "maxbgfps":
if (int.TryParse(val, out int optBgfps))
cfg.MaxBGFPS = Math.Clamp(optBgfps, 0, 99);
cfg.MaxBGFPS = Math.Clamp(optBgfps, 0, EqClientIniSchema.MaxFpsCap);
break;
case "lootallconfirm":
cfg.DisableLootAllConfirm = val == "0";
Expand All @@ -1818,7 +1819,7 @@ public static EQClientIniConfig SeedFromIni(string iniPath)
cfg.RaidInviteConfirm = val == "1";
break;
case "aanoconfirm":
cfg.AANoConfirm = val == "0";
cfg.AANoConfirm = val == "1"; // 1 = no-confirm (schema-aligned; was inverted "0")
break;
case "chatserverport":
cfg.DisableChatServer = val == "0";
Expand Down
13 changes: 11 additions & 2 deletions Config/EqClientIniSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ public string NumberToIni(decimal v)
/// </summary>
public static class EqClientIniSchema
{
/// <summary>
/// The ONE FPS ceiling for MaxFPS/MaxBGFPS — referenced by the schema rows below, both UI NUDs
/// (EQ Client Settings + Process Manager), AppConfig.Validate, and SeedFromIni. Fresh EQ ships
/// MaxFPS=100, so the historical 99 cap mis-clamped the stock default; v3.24.48 raised it in only
/// 2 of 6 places, so a round-trip still snapped 100→99. One definition kills that §8-F drift class.
/// EQ tolerates far higher; 999 is a sane UI ceiling.
/// </summary>
public const int MaxFpsCap = 999;

/// <summary>Sentinel used for the dual-section MaxFPS/MaxBGFPS (canonical [Options], mirror [Defaults]).</summary>
private static readonly string[] MirrorDefaults = { "Defaults" };

Expand Down Expand Up @@ -174,8 +183,8 @@ public static class EqClientIniSchema

// ───────────────────────── MAIN FORM — Performance (6) ─────────────────────────
// MaxFPS/MaxBGFPS: canonical [Options] (EQ runtime), mirror [Defaults] (legacy). Also edited in ProcessManager (§8 F).
IniSetting.Number("MaxFPS", "Options", Bucket.UserPref, min: 0m, max: 999m, decimals: 0, def: "80", "MaxFPS", skipBelow: 1m, meaning: "foreground FPS cap; 0 = don't set. Max 999 — fresh EQ ships MaxFPS=100, so a 99 cap mis-displayed the ship default.", mirrors: MirrorDefaults),
IniSetting.Number("MaxBGFPS", "Options", Bucket.UserPref, min: 0m, max: 999m, decimals: 0, def: "80", "MaxBGFPS", skipBelow: 1m, meaning: "background FPS cap; 0 = don't set. Max 999 (was 99).", mirrors: MirrorDefaults),
IniSetting.Number("MaxFPS", "Options", Bucket.UserPref, min: 0m, max: MaxFpsCap, decimals: 0, def: "80", "MaxFPS", skipBelow: 1m, meaning: "foreground FPS cap; 0 = don't set. Cap = MaxFpsCap — fresh EQ ships MaxFPS=100, so a 99 cap mis-displayed the ship default.", mirrors: MirrorDefaults),
IniSetting.Number("MaxBGFPS", "Options", Bucket.UserPref, min: 0m, max: MaxFpsCap, decimals: 0, def: "80", "MaxBGFPS", skipBelow: 1m, meaning: "background FPS cap; 0 = don't set. Cap = MaxFpsCap (was 99).", mirrors: MirrorDefaults),
IniSetting.Number("MouseSensitivity", "Options", Bucket.UserPref, min: -1m, max: 100m, decimals: 0, def: "5", "Mouse", skipBelow: 0m, meaning: "-1 = don't set"),
IniSetting.Number("ClipPlane", "Options", Bucket.UserPref, min: 0m, max: 999m, decimals: 0, def: "14", "Clip", skipBelow: 1m, meaning: "view distance; EQ default 14"),
IniSetting.Number("ShadowClipPlane", "Options", Bucket.UserPref, min: 0m, max: 999m, decimals: 0, def: "35", "Shadow", skipBelow: 1m, meaning: "EQ default 35"),
Expand Down
2 changes: 1 addition & 1 deletion EQSwitch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ApplicationIcon>eqswitch.ico</ApplicationIcon>
<AssemblyName>EQSwitch</AssemblyName>
<RootNamespace>EQSwitch</RootNamespace>
<Version>3.24.48</Version>
<Version>3.24.49</Version>
<Description>EverQuest Multiboxing Window Manager for Shards of Dalaya</Description>
<Authors>itsnateai</Authors>
<Copyright>© itsnateai</Copyright>
Expand Down
42 changes: 42 additions & 0 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,48 @@ static void Main(string[] args)
return;
}

// --test-eqclient-chatspam-save — run UI/DiagRender.RunEqChatSpamSaveRoundtrip(): constructs
// the Chat Spam window against a temp INI and proves Phase 2's touch-gated Save (only changed
// filters written; absent/untouched keys NOT inserted — i.e. no write-all; unmanaged preserved;
// no ghost). Phase 2 of the EQ Client Settings overhaul.
if (args.Length >= 1 && args[0] == "--test-eqclient-chatspam-save")
{
int exitCode;
try
{
exitCode = UI.DiagRender.RunEqChatSpamSaveRoundtrip();
}
catch (Exception ex)
{
Console.Error.WriteLine($"EqChatSpamSaveRoundtrip CRASHED: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine(ex.StackTrace);
exitCode = 2;
}
Environment.Exit(exitCode);
return;
}

// --test-eqclient-particles-save — run UI/DiagRender.RunEqParticlesSaveRoundtrip(): constructs
// the Particles window against a temp INI and proves Phase 4's touch-gated Save incl. the new
// engine slider↔float path (drag a slider -> 6-decimal write; absent/untouched NOT inserted;
// unmanaged preserved; no ghost). Phase 4 of the EQ Client Settings overhaul.
if (args.Length >= 1 && args[0] == "--test-eqclient-particles-save")
{
int exitCode;
try
{
exitCode = UI.DiagRender.RunEqParticlesSaveRoundtrip();
}
catch (Exception ex)
{
Console.Error.WriteLine($"EqParticlesSaveRoundtrip CRASHED: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine(ex.StackTrace);
exitCode = 2;
}
Environment.Exit(exitCode);
return;
}

// --test-font-dispose — run Core/FontDisposeOwnershipTests.RunAll(): asserts
// DisposeControlFonts frees only owned fonts, never inherited/Control.DefaultFont.
// Guards the button-click "Parameter is not valid" crash class from regressing.
Expand Down
Loading
Loading