namespace AcDream.UI.Abstractions.Panels.Settings; /// /// Chat-related preferences persisted to settings.json. Mixes /// retail's CharacterOptions2 chat-channel filter bits (Hear*Chat /// + TimeStamp + FilterLanguage + AppearOffline) with a few visual /// preferences (font size) that don't have a retail bitfield. /// See docs/research/named-retail/acclient.h:3451+ for the /// retail bit values. /// /// /// Campaign CH slice CH3 (2026-08-09): 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 /// RuntimeCharacterOptionsState.Options2 (already parsed out of /// PlayerDescription) whenever a fresh description lands, and Save publishes /// any changed bit through SetSingleCharacterOption (0x0005) — /// RuntimeSettingsController.SyncChatFromServerOptions / /// SaveChat. There is no sixth toggle for /// HearAllegianceChat (CharacterOptions1 0x40000000): retail's own /// Settings UI does not expose one either — Allegiance chat membership rides /// allegiance membership, not a standalone preference. /// /// /// /// N5 (CH3 Opus review, 2026-08-09): the server-backed sync/publish /// wiring above covers ONLY the five Hear*Chat fields. /// , , and /// each correspond to a real retail /// CharacterOptions2 bit (per the field comments below) but are /// deliberately NOT wired to SyncChatFromServerOptions or /// SaveChat's SetSingleCharacterOption 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 settings.json alone. /// /// 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) { /// /// N4 (CH3 Opus review): matches ACE's ACTUAL /// CharacterOptions2.Default (0x00948700) stance for the five /// server-backed Hear*Chat bits — General/Trade/LFG are on, but /// Roleplay (0x800) and Society (0x80000) 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 (SyncChatFromServerOptions) remains /// authoritative regardless of this constant — this is only the /// pre-login / never-connected starting value. /// public static ChatSettings Default { get; } = new( HearGeneralChat: true, HearTradeChat: true, HearLFGChat: true, HearRoleplayChat: false, HearSocietyChat: false, AppearOffline: false, ShowTimestamps: true, FilterProfanity: true, FontSize: 12f); }