Binds the retail Options panel's Config tab (LayoutDesc 0x21000029, 27 authored rows across 6 sections) through OP2's template mechanism and OP3's per-page OptionPage model, matching the Character/Chat tab controllers' established pattern. The row table is transcribed directly from two decompiled sources — gmConfigUI::InitOptions @0x0049E400 (row order, widget shape, defaults) and gmClient::InitUIPreferences @0x004035b0 (the complete UIPreferences::AttachPreference registration: every label/tooltip key, every slider's real-unit range, every menu's enum choices) — which resolves the research docs' own "U4" unverified slider-caption pairing: retail ships ZERO range captions on this tab (every SetSliderLabel call passes literal string id 0). Consumer disposition: LIVE — Sound/Ambient volume-trio sliders and their toggle halves (AudioSettings.SfxDisabled/AmbientDisabled now gate the already-live engine write; RuntimeSettingsController.SaveAudio newly pushes into OpenAlAudioEngine on every change, not just at startup), Resolution/Full Screen (immediate window resize on save). NEXT-LAUNCH (pre-existing precedent): Sync To Refresh, Field of View. STORE-ONLY (register rows AP-198/199/200, TS-74 extended): Sound Features/Interface trio/Play-Only-When-Active, the nine Graphics/Rendering-Quality rows (Vulkan has no per-feature render knobs), Camera/Input's six rows and Use Mouse Turning (no persistent mouse-turning camera mode), Chat Font Face/Size (distinct new fields from the existing live ChatSettings.FontSize). AudioSettings/DisplaySettings/CameraTurningSettings/ChatSettings each gain new fields for their slice of the 27 rows, backed by SettingsStore round-trips. A real bug caught by testing: the scrollbar scope lookup used the standalone-layout root id (0x100001FF), which does not survive base-merge into the host-mounted tree — fixed to scope from the tab host's own page-slot id (0x10000213), matching Chat's established pattern for the same shared-scrollbar-id hazard (0x10000201, authored by both the Chat and Config ListBoxes). 30 new tests (27 authored rows register as 30 IOptionRow instances — the three toggle+slider trios each register two). Full Release suite: 13,107 passed / 4 skipped / 0 failed (was 13,083/4/0 — net +24, the one existing RuntimeSettingsControllerTests case updated for SaveAudio's new live-apply call, not a regression). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
129 lines
7.5 KiB
C#
129 lines
7.5 KiB
C#
namespace AcDream.UI.Abstractions.Panels.Settings;
|
|
|
|
/// <summary>
|
|
/// Chat-related preferences persisted to <c>settings.json</c>. Mixes
|
|
/// retail's <c>CharacterOptions2</c> chat-channel filter bits (Hear*Chat
|
|
/// + TimeStamp + FilterLanguage + AppearOffline) with a few visual
|
|
/// preferences (font size) that don't have a retail bitfield.
|
|
/// See <c>docs/research/named-retail/acclient.h:3451+</c> for the
|
|
/// retail bit values.
|
|
///
|
|
/// <para>
|
|
/// <b>Campaign CH slice CH3 (2026-08-09):</b> these five Hear*Chat flags
|
|
/// are no longer local-only display filters — they ARE retail's server-side
|
|
/// Turbine room membership. The App host seeds this draft from
|
|
/// <c>RuntimeCharacterOptionsState.Options2</c> (already parsed out of
|
|
/// PlayerDescription) whenever a fresh description lands, and Save publishes
|
|
/// any changed bit through <c>SetSingleCharacterOption (0x0005)</c> —
|
|
/// <c>RuntimeSettingsController.SyncChatFromServerOptions</c> /
|
|
/// <c>SaveChat</c>. There is no sixth toggle for
|
|
/// <c>HearAllegianceChat (CharacterOptions1 0x40000000)</c>: retail's own
|
|
/// Settings UI does not expose one either — Allegiance chat membership rides
|
|
/// allegiance membership, not a standalone preference.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// N5 (CH3 Opus review, 2026-08-09): the server-backed sync/publish
|
|
/// wiring above covers ONLY the five Hear*Chat fields.
|
|
/// <see cref="AppearOffline"/>, <see cref="ShowTimestamps"/>, and
|
|
/// <see cref="FilterProfanity"/> each correspond to a real retail
|
|
/// <c>CharacterOptions2</c> bit (per the field comments below) but are
|
|
/// deliberately NOT wired to <c>SyncChatFromServerOptions</c> or
|
|
/// <c>SaveChat</c>'s <c>SetSingleCharacterOption</c> publish — they stay
|
|
/// local-only display preferences. Do not extend the sync/publish pair to
|
|
/// them without a corresponding design decision; today they are read and
|
|
/// written from <c>settings.json</c> alone.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed record ChatSettings(
|
|
// CharacterOptions2 (32-bit) channel filters.
|
|
bool HearGeneralChat, // 0x100 — General channel
|
|
bool HearTradeChat, // 0x200 — Trade channel
|
|
bool HearLFGChat, // 0x400 — LFG channel
|
|
bool HearRoleplayChat, // 0x800 — RP channel
|
|
bool HearSocietyChat, // 0x80000 — Society chat (CD/EW/RB)
|
|
bool AppearOffline, // 0x1000 — hide /who status
|
|
bool ShowTimestamps, // 0x40 — TimeStamp prefix on chat lines
|
|
bool FilterProfanity, // 0x20000 — FilterLanguage (Turbine's profanity filter)
|
|
// Visual / UX (no retail bitfield).
|
|
float FontSize, // chat panel font, 10..20 pt
|
|
// Campaign CH slice CH6b: local-only persistence of the four floating
|
|
// chat windows' 64-bit text-type filters
|
|
// (AcDream.Core.Chat.ChatWindowState — retail's per-window
|
|
// 0x1000007F option, docs/research/2026-08-09-chat-retail-color-table.md
|
|
// §4). Retail persists these server-side inside the opaque
|
|
// 0x1000008C GameplayOptions blob (window-shell research doc §4.4);
|
|
// acdream has no writer for that blob yet, so these fields are the
|
|
// interim local store, with default values matching retail's own
|
|
// ChatInterface::PostInit @0x004F3DD0 seed exactly. Trailing with
|
|
// defaults so no existing positional/named ChatSettings construction
|
|
// site needed to change.
|
|
ulong ChatWindow1Filter = 0x0000101Cu, // Speech, Tell, Speech_Direct_Send, Emote
|
|
ulong ChatWindow2Filter = 0x00040C00u, // Social, Social_Send, Allegiance
|
|
ulong ChatWindow3Filter = 0x00080000u, // Fellowship
|
|
ulong ChatWindow4Filter = 0x78000000u, // Turbine General/Trade/LFG/Roleplay
|
|
// Campaign OP slice OP5: the MAIN chat window's own filter — CH6a/b's
|
|
// ChatWindowState.SetFilter already accepted window id 0 as a real write
|
|
// (its class doc: "Retail's main window IS user-settable through the
|
|
// options page; only the settings UI to drive this is future work") but
|
|
// nothing persisted it until the Chat tab's main-window filter block
|
|
// shipped. Default matches ChatWindowState.MainWindowDefaultFilter.
|
|
ulong ChatWindowMainFilter = 0xFBFFFFFFu,
|
|
// Campaign CH slice CH6c: retail's two GLOBAL window-opacity options
|
|
// (Option_DefaultOpacity_Property 0x10000080 / Option_ActiveOpacity_Property
|
|
// 0x10000081, docs/research/2026-08-09-chat-retail-window-shell.md §3).
|
|
// DefaultOpacity applies while a window's descendant does NOT have keyboard
|
|
// focus; ActiveOpacity while it does. Always active >= default — enforced by
|
|
// ChatOpacityLink at every setter, not by clamping here. acdream applies this
|
|
// GLOBALLY to every RetailWindowManager-registered window (register row
|
|
// AP-190), where retail scopes it to ChatInterface-derived windows only.
|
|
//
|
|
// CH6c review fix (2026-08-10): the base ChatInterface ctor's 0.5/1.0 pair
|
|
// is retail-correct ONLY for the four floating chat windows —
|
|
// gmMainChatUI overrides to 1.0/1.0 (0x004CD0F0), and every other
|
|
// RetailWindowManager-registered window (radar, vitals, toolbar, ...) has
|
|
// no retail opacity fade at all, so applying 0.5 to them out of the box
|
|
// rendered the whole registered UI half-transparent forever, including
|
|
// several windows that can never take keyboard focus and so were
|
|
// PERMANENTLY stuck at 0.5. DefaultOpacity now ships 1.0, matching
|
|
// retail-identical opaque presentation for the 11 non-chat windows and
|
|
// the main chat window; the four floating chat windows lose their
|
|
// retail 0.5-while-idle fade by default, but the Settings → Chat
|
|
// transparency slider remains fully user-settable (AP-190).
|
|
float DefaultOpacity = 1.0f,
|
|
float ActiveOpacity = 1.0f,
|
|
// Campaign OP slice OP6: the Config tab's "UI Options" section —
|
|
// UI_ChatFontFace (retail Windows TrueType face name, enum choices
|
|
// starting "Arial") and UI_ChatFontSize (index into
|
|
// Tiny/Small/Medium/Large/XLarge, gmClient::InitUIPreferences
|
|
// @0x0040387b/@0x00403a1a). Deliberately NOT the same field as
|
|
// FontSize above: FontSize is acdream's own live 10..20pt render
|
|
// knob with no verified index-to-point mapping to retail's five-tier
|
|
// enum, and acdream's text rendering has no arbitrary system-font-face
|
|
// swap (DAT-baked/bitmap fonts only) — both new fields are honest
|
|
// store-only round-trips (register row, OP6).
|
|
int ChatFontFace = 2,
|
|
int ChatFontSizeIndex = 1)
|
|
{
|
|
/// <summary>
|
|
/// N4 (CH3 Opus review): matches ACE's ACTUAL
|
|
/// <c>CharacterOptions2.Default (0x00948700)</c> stance for the five
|
|
/// server-backed Hear*Chat bits — General/Trade/LFG are on, but
|
|
/// Roleplay (<c>0x800</c>) and Society (<c>0x80000</c>) are OFF (research
|
|
/// doc §5.2). The prior "matching the retail 'all on' stance" comment
|
|
/// here was wrong: two of the five synced flags are off by default. The
|
|
/// server reseed at login (<c>SyncChatFromServerOptions</c>) remains
|
|
/// authoritative regardless of this constant — this is only the
|
|
/// pre-login / never-connected starting value.
|
|
/// </summary>
|
|
public static ChatSettings Default { get; } = new(
|
|
HearGeneralChat: true,
|
|
HearTradeChat: true,
|
|
HearLFGChat: true,
|
|
HearRoleplayChat: false,
|
|
HearSocietyChat: false,
|
|
AppearOffline: false,
|
|
ShowTimestamps: true,
|
|
FilterProfanity: true,
|
|
FontSize: 12f);
|
|
}
|