chore: comments, second pass -- the task codes the first pass missed
The first sweep matched a character class that swallowed the digit, so a bare B1 slipped through while B1-2 was caught. Searching the whole A-Z space instead of guessing prefixes turned up 130-odd more: B0 through B6, C2, C3, D1, H2, M6, P7, P8, T2, W2, plus GP-04, KB-01, OD-1, PM-1, PM-3, SEC-01, TR-4, TR-7, UI-11, UI-12, XC-8 and API-3. Kept deliberately: 41 B4 01 is a byte signature, "N0" a format string, #L119-L128 a source anchor, LS4/LS6 are linkshells, and A=FF B=0C G=41 R=C2 explains a colour-channel order. Those look like codes and are not. Also translated the eight German comments left in the theme files and ImGuiUtil. Seven of them described what a palette does to which channel, which is worth reading -- just not in a second language in an otherwise English codebase.
This commit is contained in:
@@ -23,7 +23,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
private readonly ILogger<AutoTellTabsService> _logger;
|
private readonly ILogger<AutoTellTabsService> _logger;
|
||||||
|
|
||||||
// Tabs-list structure lock now lives on Plugin (neutral owner) so the
|
// Tabs-list structure lock now lives on Plugin (neutral owner) so the
|
||||||
// MessageManager refilter can share it. See Plugin.TabsListLock / B3.
|
// MessageManager refilter can share it. See Plugin.TabsListLock.
|
||||||
private object TabsListLock => _plugin.TabsListLock;
|
private object TabsListLock => _plugin.TabsListLock;
|
||||||
|
|
||||||
// Bumped whenever something wipes unpinned temp tabs wholesale (logout).
|
// Bumped whenever something wipes unpinned temp tabs wholesale (logout).
|
||||||
@@ -179,7 +179,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Three steps, because building the tab pulls history out of the store and
|
// Three steps, because building the tab pulls history out of the store and
|
||||||
// that must not happen under TabsListLock (B3 rule; the query sorts the whole
|
// that must not happen under TabsListLock (the query sorts the whole
|
||||||
// receiver history). Step 1 and 3 are locked, step 2 is not.
|
// receiver history). Step 1 and 3 are locked, step 2 is not.
|
||||||
int generation;
|
int generation;
|
||||||
lock (TabsListLock)
|
lock (TabsListLock)
|
||||||
@@ -303,7 +303,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
|
|
||||||
internal void DropOldestTempTab()
|
internal void DropOldestTempTab()
|
||||||
{
|
{
|
||||||
// B3: lock the list-structure ops so the (currently caller-less) Unpin path
|
// Lock the list-structure ops so the (currently caller-less) Unpin path
|
||||||
// can't race the worker; re-entrant when HandleTell already holds the lock.
|
// can't race the worker; re-entrant when HandleTell already holds the lock.
|
||||||
lock (TabsListLock)
|
lock (TabsListLock)
|
||||||
{
|
{
|
||||||
@@ -577,7 +577,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
|
|
||||||
// Count and flag under one lock so the cap can't be raced. SaveConfig stays
|
// Count and flag under one lock so the cap can't be raced. SaveConfig stays
|
||||||
// OUTSIDE -- holding TabsListLock across a save would put an fsync on the
|
// OUTSIDE -- holding TabsListLock across a save would put an fsync on the
|
||||||
// click path, which is what B6 just removed elsewhere.
|
// click path, which is what a later cycle just removed elsewhere.
|
||||||
lock (TabsListLock)
|
lock (TabsListLock)
|
||||||
{
|
{
|
||||||
if (PinnedTempTabCount >= MaxPinnedTempTabs)
|
if (PinnedTempTabCount >= MaxPinnedTempTabs)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace HellionChat;
|
namespace HellionChat;
|
||||||
|
|
||||||
// Reduced CJK fallback coverage for the v1.5.3 NotoSansCjk fallback merge (B1).
|
// Reduced CJK fallback coverage for the v1.5.3 NotoSansCjk fallback merge.
|
||||||
// Before B1 the fallback merged over the full `Ranges` array (Default + endonyms),
|
// Before that the fallback merged over the full `Ranges` array (Default + endonyms),
|
||||||
// duplicating the Latin/Default work already done by the global/Japanese fonts.
|
// duplicating the Latin/Default work already done by the global/Japanese fonts.
|
||||||
// This is the trimmed remainder the fallback is actually the sole source for:
|
// This is the trimmed remainder the fallback is actually the sole source for:
|
||||||
// - Hangul Syllables (AC00-D7A3): no other merged font ships Korean glyphs.
|
// - Hangul Syllables (AC00-D7A3): no other merged font ships Korean glyphs.
|
||||||
@@ -9,7 +9,7 @@ namespace HellionChat;
|
|||||||
// font is Inter-Light (no CJK), so the fallback is the SOLE Han source. The JpRange
|
// font is Inter-Light (no CJK), so the fallback is the SOLE Han source. The JpRange
|
||||||
// overlap is harmless (MergeMode: the Japanese font wins for shared kanji).
|
// overlap is harmless (MergeMode: the Japanese font wins for shared kanji).
|
||||||
// Deliberately excluded: ONLY the ASCII/Latin Default block (0x20-0xFF), which the
|
// Deliberately excluded: ONLY the ASCII/Latin Default block (0x20-0xFF), which the
|
||||||
// global font already owns -- that doubled Latin merge is the B1 waste being removed.
|
// global font already owns -- that doubled Latin merge is the waste being removed.
|
||||||
// Kept as plain start/end pairs so it is unit-testable without the unsafe ImGui
|
// Kept as plain start/end pairs so it is unit-testable without the unsafe ImGui
|
||||||
// glyph-range builder (mirrors FontSizeResolver's split-for-test rationale).
|
// glyph-range builder (mirrors FontSizeResolver's split-for-test rationale).
|
||||||
internal static class CjkFallbackRange
|
internal static class CjkFallbackRange
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class ConfigKeyBind
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
internal const int LatestVersion = 26;
|
internal const int LatestVersion = 27;
|
||||||
|
|
||||||
public int Version { get; set; } = LatestVersion;
|
public int Version { get; set; } = LatestVersion;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
// Global window opacity, applied across all themes.
|
// Global window opacity, applied across all themes.
|
||||||
public float WindowOpacity = 0.85f;
|
public float WindowOpacity = 0.85f;
|
||||||
|
|
||||||
// UI-12: background opacity of the main chat window while unfocused.
|
// Background opacity of the main chat window while unfocused.
|
||||||
// WindowOpacity above stays the focused value.
|
// WindowOpacity above stays the focused value.
|
||||||
public float WindowOpacityInactive = 0.65f;
|
public float WindowOpacityInactive = 0.65f;
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
public bool SeenPopOutHeaderHint;
|
public bool SeenPopOutHeaderHint;
|
||||||
public bool AutoTellTabsOpenAsPopout;
|
public bool AutoTellTabsOpenAsPopout;
|
||||||
|
|
||||||
// UI-7: how sender names are rendered in the chat log.
|
// How sender names are rendered in the chat log.
|
||||||
public WorldSuffixMode WorldSuffixMode = WorldSuffixMode.OtherWorldOnly;
|
public WorldSuffixMode WorldSuffixMode = WorldSuffixMode.OtherWorldOnly;
|
||||||
public NameFormMode NameFormMode = NameFormMode.Full;
|
public NameFormMode NameFormMode = NameFormMode.Full;
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
// Toast when a tell the user sent could not be delivered.
|
// Toast when a tell the user sent could not be delivered.
|
||||||
public bool NotifyFailedTell = true;
|
public bool NotifyFailedTell = true;
|
||||||
|
|
||||||
// UI-11: warn before sending a message that carries plugin-only glyphs.
|
// Warn before sending a message that carries plugin-only glyphs.
|
||||||
public bool NotifyPluginDisclosure = true;
|
public bool NotifyPluginDisclosure = true;
|
||||||
public bool KeepInputFocus = true;
|
public bool KeepInputFocus = true;
|
||||||
public bool Use24HourClock = true;
|
public bool Use24HourClock = true;
|
||||||
@@ -462,7 +462,7 @@ public class Tab
|
|||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
internal string? _cachedTellIcon;
|
internal string? _cachedTellIcon;
|
||||||
|
|
||||||
// PM-3 hover-lerp state. Default 0f means "not hovered". Sidebar
|
// hover-lerp state. Default 0f means "not hovered". Sidebar
|
||||||
// path animates per tab; card-mode-border path is tab-aggregate
|
// path animates per tab; card-mode-border path is tab-aggregate
|
||||||
// (any card-row hover ramps the alpha for all cards in this tab).
|
// (any card-row hover ramps the alpha for all cards in this tab).
|
||||||
// Lerp speed lives in the render loop, not here, so the same field
|
// Lerp speed lives in the render loop, not here, so the same field
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ public sealed class FontManager : IDisposable
|
|||||||
private ushort[] Ranges = [];
|
private ushort[] Ranges = [];
|
||||||
private ushort[] JpRange = [];
|
private ushort[] JpRange = [];
|
||||||
|
|
||||||
// B1: trimmed remainder the NotoSansCjk fallback is the sole source for
|
// Trimmed remainder the NotoSansCjk fallback is the sole source for
|
||||||
// (Hangul + full Han); excludes the Default/Latin block already merged
|
// (Hangul + full Han); excludes the Default/Latin block already merged
|
||||||
// by the global font, so the fallback no longer re-merges the full Ranges array.
|
// by the global font, so the fallback no longer re-merges the full Ranges array.
|
||||||
private ushort[] CjkFallbackGlyphRange = [];
|
private ushort[] CjkFallbackGlyphRange = [];
|
||||||
|
|
||||||
// Report accessor for the ctor self-test: built glyph-range array lengths so
|
// Report accessor for the ctor self-test: built glyph-range array lengths so
|
||||||
// the step can show the B1 dedup effect (a small trimmed fallback vs the large
|
// the step can show the dedup effect (a small trimmed fallback vs the large
|
||||||
// primary range) in its on-disk report instead of a bare Pass.
|
// primary range) in its on-disk report instead of a bare Pass.
|
||||||
internal (int Ranges, int JpRange, int CjkFallback) GlyphRangeLengths =>
|
internal (int Ranges, int JpRange, int CjkFallback) GlyphRangeLengths =>
|
||||||
(Ranges.Length, JpRange.Length, CjkFallbackGlyphRange.Length);
|
(Ranges.Length, JpRange.Length, CjkFallbackGlyphRange.Length);
|
||||||
@@ -247,7 +247,7 @@ public sealed class FontManager : IDisposable
|
|||||||
|
|
||||||
// Instance method so Ranges / JpRange are reachable without parameter
|
// Instance method so Ranges / JpRange are reachable without parameter
|
||||||
// plumbing; PascalCase field names follow the existing class style.
|
// plumbing; PascalCase field names follow the existing class style.
|
||||||
// B1: shared CJK + symbols tail for both the regular and italic delegate
|
// Shared CJK + symbols tail for both the regular and italic delegate
|
||||||
// fonts. Earlier-merged fonts win for shared codepoints (imgui MergeMode),
|
// fonts. Earlier-merged fonts win for shared codepoints (imgui MergeMode),
|
||||||
// so this runs AFTER the primary font is set as config.MergeFont. The CJK
|
// so this runs AFTER the primary font is set as config.MergeFont. The CJK
|
||||||
// fallback is the sole Hangul/Simplified-Han source when UseHellionFont=true
|
// fallback is the sole Hangul/Simplified-Han source when UseHellionFont=true
|
||||||
@@ -265,7 +265,7 @@ public sealed class FontManager : IDisposable
|
|||||||
config.GlyphRanges = JpRange;
|
config.GlyphRanges = JpRange;
|
||||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||||
|
|
||||||
// NotoSansCjk fallback, trimmed to CjkFallbackGlyphRange (B1). Merged last so earlier fonts win.
|
// NotoSansCjk fallback, trimmed to CjkFallbackGlyphRange. Merged last so earlier fonts win.
|
||||||
config.SizePt = basePt;
|
config.SizePt = basePt;
|
||||||
config.GlyphRanges = CjkFallbackGlyphRange;
|
config.GlyphRanges = CjkFallbackGlyphRange;
|
||||||
AddFontWithFallback(
|
AddFontWithFallback(
|
||||||
@@ -427,7 +427,7 @@ public sealed class FontManager : IDisposable
|
|||||||
|
|
||||||
// Common extras (Axis ingame glyphs, endonyms, enclosed alphanumerics)
|
// Common extras (Axis ingame glyphs, endonyms, enclosed alphanumerics)
|
||||||
// belong to the primary/Japanese ranges only. The trimmed CJK fallback
|
// belong to the primary/Japanese ranges only. The trimmed CJK fallback
|
||||||
// (B1) skips them so it stays a pure Hangul/Simplified-Han remainder and
|
// skips them so it stays a pure Hangul/Simplified-Han remainder and
|
||||||
// does not re-merge the Default-block work the global font already did.
|
// does not re-merge the Default-block work the global font already did.
|
||||||
if (includeCommonExtras)
|
if (includeCommonExtras)
|
||||||
{
|
{
|
||||||
@@ -498,7 +498,7 @@ public sealed class FontManager : IDisposable
|
|||||||
);
|
);
|
||||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges, includeCommonExtras: true);
|
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges, includeCommonExtras: true);
|
||||||
|
|
||||||
// B1: the fallback gets only the trimmed Hangul/Simplified-Han remainder.
|
// The fallback gets only the trimmed Hangul/Simplified-Han remainder.
|
||||||
// No Default block, no endonyms — those are already merged by the global and
|
// No Default block, no endonyms — those are already merged by the global and
|
||||||
// Japanese fonts, so re-merging them on the fallback was wasted atlas work.
|
// Japanese fonts, so re-merging them on the fallback was wasted atlas work.
|
||||||
CjkFallbackGlyphRange = BuildRange(CjkFallbackRange.Pairs, includeCommonExtras: false);
|
CjkFallbackGlyphRange = BuildRange(CjkFallbackRange.Pairs, includeCommonExtras: false);
|
||||||
|
|||||||
@@ -507,14 +507,14 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
|
|
||||||
// Resolve the surface this keybind acts on FIRST: a focused pop-out otherwise
|
// Resolve the surface this keybind acts on FIRST: a focused pop-out otherwise
|
||||||
// the main window. Channel-set/REPLY/prefill all write here so the action
|
// the main window. Channel-set/REPLY/prefill all write here so the action
|
||||||
// follows the input the user is typing in (C3 full tail rebuild, OD-1).
|
// follows the input the user is typing in.
|
||||||
var (targetWindow, targetTab) = ResolveKeybindTarget();
|
var (targetWindow, targetTab) = ResolveKeybindTarget();
|
||||||
|
|
||||||
// Surface + focus the resolved target ONCE, before routing. Main: ActivateChat
|
// Surface + focus the resolved target ONCE, before routing. Main: ActivateChat
|
||||||
// re-surfaces it from a hide/closed state (the chat-activation entry point
|
// re-surfaces it from a hide/closed state (the chat-activation entry point
|
||||||
// retired in v1.6.0). Pop-out: arm only its focus — NOT ActivateChat, which
|
// retired in v1.6.0). Pop-out: arm only its focus — NOT ActivateChat, which
|
||||||
// would yank the main window to front and un-hide it on every pop-out-targeted
|
// would yank the main window to front and un-hide it on every pop-out-targeted
|
||||||
// keybind (OD-1: stay where the user types). Exactly one window arms focus per
|
// keybind: stay where the user types. Exactly one window arms focus per
|
||||||
// keybind, so the next frame has no SetKeyboardFocusHere race.
|
// keybind, so the next frame has no SetKeyboardFocusHere race.
|
||||||
if (targetWindow is ChannelPopoutWindow)
|
if (targetWindow is ChannelPopoutWindow)
|
||||||
targetWindow.RequestInputFocus();
|
targetWindow.RequestInputFocus();
|
||||||
@@ -547,7 +547,7 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
// Rotation binds (REPLY / linkshell-cycle). Ported from v1.5.6's
|
// Rotation binds (REPLY / linkshell-cycle). Ported from v1.5.6's
|
||||||
// ChatLogWindow.Activated (1d3b429:240-334) without the ChatActivatedArgs
|
// ChatLogWindow.Activated (1d3b429:240-334) without the ChatActivatedArgs
|
||||||
// indirection (gone in the rewrite). Writes onto the resolved surface's
|
// indirection (gone in the rewrite). Writes onto the resolved surface's
|
||||||
// tab (C2/C3 shared target), not Plugin.CurrentTab.
|
// tab, not Plugin.CurrentTab.
|
||||||
if (targetTab is { } rotTab)
|
if (targetTab is { } rotTab)
|
||||||
{
|
{
|
||||||
var targetChannel = (InputChannel?)rotateChannel;
|
var targetChannel = (InputChannel?)rotateChannel;
|
||||||
@@ -629,7 +629,7 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
|
|
||||||
if (info.Permanent)
|
if (info.Permanent)
|
||||||
{
|
{
|
||||||
// KB-01 (1.5.6 parity, ChatLogWindow.SetChannel 1d3b429:1476-1479):
|
// 1.5.6 parity (ChatLogWindow.SetChannel, 1d3b429:1476-1479):
|
||||||
// committing the game channel also pre-targets the game's native input.
|
// committing the game channel also pre-targets the game's native input.
|
||||||
// Forward the tab's reply target for Tell so the partner is armed
|
// Forward the tab's reply target for Tell so the partner is armed
|
||||||
// game-side (ChangeChatChannel code 17); null for a linkshell —
|
// game-side (ChangeChatChannel code 17); null for a linkshell —
|
||||||
@@ -654,7 +654,7 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
// Prefill text binds (CMD_COMMAND seeds "/"): the token always goes to the
|
// Prefill text binds (CMD_COMMAND seeds "/"): the token always goes to the
|
||||||
// main InputBar (the focus contract does not expose pop-out buffers); a
|
// main InputBar (the focus contract does not expose pop-out buffers); a
|
||||||
// focused pop-out already received focus above, so only token routing matters
|
// focused pop-out already received focus above, so only token routing matters
|
||||||
// here (documented scope limit, OD-1).
|
// here -- a documented scope limit.
|
||||||
if (info.Text is { } text)
|
if (info.Text is { } text)
|
||||||
Plugin.Instance.InputBar.SetPendingMessage(text);
|
Plugin.Instance.InputBar.SetPendingMessage(text);
|
||||||
}
|
}
|
||||||
@@ -665,7 +665,7 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve which chat surface a keybind action targets: the open pop-out whose
|
// Resolve which chat surface a keybind action targets: the open pop-out whose
|
||||||
// input currently has focus, otherwise the main window. C2/C3 share this so a
|
// input currently has focus, otherwise the main window. Both paths share it so a
|
||||||
// channel-switch/REPLY/prefill follows the surface the user is typing in. The
|
// channel-switch/REPLY/prefill follows the surface the user is typing in. The
|
||||||
// returned tab is that surface's bound tab (pop-out: Bound; main: ActiveTab).
|
// returned tab is that surface's bound tab (pop-out: Bound; main: ActiveTab).
|
||||||
// Null tab => skip the tab-write (early-load window where no tab exists yet).
|
// Null tab => skip the tab-write (early-load window where no tab exists yet).
|
||||||
@@ -682,7 +682,7 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tab-delta keybinds (ChatTabForward/Backward) stay main-window-only by design:
|
// Tab-delta keybinds (ChatTabForward/Backward) stay main-window-only by design:
|
||||||
// a channel-bound pop-out has no tab list to cycle (OD-1). The focus contract is
|
// a channel-bound pop-out has no tab list to cycle. The focus contract is
|
||||||
// consumed by the channel-set/REPLY/prefill tail, not here.
|
// consumed by the channel-set/REPLY/prefill tail, not here.
|
||||||
private void DispatchTabDelta(int delta)
|
private void DispatchTabDelta(int delta)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public sealed class ExtraChat : IDisposable
|
|||||||
|
|
||||||
// volatile: IPC callbacks fire on a Dalamud thread while ImGui reads these.
|
// volatile: IPC callbacks fire on a Dalamud thread while ImGui reads these.
|
||||||
// Reference assignment is atomic on x64, but the barrier ensures visibility
|
// Reference assignment is atomic on x64, but the barrier ensures visibility
|
||||||
// across threads (especially Mono/Wine). See AUDIT-2026-05-05 [SEC-01].
|
// across threads (especially Mono/Wine). Raised in the 2026-05-05 audit.
|
||||||
private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
|
private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
|
||||||
internal IReadOnlyDictionary<string, uint> ChannelCommandColours =>
|
internal IReadOnlyDictionary<string, uint> ChannelCommandColours =>
|
||||||
ChannelCommandColoursInternal;
|
ChannelCommandColoursInternal;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
|
|
||||||
internal void ClearAllTabs()
|
internal void ClearAllTabs()
|
||||||
{
|
{
|
||||||
// B3: snapshot the tab LIST under the shared lock so the worker-thread
|
// Snapshot the tab LIST under the shared lock so the worker-thread
|
||||||
// add/remove can't tear the enumeration; tab.Clear() then runs lock-free
|
// add/remove can't tear the enumeration; tab.Clear() then runs lock-free
|
||||||
// (each tab's Messages has its own SemaphoreSlim — lock order: list outer).
|
// (each tab's Messages has its own SemaphoreSlim — lock order: list outer).
|
||||||
List<Tab> tabsSnapshot;
|
List<Tab> tabsSnapshot;
|
||||||
@@ -184,8 +184,8 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
using var messages = Store.GetMostRecentMessages(CurrentContentId, since);
|
using var messages = Store.GetMostRecentMessages(CurrentContentId, since);
|
||||||
|
|
||||||
// TempTabs excluded (live state from AutoTellTabsService). Bucket via the
|
// TempTabs excluded (live state from AutoTellTabsService). Bucket via the
|
||||||
// pure MapMessagesToTabs so the assignment stays testable outside Dalamud (B3-1).
|
// pure MapMessagesToTabs so the assignment stays testable outside Dalamud.
|
||||||
// B3: snapshot under the shared lock (list copy only — short critical
|
// Snapshot under the shared lock (list copy only — short critical
|
||||||
// section). The Store query above and the AddSortPrune writes below stay
|
// section). The Store query above and the AddSortPrune writes below stay
|
||||||
// OUTSIDE the lock (lock order: list outer, MessageList inner).
|
// OUTSIDE the lock (lock order: list outer, MessageList inner).
|
||||||
List<Tab> nonTempTabs;
|
List<Tab> nonTempTabs;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using HellionChat.Resources;
|
|||||||
|
|
||||||
namespace HellionChat;
|
namespace HellionChat;
|
||||||
|
|
||||||
// UI-7: how a sender's name is rendered in the chat log. Kept in its own file
|
// How a sender's name is rendered in the chat log. Kept in its own file
|
||||||
// (no Dalamud usings) so the SenderNameFormatter pure-helper test stays
|
// (no Dalamud usings) so the SenderNameFormatter pure-helper test stays
|
||||||
// AppDomain-isolated (feedback_dalamud_test_isolation).
|
// AppDomain-isolated (feedback_dalamud_test_isolation).
|
||||||
|
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ internal sealed class PayloadHandler
|
|||||||
if (!Sheets.IsInForay())
|
if (!Sheets.IsInForay())
|
||||||
{
|
{
|
||||||
// Single SetPendingMessage call; v1.5.6 used incremental Chat += writes.
|
// Single SetPendingMessage call; v1.5.6 used incremental Chat += writes.
|
||||||
// XC-8: shares the /tell builder with the native detours. IsPublic (not
|
// Shares the /tell builder with the native detours. IsPublic (not
|
||||||
// IsNullOrEmpty) is resolved HERE — a private/null world must NOT leak @World.
|
// IsNullOrEmpty) is resolved HERE — a private/null world must NOT leak @World.
|
||||||
_inputBar.SetPendingMessage(
|
_inputBar.SetPendingMessage(
|
||||||
GameFunctions.Chat.BuildTellCommand(
|
GameFunctions.Chat.BuildTellCommand(
|
||||||
|
|||||||
+134
-67
@@ -134,7 +134,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
|
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
|
||||||
internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!;
|
internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!;
|
||||||
|
|
||||||
// Ctor-smoke anchors (B0-2). Exposed so the Payload/Chunk ctor-smoke steps
|
// Ctor-smoke anchors. Exposed so the Payload/Chunk ctor-smoke steps
|
||||||
// can drive the real per-frame Lender path (Borrow()) and the eager
|
// can drive the real per-frame Lender path (Borrow()) and the eager
|
||||||
// singletons through the container, never via new(). Mirror of the
|
// singletons through the container, never via new(). Mirror of the
|
||||||
// FontManager property pattern — every SelfTest reaches services this way.
|
// FontManager property pattern — every SelfTest reaches services this way.
|
||||||
@@ -178,7 +178,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
internal bool ChatActivationRequested;
|
internal bool ChatActivationRequested;
|
||||||
|
|
||||||
// Set in the first DisposeAsync statement so async callbacks scheduled
|
// Set in the first DisposeAsync statement so async callbacks scheduled
|
||||||
// via Framework.RunOnTick (v1.4.8 B3 retention sweep) can early-bail
|
// via Framework.RunOnTick (v1.4.8 retention sweep) can early-bail
|
||||||
// before they touch state that has already been torn down. Volatile
|
// before they touch state that has already been torn down. Volatile
|
||||||
// because the tick reads it from a different thread than the writer.
|
// because the tick reads it from a different thread than the writer.
|
||||||
private volatile bool _isDisposing;
|
private volatile bool _isDisposing;
|
||||||
@@ -188,7 +188,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
// just unloaded belongs to nobody.
|
// just unloaded belongs to nobody.
|
||||||
internal bool IsDisposing => _isDisposing;
|
internal bool IsDisposing => _isDisposing;
|
||||||
|
|
||||||
// v1.9.0 B5: last full Draw() wall-time in ms, written once per frame at
|
// v1.9.0: last full Draw() wall-time in ms, written once per frame at
|
||||||
// the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push
|
// the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push
|
||||||
// and the font push (the first-frame hitch measurement must include atlas/style
|
// and the font push (the first-frame hitch measurement must include atlas/style
|
||||||
// prologue cost), not just WindowSystem.Draw — measuring the inner call
|
// prologue cost), not just WindowSystem.Draw — measuring the inner call
|
||||||
@@ -214,7 +214,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
// must never block doing so.
|
// must never block doing so.
|
||||||
internal readonly Util.DbOperationGate DbOperations = new();
|
internal readonly Util.DbOperationGate DbOperations = new();
|
||||||
|
|
||||||
// B3: neutral owner of the Config.Tabs LIST-structure lock so both the
|
// Neutral owner of the Config.Tabs LIST-structure lock so both the
|
||||||
// worker-thread mutator (AutoTellTabsService) and the framework-thread
|
// worker-thread mutator (AutoTellTabsService) and the framework-thread
|
||||||
// refilter (MessageManager) share ONE monitor. Lock order: this outer,
|
// refilter (MessageManager) share ONE monitor. Lock order: this outer,
|
||||||
// MessageList's SemaphoreSlim inner — never the reverse.
|
// MessageList's SemaphoreSlim inner — never the reverse.
|
||||||
@@ -287,83 +287,118 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
|
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch,
|
// 2.0.0 does not migrate, it starts over. Five cycles rebuilt the whole
|
||||||
// superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who
|
// window layer, and a config carried through them keeps values chosen
|
||||||
// set it false (only effective in 1.5.6) wanted top tabs — carry that
|
// against surfaces that no longer exist -- an opacity picked for a
|
||||||
// intent forward. Runs only for pre-v23 configs; fresh configs load at
|
// window that has been redrawn twice since, tabs laid out for a sidebar
|
||||||
// LatestVersion and skip it. Additive v20/v22 fields keep their
|
// that works differently now. Every user of this build is a tester who
|
||||||
// initializer defaults as before.
|
// was told this happens, and it is the only way to be sure everyone
|
||||||
if (Config.Version < 23 && !Config.SidebarTabView)
|
// sees the same defaults.
|
||||||
|
//
|
||||||
|
// The file is copied aside first. Rolling back to 1.5.6 is a supported
|
||||||
|
// move here and it stays cheap: the old settings are a file copy away,
|
||||||
|
// rather than an evening of clicking them back in.
|
||||||
|
if (Config.Version < 27)
|
||||||
{
|
{
|
||||||
Config.MainWindowLayoutMode = MainWindowLayoutMode.TopTabs;
|
BackUpConfigBeforeReset();
|
||||||
}
|
|
||||||
|
Config = Configuration.CreateFresh();
|
||||||
|
Config.Version = 27;
|
||||||
|
|
||||||
|
// Saved immediately: a crash between here and the first user-driven
|
||||||
|
// save would otherwise run the whole reset again on the next start,
|
||||||
|
// and the second run would back up the already-reset file over the
|
||||||
|
// real backup.
|
||||||
|
SaveConfig();
|
||||||
|
|
||||||
// v24 migration: the privacy filter used to route a known but unticked
|
|
||||||
// channel through the unknown-type failsafe, so the channel grid was
|
|
||||||
// inert whenever that failsafe was on. Corrected in v1.12.0. A config
|
|
||||||
// that never picked a channel was storing everything through that hole,
|
|
||||||
// and the corrected rule would store nothing at all -- so the intent is
|
|
||||||
// carried forward as a filter that is honestly switched off.
|
|
||||||
if (
|
|
||||||
Config.Version < 24
|
|
||||||
&& Privacy.StorageRule.ShouldDisableFilterOnV24(
|
|
||||||
Config.PrivacyFilterEnabled,
|
|
||||||
Config.PrivacyPersistUnknownChannels,
|
|
||||||
Config.PrivacyPersistChannels.Count
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Config.PrivacyFilterEnabled = false;
|
|
||||||
// Log, not LogProxy: this runs in Phase-0 and the proxy is only
|
|
||||||
// resolved from the container further down.
|
|
||||||
Log.Information(
|
Log.Information(
|
||||||
"Privacy filter switched off during the v24 migration: it was on with no channels "
|
"Config reset to defaults for 2.0.0. Previous settings kept as "
|
||||||
+ "picked, which stored everything through the unknown-channel failsafe. Pick "
|
+ "HellionChat.json.pre-2.0.0.bak next to the config file."
|
||||||
+ "channels in Settings to switch it back on."
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// v25 carried no migration step; the bump was documentation.
|
|
||||||
//
|
|
||||||
// v26 does. NameCameFromPartner is what screenshot mode reads to decide
|
|
||||||
// whether a tab name is a person, and a config written before it existed
|
|
||||||
// has it false on every tab -- including pinned tell tabs, which survive
|
|
||||||
// reloads and are named "Player@World". Anything still carrying a tell
|
|
||||||
// binding or the temp flag got its name from a partner, so the flag is
|
|
||||||
// set from those two.
|
|
||||||
//
|
|
||||||
// Tabs promoted before this version are past saving: promotion clears
|
|
||||||
// both markers and keeps the name, so nothing in the stored data says
|
|
||||||
// where that name came from. Renaming one clears the flag anyway, which
|
|
||||||
// is the same outcome the user gets by editing it.
|
|
||||||
if (Config.Version < 26)
|
|
||||||
{
|
{
|
||||||
var carried = 0;
|
// v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch,
|
||||||
foreach (var tab in Config.Tabs)
|
// superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who
|
||||||
|
// set it false (only effective in 1.5.6) wanted top tabs — carry that
|
||||||
|
// intent forward. Runs only for pre-v23 configs; fresh configs load at
|
||||||
|
// LatestVersion and skip it. Additive v20/v22 fields keep their
|
||||||
|
// initializer defaults as before.
|
||||||
|
if (Config.Version < 23 && !Config.SidebarTabView)
|
||||||
{
|
{
|
||||||
if (tab.NameCameFromPartner || (!tab.IsTempTab && tab.TellTarget?.IsSet() != true))
|
Config.MainWindowLayoutMode = MainWindowLayoutMode.TopTabs;
|
||||||
continue;
|
|
||||||
|
|
||||||
tab.NameCameFromPartner = true;
|
|
||||||
carried++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (carried > 0)
|
// v24 migration: the privacy filter used to route a known but unticked
|
||||||
|
// channel through the unknown-type failsafe, so the channel grid was
|
||||||
|
// inert whenever that failsafe was on. Corrected in v1.12.0. A config
|
||||||
|
// that never picked a channel was storing everything through that hole,
|
||||||
|
// and the corrected rule would store nothing at all -- so the intent is
|
||||||
|
// carried forward as a filter that is honestly switched off.
|
||||||
|
if (
|
||||||
|
Config.Version < 24
|
||||||
|
&& Privacy.StorageRule.ShouldDisableFilterOnV24(
|
||||||
|
Config.PrivacyFilterEnabled,
|
||||||
|
Config.PrivacyPersistUnknownChannels,
|
||||||
|
Config.PrivacyPersistChannels.Count
|
||||||
|
)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
Config.PrivacyFilterEnabled = false;
|
||||||
|
// Log, not LogProxy: this runs in Phase-0 and the proxy is only
|
||||||
|
// resolved from the container further down.
|
||||||
Log.Information(
|
Log.Information(
|
||||||
$"Marked {carried} tab(s) as partner-named during the v26 migration, so "
|
"Privacy filter switched off during the v24 migration: it was on with no channels "
|
||||||
+ "screenshot mode hides them in the channel header."
|
+ "picked, which stored everything through the unknown-channel failsafe. Pick "
|
||||||
|
+ "channels in Settings to switch it back on."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v25 carried no migration step; the bump was documentation.
|
||||||
|
//
|
||||||
|
// v26 does. NameCameFromPartner is what screenshot mode reads to decide
|
||||||
|
// whether a tab name is a person, and a config written before it existed
|
||||||
|
// has it false on every tab -- including pinned tell tabs, which survive
|
||||||
|
// reloads and are named "Player@World". Anything still carrying a tell
|
||||||
|
// binding or the temp flag got its name from a partner, so the flag is
|
||||||
|
// set from those two.
|
||||||
|
//
|
||||||
|
// Tabs promoted before this version are past saving: promotion clears
|
||||||
|
// both markers and keeps the name, so nothing in the stored data says
|
||||||
|
// where that name came from. Renaming one clears the flag anyway, which
|
||||||
|
// is the same outcome the user gets by editing it.
|
||||||
|
if (Config.Version < 26)
|
||||||
|
{
|
||||||
|
var carried = 0;
|
||||||
|
foreach (var tab in Config.Tabs)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
tab.NameCameFromPartner
|
||||||
|
|| (!tab.IsTempTab && tab.TellTarget?.IsSet() != true)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tab.NameCameFromPartner = true;
|
||||||
|
carried++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carried > 0)
|
||||||
|
{
|
||||||
|
Log.Information(
|
||||||
|
$"Marked {carried} tab(s) as partner-named during the v26 migration, so "
|
||||||
|
+ "screenshot mode hides them in the channel header."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.Version = 26;
|
Config.Version = 27;
|
||||||
|
|
||||||
// Unpinned TempTabs are session-only and dropped on every load. Pinned
|
// Unpinned TempTabs are session-only and dropped on every load. Pinned
|
||||||
// TempTabs survive reload -- tester feedback in v1.4.7.
|
// TempTabs survive reload -- tester feedback in v1.4.7.
|
||||||
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
|
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
|
||||||
|
|
||||||
// GP-04: clear stale Tab.PopOut flags now — the pool binds further down
|
// Clear stale Tab.PopOut flags now — the pool binds further down
|
||||||
// (ChannelPopoutPool resolve below), so at this point no tab can own a
|
// (ChannelPopoutPool resolve below), so at this point no tab can own a
|
||||||
// slot. A persisted PopOut=true (notably on surviving pinned TempTabs)
|
// slot. A persisted PopOut=true (notably on surviving pinned TempTabs)
|
||||||
// would otherwise be a flag with no window. Runs after the strip, before
|
// would otherwise be a flag with no window. Runs after the strip, before
|
||||||
@@ -462,7 +497,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>();
|
FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>();
|
||||||
ChannelPopoutPool = _host.Services.GetRequiredService<Ui.Windows.ChannelPopoutPool>();
|
ChannelPopoutPool = _host.Services.GetRequiredService<Ui.Windows.ChannelPopoutPool>();
|
||||||
|
|
||||||
// Ctor-smoke anchors (B0-2). Resolved last, against the fully built
|
// Ctor-smoke anchors. Resolved last, against the fully built
|
||||||
// container: every MakePayloadHandler dep (MainWindow, InputBar,
|
// container: every MakePayloadHandler dep (MainWindow, InputBar,
|
||||||
// ChunkRenderer, ...) is resolvable here, and the ChunkRenderer resolve
|
// ChunkRenderer, ...) is resolvable here, and the ChunkRenderer resolve
|
||||||
// below just reuses the same cached singleton. These are plain
|
// below just reuses the same cached singleton. These are plain
|
||||||
@@ -732,7 +767,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Set before any cleanup so deferred Framework.RunOnTick callbacks
|
// Set before any cleanup so deferred Framework.RunOnTick callbacks
|
||||||
// (B3 retention sweep) see the flag and bail out before they touch
|
// (the retention sweep) see the flag and bail out before they touch
|
||||||
// MessageManager / Log / static fields that the rest of this method
|
// MessageManager / Log / static fields that the rest of this method
|
||||||
// is about to tear down.
|
// is about to tear down.
|
||||||
_isDisposing = true;
|
_isDisposing = true;
|
||||||
@@ -840,6 +875,38 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copies the config aside before the 2.0.0 reset overwrites it. Best effort
|
||||||
|
// by design: a backup that cannot be written must not stop the plugin from
|
||||||
|
// starting, and the reset itself is what the user was told would happen.
|
||||||
|
//
|
||||||
|
// Overwrite=false, so a second run cannot bury the real backup under a copy
|
||||||
|
// of the already-reset file. The reset saves immediately for the same
|
||||||
|
// reason, but a crash in between is exactly when this matters.
|
||||||
|
private static void BackUpConfigBeforeReset()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dir = Interface.ConfigDirectory.Parent?.FullName;
|
||||||
|
if (dir is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var configFile = Path.Combine(dir, "HellionChat.json");
|
||||||
|
if (!File.Exists(configFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var backup = Path.Combine(dir, "HellionChat.json.pre-2.0.0.bak");
|
||||||
|
if (File.Exists(backup))
|
||||||
|
return;
|
||||||
|
|
||||||
|
File.Copy(configFile, backup);
|
||||||
|
Log.Information($"HellionChat: config backed up to {backup} before the 2.0.0 reset");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Warning(e, "HellionChat: could not back up the config before the 2.0.0 reset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void MigrateFromChatTwoLayout()
|
private static void MigrateFromChatTwoLayout()
|
||||||
{
|
{
|
||||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||||
@@ -1181,7 +1248,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
// Schedule on the next framework tick to avoid the ~194ms
|
// Schedule on the next framework tick to avoid the ~194ms
|
||||||
// hitch from blocking with .Wait() while the frame finishes.
|
// hitch from blocking with .Wait() while the frame finishes.
|
||||||
// The Config.Tabs enumeration in ClearAllTabs/FilterAllTabs is
|
// The Config.Tabs enumeration in ClearAllTabs/FilterAllTabs is
|
||||||
// now guarded by the shared Plugin.TabsListLock (B3), so this
|
// now guarded by the shared Plugin.TabsListLock, so this
|
||||||
// tick scheduling is purely hitch-avoidance, not safety.
|
// tick scheduling is purely hitch-avoidance, not safety.
|
||||||
// Pattern reference: SimpleTweaks
|
// Pattern reference: SimpleTweaks
|
||||||
// Tweaks/Chat/CaseInsensitiveCommands.cs:45.
|
// Tweaks/Chat/CaseInsensitiveCommands.cs:45.
|
||||||
@@ -1268,7 +1335,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
|
|
||||||
private void Draw()
|
private void Draw()
|
||||||
{
|
{
|
||||||
// v1.9.0 B5: time the whole handler (style + font prologue included).
|
// v1.9.0: time the whole handler (style + font prologue included).
|
||||||
// Bail before measuring once teardown has begun — a late Draw tick
|
// Bail before measuring once teardown has begun — a late Draw tick
|
||||||
// must not touch ThemeRegistry / FontManager after DisposeAsync.
|
// must not touch ThemeRegistry / FontManager after DisposeAsync.
|
||||||
if (_isDisposing)
|
if (_isDisposing)
|
||||||
@@ -1277,7 +1344,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
var drawWatch = Stopwatch.StartNew();
|
var drawWatch = Stopwatch.StartNew();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// v1.4.8 B2: pick up external edits of the active custom theme JSON
|
// v1.4.8: pick up external edits of the active custom theme JSON
|
||||||
// without forcing the user to re-click the picker. The disk-stat is
|
// without forcing the user to re-click the picker. The disk-stat is
|
||||||
// 1Hz-throttled inside RefreshActiveIfStale, so this is essentially
|
// 1Hz-throttled inside RefreshActiveIfStale, so this is essentially
|
||||||
// free on built-in themes and ~1 stat/second on custom themes.
|
// free on built-in themes and ~1 stat/second on custom themes.
|
||||||
@@ -1367,7 +1434,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
// Config.Tabs across the save so JSON includes them. Cloning only the
|
// Config.Tabs across the save so JSON includes them. Cloning only the
|
||||||
// unpinned subset keeps the allocation proportional to
|
// unpinned subset keeps the allocation proportional to
|
||||||
// AutoTellTabsLimit (<=15) instead of the full tab list.
|
// AutoTellTabsLimit (<=15) instead of the full tab list.
|
||||||
// B3: the strip/restore mutates the tab LIST, so it shares TabsListLock
|
// The strip/restore mutates the tab LIST, so it shares TabsListLock
|
||||||
// with the worker add/remove and the refilter snapshot. Re-entrant: the
|
// with the worker add/remove and the refilter snapshot. Re-entrant: the
|
||||||
// one worker caller (HandleTell) already holds it; framework callers take
|
// one worker caller (HandleTell) already holds it; framework callers take
|
||||||
// it here. SavePluginConfig runs inside (short, in-memory) — the documented fallback
|
// it here. SavePluginConfig runs inside (short, in-memory) — the documented fallback
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using HellionChat.Util;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B2: behavioural check that the card path feeds CardClipPlanner AND that a
|
// Behavioural check that the card path feeds CardClipPlanner AND that a
|
||||||
// layout change clears the height cache — not a non-null check. Pure plan math
|
// layout change clears the height cache — not a non-null check. Pure plan math
|
||||||
// is pinned headless by CardClipPlanTests; this drives the live accessors.
|
// is pinned headless by CardClipPlanTests; this drives the live accessors.
|
||||||
internal sealed class CardClipPlanStep : ISelfTestStep
|
internal sealed class CardClipPlanStep : ISelfTestStep
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace HellionChat.SelfTests;
|
|||||||
// - a BOUND auto-tell tab keeps its partner by design (leg1) — its binding is
|
// - a BOUND auto-tell tab keeps its partner by design (leg1) — its binding is
|
||||||
// Tab.TellTarget and is deliberately untouched by the strip.
|
// Tab.TellTarget and is deliberately untouched by the strip.
|
||||||
// The channel label is intentionally NOT asserted: a tell tab re-derives back to
|
// The channel label is intentionally NOT asserted: a tell tab re-derives back to
|
||||||
// Tell after the strip (spec TR-7); only the target matters for privacy.
|
// Tell after the strip ; only the target matters for privacy.
|
||||||
internal sealed class CurrentTabGuidedStep : ISelfTestStep
|
internal sealed class CurrentTabGuidedStep : ISelfTestStep
|
||||||
{
|
{
|
||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using HellionChat._Helpers;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B2-3: proves the plugin-disclosure arm-and-hold wires the (otherwise verwaist)
|
// Proves the plugin-disclosure arm-and-hold wires the (otherwise verwaist)
|
||||||
// scanner into the REAL send entry InputBar.TrySend. Drives TrySend via the
|
// scanner into the REAL send entry InputBar.TrySend. Drives TrySend via the
|
||||||
// arm-test-hook with a PUA glyph in the buffer and NotifyPluginDisclosure on:
|
// arm-test-hook with a PUA glyph in the buffer and NotifyPluginDisclosure on:
|
||||||
// the first send must ARM and HOLD (no send), so PendingMessage stays the probe
|
// the first send must ARM and HOLD (no send), so PendingMessage stays the probe
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
|
|||||||
return SelfTestStepResult.Fail;
|
return SelfTestStepResult.Fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
// B1: assert the atlas actually finished building all required handles,
|
// Assert the atlas actually finished building all required handles,
|
||||||
// not just that the references are non-null. FontsReady is the observable
|
// not just that the references are non-null. FontsReady is the observable
|
||||||
// state the trimmed-fallback rebuild must still reach; a half-built atlas
|
// state the trimmed-fallback rebuild must still reach; a half-built atlas
|
||||||
// would pass the null/exception checks above but fail here.
|
// would pass the null/exception checks above but fail here.
|
||||||
@@ -128,7 +128,7 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Report what was actually verified rather than a bare Pass.
|
// Report what was actually verified rather than a bare Pass.
|
||||||
// The glyph-range entry counts make the B1 dedup visible — the cjk-fallback
|
// The glyph-range entry counts make the dedup visible — the cjk-fallback
|
||||||
// range is now a small trimmed remainder next to the large primary range.
|
// range is now a small trimmed remainder next to the large primary range.
|
||||||
var counts = fm.GlyphRangeLengths;
|
var counts = fm.GlyphRangeLengths;
|
||||||
var italicState =
|
var italicState =
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat.Ui.Windows;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// P8 wiring: UserHide() suppresses DrawConditions; both ActivateChat() (Enter) and
|
// wiring: UserHide() suppresses DrawConditions; both ActivateChat() (Enter) and
|
||||||
// Toggle() (/hellion) restore it. Pure window-state — the focus side is left to smoke.
|
// Toggle() (/hellion) restore it. Pure window-state — the focus side is left to smoke.
|
||||||
internal sealed class HideRestoreSelfTestStep : ISelfTestStep
|
internal sealed class HideRestoreSelfTestStep : ISelfTestStep
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat.Ui.Windows;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B1-2 window flags. Drives the REAL MainWindow.PreDraw and asserts it wired
|
// window flags. Drives the REAL MainWindow.PreDraw and asserts it wired
|
||||||
// Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure
|
// Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure
|
||||||
// fresh-base contract: false/false adds NoMove|NoResize, true/true clears them
|
// fresh-base contract: false/false adds NoMove|NoResize, true/true clears them
|
||||||
// -- flags must rebuild from a fresh base, or NoMove sticks after toggling
|
// -- flags must rebuild from a fresh base, or NoMove sticks after toggling
|
||||||
@@ -72,7 +72,7 @@ internal sealed class MainWindowFlagsStep : ISelfTestStep
|
|||||||
return SelfTestStepResult.Fail;
|
return SelfTestStepResult.Fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
// P7 title-bar contract: ShowTitleBar=false adds NoTitleBar from the
|
// title-bar contract: ShowTitleBar=false adds NoTitleBar from the
|
||||||
// fresh base, true clears it (same no-accumulation guarantee).
|
// fresh base, true clears it (same no-accumulation guarantee).
|
||||||
var barHidden = MainWindow.ResolveFlags(true, true, false);
|
var barHidden = MainWindow.ResolveFlags(true, true, false);
|
||||||
var barShown = MainWindow.ResolveFlags(true, true, true);
|
var barShown = MainWindow.ResolveFlags(true, true, true);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// UI-12 focus opacity. Pins the pure ResolveBgAlpha contract (focused →
|
// focus opacity. Pins the pure ResolveBgAlpha contract (focused →
|
||||||
// WindowOpacity, unfocused → WindowOpacityInactive). The PreDraw wiring
|
// WindowOpacity, unfocused → WindowOpacityInactive). The PreDraw wiring
|
||||||
// (BgAlpha = ResolveBgAlpha(IsFocused) behind the main-viewport/!docked guard)
|
// (BgAlpha = ResolveBgAlpha(IsFocused) behind the main-viewport/!docked guard)
|
||||||
// is NOT headless-deterministic — the guard may leave BgAlpha null when
|
// is NOT headless-deterministic — the guard may leave BgAlpha null when
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using HellionChat.Util;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B3-3: notification-sound selection. Drives the pure SelectNotificationSound
|
// Notification-sound selection. Drives the pure SelectNotificationSound
|
||||||
// (the exact pick logic ProcessMessage runs per message) through its SelfTest
|
// (the exact pick logic ProcessMessage runs per message) through its SelfTest
|
||||||
// wrapper with local synthetic tabs — Plugin.Config.Tabs is never touched, so
|
// wrapper with local synthetic tabs — Plugin.Config.Tabs is never touched, so
|
||||||
// no real tab gains messages or unread state. The audible preview button is
|
// no real tab gains messages or unread state. The audible preview button is
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.IO;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// Disk sink for the B5 performance baseline. Kept separate from the SelfTest
|
// Disk sink for the performance baseline. Kept separate from the SelfTest
|
||||||
// step so the per-frame hot path never references file IO. Writes one
|
// step so the per-frame hot path never references file IO. Writes one
|
||||||
// perf-baseline.json into the plugin ConfigDirectory, atomically (tmp + move)
|
// perf-baseline.json into the plugin ConfigDirectory, atomically (tmp + move)
|
||||||
// like ThemeRegistry's theme writer, so a mid-write crash leaves either the
|
// like ThemeRegistry's theme writer, so a mid-write crash leaves either the
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ internal sealed class PerformanceBaselineStep : ISelfTestStep
|
|||||||
private const int TargetFrames = 1000;
|
private const int TargetFrames = 1000;
|
||||||
|
|
||||||
// Rough draw-call proxy: ImGui emits 6 indices per quad, so vertices/6 is an
|
// Rough draw-call proxy: ImGui emits 6 indices per quad, so vertices/6 is an
|
||||||
// intentional under-count of draw work, not the exact quad count (API-3).
|
// intentional under-count of draw work, not the exact quad count.
|
||||||
private const int VerticesPerQuadProxy = 6;
|
private const int VerticesPerQuadProxy = 6;
|
||||||
|
|
||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B3-5: only the snap decision is headless-testable. Scroll detection + bar +
|
// Only the snap decision is headless-testable. Scroll detection + bar +
|
||||||
// hit-test are smoke-only (the scroll child exists only in-game; GetScrollY is
|
// hit-test are smoke-only (the scroll child exists only in-game; GetScrollY is
|
||||||
// garbage headless). Drives ResolveSnapToBottom via the SelfTest accessor and
|
// garbage headless). Drives ResolveSnapToBottom via the SelfTest accessor and
|
||||||
// asserts the OR + the request reset invariant.
|
// asserts the OR + the request reset invariant.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using Dalamud.Plugin.SelfTest;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B2-1/B2-2: proves the WorldSuffixMode/NameFormMode reformat reaches the REAL
|
// Proves the WorldSuffixMode/NameFormMode reformat reaches the REAL
|
||||||
// render entry. Drives ChunkRenderer.DrawChunks (a SelfTests/README-sanctioned
|
// render entry. Drives ChunkRenderer.DrawChunks (a SelfTests/README-sanctioned
|
||||||
// real entry that wires SenderNameDisplay.ForDisplay at ChunkRenderer.cs:54)
|
// real entry that wires SenderNameDisplay.ForDisplay at ChunkRenderer.cs:54)
|
||||||
// with a synthetic ChunkSource.Sender chunk carrying a PlayerPayload, at a
|
// with a synthetic ChunkSource.Sender chunk carrying a PlayerPayload, at a
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat.Code;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// v1.10.0/C3: the active row gets a surface and an accent bar, so exactly the
|
// v1.10.0: the active row gets a surface and an accent bar, so exactly the
|
||||||
// row the user is on must be marked -- and only that one. Drives the real
|
// row the user is on must be marked -- and only that one. Drives the real
|
||||||
// Sidebar.Draw and reads the render-observability counter, so a regression in
|
// Sidebar.Draw and reads the render-observability counter, so a regression in
|
||||||
// the draw path fails rather than a parallel calculation passing.
|
// the draw path fails rather than a parallel calculation passing.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using HellionChat.GameFunctions.Types;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B3-2: greeted glyph renders only for temp tabs when the toggle is on. Drives
|
// Greeted glyph renders only for temp tabs when the toggle is on. Drives
|
||||||
// the REAL Sidebar.Draw (render precedent: HonorificHeaderRenderStep, the only
|
// the REAL Sidebar.Draw (render precedent: HonorificHeaderRenderStep, the only
|
||||||
// real .Draw in this pool — NOT SidebarModeAutoSwitchStep which only calls
|
// real .Draw in this pool — NOT SidebarModeAutoSwitchStep which only calls
|
||||||
// IsExpanded/GetWidth) inside the /xlperf window frame and reads the render
|
// IsExpanded/GetWidth) inside the /xlperf window frame and reads the render
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ internal sealed class SidebarModeAutoSwitchStep : ISelfTestStep
|
|||||||
return SelfTestStepResult.Fail;
|
return SelfTestStepResult.Fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
// B1-3a: the expanded width must come from Config.SidebarWidth, not the
|
// The expanded width must come from Config.SidebarWidth, not the
|
||||||
// old fixed 150 constant. Drive the REAL GetWidth (the single source
|
// old fixed 150 constant. Drive the REAL GetWidth (the single source
|
||||||
// Sidebar.Draw consumes) with concrete values and assert the OBSERVED
|
// Sidebar.Draw consumes) with concrete values and assert the OBSERVED
|
||||||
// effect — in-range passthrough plus clamping — instead of mirroring the
|
// effect — in-range passthrough plus clamping — instead of mirroring the
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using HellionChat.GameFunctions.Types;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B3-4: section headers render once per non-empty temp-tab pool, and compact
|
// Section headers render once per non-empty temp-tab pool, and compact
|
||||||
// mode suppresses the header text (separators stay). Drives the REAL
|
// mode suppresses the header text (separators stay). Drives the REAL
|
||||||
// Sidebar.Draw inside the /xlperf window frame (same render precedent as
|
// Sidebar.Draw inside the /xlperf window frame (same render precedent as
|
||||||
// SidebarGreetedGlyphStep) and reads the render observability counter.
|
// SidebarGreetedGlyphStep) and reads the render observability counter.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat.Ui.Components;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// B3-1: rename must persist. Drives the real ApplyTabRename (the InputText
|
// Rename must persist. Drives the real ApplyTabRename (the InputText
|
||||||
// callback path), then SaveConfig + reload from disk and asserts the new name
|
// callback path), then SaveConfig + reload from disk and asserts the new name
|
||||||
// survived — a fresh-from-config tab, not the same reference (a reference check
|
// survived — a fresh-from-config tab, not the same reference (a reference check
|
||||||
// would pass on a dead roundtrip). Uses a persistent (non-temp) tab: unpinned
|
// would pass on a dead roundtrip). Uses a persistent (non-temp) tab: unpinned
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace HellionChat.SelfTests;
|
|||||||
// The activation strip. Drives the REAL OnTabActivated -- the entry the
|
// The activation strip. Drives the REAL OnTabActivated -- the entry the
|
||||||
// Sidebar/TopTabBar click handlers, the pop-out path and the Draw-seed all call
|
// Sidebar/TopTabBar click handlers, the pop-out path and the Draw-seed all call
|
||||||
// — with local probe tabs (Plugin.Config.Tabs is never touched). Asserts the
|
// — with local probe tabs (Plugin.Config.Tabs is never touched). Asserts the
|
||||||
// five contracts: strip-on-switch, no-strip-on-reclick (TR-4), leg1 preserve,
|
// five contracts: strip-on-switch, no-strip-on-reclick, leg1 preserve,
|
||||||
// derive, and non-tell untouched.
|
// derive, and non-tell untouched.
|
||||||
internal sealed class TellResetOnActivateStep : ISelfTestStep
|
internal sealed class TellResetOnActivateStep : ISelfTestStep
|
||||||
{
|
{
|
||||||
@@ -41,7 +41,7 @@ internal sealed class TellResetOnActivateStep : ISelfTestStep
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (b) re-clicking the already-active tab (previous == tab) must NOT strip
|
// (b) re-clicking the already-active tab (previous == tab) must NOT strip
|
||||||
// a live game-tell conversation (TR-4 regression guard).
|
// a live game-tell conversation (regression guard).
|
||||||
var reclick = MakeStaleTellTab(boundTellTarget: false, withLabel: false);
|
var reclick = MakeStaleTellTab(boundTellTarget: false, withLabel: false);
|
||||||
TabLifecycleHelpers.OnTabActivated(reclick, reclick);
|
TabLifecycleHelpers.OnTabActivated(reclick, reclick);
|
||||||
if (reclick.CurrentChannel.TellTarget is null)
|
if (reclick.CurrentChannel.TellTarget is null)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat.Themes;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// Verifies the v1.5.4 PM-1 crossfade contract: switching the active
|
// Verifies the v1.5.4 crossfade contract: switching the active
|
||||||
// theme arms TryGetActiveCrossfade for ~300ms, then the registry
|
// theme arms TryGetActiveCrossfade for ~300ms, then the registry
|
||||||
// returns to direct AbgrCache reads. A second switch within 100ms
|
// returns to direct AbgrCache reads. A second switch within 100ms
|
||||||
// keeps the lerped path active (no identity-snap). CleanUp restores
|
// keeps the lerped path active (no identity-snap). CleanUp restores
|
||||||
@@ -67,7 +67,7 @@ internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
|
|||||||
// it as "saw the start" if more than 300ms have elapsed.
|
// it as "saw the start" if more than 300ms have elapsed.
|
||||||
// Skip the mid-crossfade-switch phase in that case -- the
|
// Skip the mid-crossfade-switch phase in that case -- the
|
||||||
// lerped path is no longer active, so a second switch would
|
// lerped path is no longer active, so a second switch would
|
||||||
// re-arm a fresh crossfade and not exercise PM-1b's
|
// re-arm a fresh crossfade and not exercise its
|
||||||
// mid-flight-origin override.
|
// mid-flight-origin override.
|
||||||
if (Environment.TickCount64 - this.armedAtTickMs > 300)
|
if (Environment.TickCount64 - this.armedAtTickMs > 300)
|
||||||
{
|
{
|
||||||
@@ -83,7 +83,7 @@ internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
|
|||||||
|
|
||||||
if (!this.sawMidCrossfadeSwitch)
|
if (!this.sawMidCrossfadeSwitch)
|
||||||
{
|
{
|
||||||
// PM-Test-3 mid-crossfade-switch phase: within ~100ms of the
|
// mid-crossfade-switch phase: within ~100ms of the
|
||||||
// first observed crossfade, fire a second Switch to a THIRD
|
// first observed crossfade, fire a second Switch to a THIRD
|
||||||
// theme. ArmCrossfade must compose the current lerped state
|
// theme. ArmCrossfade must compose the current lerped state
|
||||||
// as the new origin -- TryGetActiveCrossfade still returns
|
// as the new origin -- TryGetActiveCrossfade still returns
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat.Code;
|
|||||||
|
|
||||||
namespace HellionChat.SelfTests;
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
// v1.10.0/D1: the top-tab strip marks the active tab with a fill plus an accent
|
// v1.10.0: the top-tab strip marks the active tab with a fill plus an accent
|
||||||
// underline. Drives the real TopTabBar.Draw and reads the render counter.
|
// underline. Drives the real TopTabBar.Draw and reads the render counter.
|
||||||
//
|
//
|
||||||
// "At most one", not "exactly one": the strip skips popped-out tabs, so zero
|
// "At most one", not "exactly one": the strip skips popped-out tabs, so zero
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ internal static class EventHorizon
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Event Horizon — Cosmic-Purple-Drift: helle Pastelle bekommen
|
// Cosmic purple drift: the pale pastels take a lavender tint and
|
||||||
// Lavender-Tinte, Akzent-Channels (Tell) ziehen Richtung Magenta-
|
// the accent channels (tell) pull towards magenta-violet. Channel
|
||||||
// Lila. Channel-Identität bleibt klar erkennbar.
|
// identity stays readable throughout.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"),
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ internal static class ForgeMerchantman
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Forge Merchantman — Patina-Tinte in Party/FC, Bernstein-Tinte in
|
// Patina tint on party and free company, amber on yell, alliance
|
||||||
// Yell/Alliance/CustomEmote. Channel-identity bleibt voll erhalten.
|
// and custom emotes. Channel identity is left fully intact.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"),
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ internal static class HellionArctic
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Hellion Arctic — FFXIV-Standard mit dezenter Cyan-Tinte in den
|
// The FFXIV defaults with a restrained cyan tint on the blue
|
||||||
// blauen Channels (Party/FC). Channel-Identität bleibt klar.
|
// channels (party, free company). Channel identity stays clear.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ internal static class IndigoViolet
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Indigo Violet — Lavender-Pink-Drift in Tell und LS6/7. Türkis-
|
// Lavender-pink drift on tell and linkshells 6 and 7, countered by
|
||||||
// Mint-Aurora-Counter in Party/FC und LS4. Glitter-Gold in Yell.
|
// a turquoise-mint aurora on party, free company and linkshell 4.
|
||||||
// Differenzierung zu Event Horizon: dunkler, dichter, Türkis statt Gold.
|
// Glitter gold on yell. What sets it apart from Event Horizon:
|
||||||
|
// darker, denser, and turquoise where that one goes gold.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ internal static class MintGrove
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Mint Grove — Naturthemen-Tönung: Honey-Amber in Yell-Familie,
|
// Nature-themed tint: honey amber across the yell family, a mint
|
||||||
// Mint-Drift in NoviceNetwork und Linkshell. Tell-Pink-Identität
|
// drift in novice network and linkshell. Tell keeps its pink so
|
||||||
// bleibt erhalten für Erkennbarkeit.
|
// the channel stays recognisable.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"),
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ internal static class NightBlue
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Night Blue — Royal-Blue-Tinte in Party/FC, Bronze-Gold in Yell/
|
// Royal blue on party and free company, bronze gold on yell and
|
||||||
// Alliance. Channel-identity (Tell-Pink, NN-Lime) bleibt erhalten.
|
// alliance. Channel identity is preserved -- tell stays pink,
|
||||||
|
// novice network stays lime.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ internal static class SynthwaveSunset
|
|||||||
ChatColors: new ThemeChatColors(
|
ChatColors: new ThemeChatColors(
|
||||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||||
{
|
{
|
||||||
// Synthwave Sunset — Magenta dominiert die warmen Channels (Yell/Shout/FC),
|
// Magenta carries the warm channels (yell, shout, free company),
|
||||||
// Cyan dominiert die kühlen (Tell/Party). Neon-Akzente für Status-nahe Channels.
|
// cyan the cool ones (tell, party). Neon accents on the status-
|
||||||
|
// adjacent channels.
|
||||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"),
|
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"),
|
||||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"),
|
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"),
|
||||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"),
|
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public sealed class ThemeRegistry
|
|||||||
|
|
||||||
public const string DefaultSlug = HellionArctic.Slug;
|
public const string DefaultSlug = HellionArctic.Slug;
|
||||||
|
|
||||||
// 1Hz throttle for the v1.4.8 B2 auto-refresh-on-active path. The
|
// 1Hz throttle for the v1.4.8 auto-refresh-on-active path. The
|
||||||
// Plugin.Draw hook calls RefreshActiveIfStale every frame, but the
|
// Plugin.Draw hook calls RefreshActiveIfStale every frame, but the
|
||||||
// actual File.GetLastWriteTimeUtc disk-stat only runs once per second
|
// actual File.GetLastWriteTimeUtc disk-stat only runs once per second
|
||||||
// -- 60fps would otherwise mean 3600 stats/min on the same path (more
|
// -- 60fps would otherwise mean 3600 stats/min on the same path (more
|
||||||
@@ -24,7 +24,7 @@ public sealed class ThemeRegistry
|
|||||||
private readonly string? _customThemesDir;
|
private readonly string? _customThemesDir;
|
||||||
private Theme _active;
|
private Theme _active;
|
||||||
|
|
||||||
// v1.4.8 B2: source path of the currently active custom theme. Captured
|
// v1.4.8: source path of the currently active custom theme. Captured
|
||||||
// at Switch() time so RefreshActiveIfStale does not have to reconstruct
|
// at Switch() time so RefreshActiveIfStale does not have to reconstruct
|
||||||
// a filename from the slug -- custom theme filenames are not required
|
// a filename from the slug -- custom theme filenames are not required
|
||||||
// to match the slug they declare in the JSON body. Null when the active
|
// to match the slug they declare in the JSON body. Null when the active
|
||||||
@@ -33,7 +33,7 @@ public sealed class ThemeRegistry
|
|||||||
private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs;
|
private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs;
|
||||||
private DateTime _lastActiveStamp = DateTime.MinValue;
|
private DateTime _lastActiveStamp = DateTime.MinValue;
|
||||||
|
|
||||||
// PM-1 crossfade state. Switch() captures the previous AbgrCache as a
|
// crossfade state. Switch() captures the previous AbgrCache as a
|
||||||
// VALUE-COPY (not a Theme reference) -- the built-in singletons share
|
// VALUE-COPY (not a Theme reference) -- the built-in singletons share
|
||||||
// their RecomputeAbgrCache identity, so a reference would mutate
|
// their RecomputeAbgrCache identity, so a reference would mutate
|
||||||
// alongside the new active. _crossfadeStartTickMs == long.MinValue
|
// alongside the new active. _crossfadeStartTickMs == long.MinValue
|
||||||
@@ -115,7 +115,7 @@ public sealed class ThemeRegistry
|
|||||||
// ThemeImportExportRow opens this path via Process.Start.
|
// ThemeImportExportRow opens this path via Process.Start.
|
||||||
public string? CustomThemesDir => _customThemesDir;
|
public string? CustomThemesDir => _customThemesDir;
|
||||||
|
|
||||||
// Read-only enumeration of all built-in theme slugs. T2 ThemePickerCategoryStep
|
// Read-only enumeration of all built-in theme slugs. ThemePickerCategoryStep
|
||||||
// diffs this set against ThemePicker.CategoryMapSlugs to enforce coverage.
|
// diffs this set against ThemePicker.CategoryMapSlugs to enforce coverage.
|
||||||
public IEnumerable<string> BuiltinSlugs => _builtIns.Keys;
|
public IEnumerable<string> BuiltinSlugs => _builtIns.Keys;
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ public sealed class ThemeRegistry
|
|||||||
// (see `Switch` built-in-first lookup), so saving a custom file under
|
// (see `Switch` built-in-first lookup), so saving a custom file under
|
||||||
// a built-in slug persists the file but leaves the built-in active —
|
// a built-in slug persists the file but leaves the built-in active —
|
||||||
// looks green, behaves broken. ColorPicker DrawIdleState forks
|
// looks green, behaves broken. ColorPicker DrawIdleState forks
|
||||||
// built-in themes into a custom slug before BeginEditing, M6
|
// built-in themes into a custom slug before BeginEditing,
|
||||||
// ImportFromPath renames built-in-colliding imports to <slug>_imported.
|
// ImportFromPath renames built-in-colliding imports to <slug>_imported.
|
||||||
// New call-sites must either fork first or rename to a non-built-in slug.
|
// New call-sites must either fork first or rename to a non-built-in slug.
|
||||||
public bool SaveEditingBuffer(out string targetPath)
|
public bool SaveEditingBuffer(out string targetPath)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace HellionChat.Themes;
|
namespace HellionChat.Themes;
|
||||||
|
|
||||||
// Pure stale-check for the v1.4.8 B2 theme-auto-refresh-on-active path.
|
// Pure stale-check for the v1.4.8 theme-auto-refresh-on-active path.
|
||||||
// Lives in a free helper class so the Build-Suite can exercise the diff
|
// Lives in a free helper class so the Build-Suite can exercise the diff
|
||||||
// rules without instantiating ThemeRegistry (which touches the Dalamud
|
// rules without instantiating ThemeRegistry (which touches the Dalamud
|
||||||
// log proxy and the filesystem). The rules:
|
// log proxy and the filesystem). The rules:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace HellionChat.Ui.Components;
|
namespace HellionChat.Ui.Components;
|
||||||
|
|
||||||
// B2 (PERF-B2): variable-height clip plan. ImGuiListClipper needs a constant
|
// Variable-height clip plan. ImGuiListClipper needs a constant
|
||||||
// row height, and since v1.10.0 neither density has one (compact rows wrap
|
// row height, and since v1.10.0 neither density has one (compact rows wrap
|
||||||
// too), so both compute a plan from the cached per-row heights: a lead dummy
|
// too), so both compute a plan from the cached per-row heights: a lead dummy
|
||||||
// for the rows above
|
// for the rows above
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ internal sealed class ChunkRenderer
|
|||||||
// names change every plugin reload to avoid stable cross-session linkage.
|
// names change every plugin reload to avoid stable cross-session linkage.
|
||||||
_salt = new Random().Next().ToString();
|
_salt = new Random().Next().ToString();
|
||||||
|
|
||||||
// Not yet consumed in C2/C3; E-task wiring will likely add log call-sites later.
|
// No call sites yet; logging here will likely come later.
|
||||||
_ = _logger;
|
_ = _logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
// B2-1/B2-2 render-observability: the formatted sender text the real draw
|
// render-observability: the formatted sender text the real draw
|
||||||
// path actually produced (post-ForDisplay). A SelfTest reads this after
|
// path actually produced (post-ForDisplay). A SelfTest reads this after
|
||||||
// driving DrawChunks to prove the WorldSuffixMode/NameFormMode reformat
|
// driving DrawChunks to prove the WorldSuffixMode/NameFormMode reformat
|
||||||
// reached the real render entry — never the helper in isolation. null until
|
// reached the real render entry — never the helper in isolation. null until
|
||||||
@@ -52,7 +52,7 @@ internal sealed class ChunkRenderer
|
|||||||
float lineWidth = 0f
|
float lineWidth = 0f
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// UI-7: render a copy with the sender name reformatted per the user's
|
// Render a copy with the sender name reformatted per the user's
|
||||||
// display options. Skipped in screenshot mode so the name-anonymising
|
// display options. Skipped in screenshot mode so the name-anonymising
|
||||||
// path in DrawChunk stays reliable (privacy wins). ForDisplay returns
|
// path in DrawChunk stays reliable (privacy wins). ForDisplay returns
|
||||||
// the list unchanged when nothing applies, so non-sender lists and the
|
// the list unchanged when nothing applies, so non-sender lists and the
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ internal sealed class InputBar
|
|||||||
private bool _wasInputTextHovered;
|
private bool _wasInputTextHovered;
|
||||||
private bool? _isFocusedOverride; // Test-only; null = honour per-frame Draw() value.
|
private bool? _isFocusedOverride; // Test-only; null = honour per-frame Draw() value.
|
||||||
|
|
||||||
// UI-11 plugin-disclosure arm-and-hold: holds the buffer that armed the
|
// plugin-disclosure arm-and-hold: holds the buffer that armed the
|
||||||
// disclosure warning. null = not armed. Compared by value so an edit
|
// disclosure warning. null = not armed. Compared by value so an edit
|
||||||
// re-arms and a resend on the identical buffer goes through. 1.5.6 parity
|
// re-arms and a resend on the identical buffer goes through. 1.5.6 parity
|
||||||
// (ChatInputBar 1d3b429:27).
|
// (ChatInputBar 1d3b429:27).
|
||||||
@@ -196,7 +196,7 @@ internal sealed class InputBar
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawQuickButtons();
|
DrawQuickButtons();
|
||||||
|
|
||||||
// UI-11: yellow inline warning while a plugin-only-glyph message is
|
// Yellow inline warning while a plugin-only-glyph message is
|
||||||
// armed-and-held (buffer unchanged since it armed). Renders on its own
|
// armed-and-held (buffer unchanged since it armed). Renders on its own
|
||||||
// line below the input row. 1.5.6 parity (ChatInputBar 1d3b429:93-103).
|
// line below the input row. 1.5.6 parity (ChatInputBar 1d3b429:93-103).
|
||||||
if (
|
if (
|
||||||
@@ -589,7 +589,7 @@ internal sealed class InputBar
|
|||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// UI-11: plugin-disclosure arm-and-hold. Arm + scan on the RAW
|
// Plugin-disclosure arm-and-hold. Arm + scan on the RAW
|
||||||
// _pendingMessage (NOT the trimmed `text`) so the Draw warning gate
|
// _pendingMessage (NOT the trimmed `text`) so the Draw warning gate
|
||||||
// (_pendingMessage == _disclosureArmedBuffer) matches byte-for-byte even
|
// (_pendingMessage == _disclosureArmedBuffer) matches byte-for-byte even
|
||||||
// when the buffer has leading/trailing whitespace. 1.5.6 armed/held/
|
// when the buffer has leading/trailing whitespace. 1.5.6 armed/held/
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
private PayloadHandler? _handler;
|
private PayloadHandler? _handler;
|
||||||
|
|
||||||
// B3-5: scroll-to-bottom state. Per-instance, so pop-out windows (own
|
// Scroll-to-bottom state. Per-instance, so pop-out windows (own
|
||||||
// MessageList instance, PluginHostFactory.cs:263-266) isolate automatically —
|
// MessageList instance, PluginHostFactory.cs:263-266) isolate automatically —
|
||||||
// the old 1.5.6 updateScrollState flag is NOT needed here.
|
// the old 1.5.6 updateScrollState flag is NOT needed here.
|
||||||
private bool _scrolledUp;
|
private bool _scrolledUp;
|
||||||
private bool _scrollToBottomRequested;
|
private bool _scrollToBottomRequested;
|
||||||
|
|
||||||
// B2: the height cache is only valid while these inputs are unchanged.
|
// The height cache is only valid while these inputs are unchanged.
|
||||||
// FontManager's own fingerprint covers font sizes only, not density / the two
|
// FontManager's own fingerprint covers font sizes only, not density / the two
|
||||||
// name-display modes / width — a stale height would misplace the clipper dummies.
|
// name-display modes / width — a stale height would misplace the clipper dummies.
|
||||||
// Per tab, not per list: the old single field let a width change in tab A mark
|
// Per tab, not per list: the old single field let a width change in tab A mark
|
||||||
@@ -73,12 +73,12 @@ internal sealed class MessageList
|
|||||||
return snap;
|
return snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelfTest hook (B3-5 reset-invariant, REQUIRED — not optional). Lets
|
// SelfTest hook (reset-invariant, REQUIRED — not optional). Lets
|
||||||
// ScrollSnapDecisionStep flip the request flag without a real click, so the
|
// ScrollSnapDecisionStep flip the request flag without a real click, so the
|
||||||
// post-snap reset can be asserted; without it only the OR branch is testable.
|
// post-snap reset can be asserted; without it only the OR branch is testable.
|
||||||
internal void RequestScrollToBottomForSelfTest() => _scrollToBottomRequested = true;
|
internal void RequestScrollToBottomForSelfTest() => _scrollToBottomRequested = true;
|
||||||
|
|
||||||
// SelfTest hook (B2): runs the real planner against a caller fixture so the
|
// SelfTest hook: runs the real planner against a caller fixture so the
|
||||||
// step asserts the plan without a live scroll child (GetScrollY is garbage headless).
|
// step asserts the plan without a live scroll child (GetScrollY is garbage headless).
|
||||||
internal CardClipPlan PlanCardClipForSelfTest(
|
internal CardClipPlan PlanCardClipForSelfTest(
|
||||||
IReadOnlyList<float> heights,
|
IReadOnlyList<float> heights,
|
||||||
@@ -86,7 +86,7 @@ internal sealed class MessageList
|
|||||||
float viewportHeight
|
float viewportHeight
|
||||||
) => CardClipPlanner.Plan(heights, scrollY, viewportHeight);
|
) => CardClipPlanner.Plan(heights, scrollY, viewportHeight);
|
||||||
|
|
||||||
// SelfTest hook (B2): drives the live invalidation, returns the tab's remaining
|
// SelfTest hook: drives the live invalidation, returns the tab's remaining
|
||||||
// cached-height count so the step can assert the drop. nowMs is a parameter so
|
// cached-height count so the step can assert the drop. nowMs is a parameter so
|
||||||
// the step can step past the settle window without sleeping (v1.10.0).
|
// the step can step past the settle window without sleeping (v1.10.0).
|
||||||
internal int RunHeightCacheInvalidationForSelfTest(Tab tab, float contentWidth, long nowMs)
|
internal int RunHeightCacheInvalidationForSelfTest(Tab tab, float contentWidth, long nowMs)
|
||||||
@@ -165,7 +165,7 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
MeasureTimestampColumn(tab);
|
MeasureTimestampColumn(tab);
|
||||||
|
|
||||||
// B2: drop stale cached heights before the snapshot draw. Both densities
|
// Drop stale cached heights before the snapshot draw. Both densities
|
||||||
// need this now -- compact rows are not constant height either, they wrap.
|
// need this now -- compact rows are not constant height either, they wrap.
|
||||||
// Width read here while it is valid.
|
// Width read here while it is valid.
|
||||||
InvalidateHeightCacheIfLayoutChanged(
|
InvalidateHeightCacheIfLayoutChanged(
|
||||||
@@ -187,7 +187,7 @@ internal sealed class MessageList
|
|||||||
var frozen = _fingerprintGates[tab.Identifier].IsPending;
|
var frozen = _fingerprintGates[tab.Identifier].IsPending;
|
||||||
DrawRows(tab, messages, compact ? _drawCompactRow : _drawCardRow, frozen);
|
DrawRows(tab, messages, compact ? _drawCompactRow : _drawCardRow, frozen);
|
||||||
|
|
||||||
// B3-5: scroll values are frame-constant inside the child, so this
|
// Scroll values are frame-constant inside the child, so this
|
||||||
// reflects the current frame's state wherever it runs; kept after the
|
// reflects the current frame's state wherever it runs; kept after the
|
||||||
// render to mirror the 1.5.6 end-of-DrawMessageLog placement.
|
// render to mirror the 1.5.6 end-of-DrawMessageLog placement.
|
||||||
_scrolledUp = ImGui.GetScrollMaxY() - ImGui.GetScrollY() > 1f;
|
_scrolledUp = ImGui.GetScrollMaxY() - ImGui.GetScrollY() > 1f;
|
||||||
@@ -282,7 +282,7 @@ internal sealed class MessageList
|
|||||||
ImGui.SetCursorPos(origin with { X = origin.X + _stampColumnWidth });
|
ImGui.SetCursorPos(origin with { X = origin.X + _stampColumnWidth });
|
||||||
}
|
}
|
||||||
|
|
||||||
// B3-5: Discord-style full-width bar pinned to the bottom edge of the
|
// Discord-style full-width bar pinned to the bottom edge of the
|
||||||
// visible region while the user is scrolled up. Geometry comes from window
|
// visible region while the user is scrolled up. Geometry comes from window
|
||||||
// pos + size (visible region), never from the content flow: when scrolled
|
// pos + size (visible region), never from the content flow: when scrolled
|
||||||
// up the visible bottom sits above the content bottom, so the
|
// up the visible bottom sits above the content bottom, so the
|
||||||
@@ -335,7 +335,7 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
private void DrawCompactRow(Message message, string? previousStamp)
|
private void DrawCompactRow(Message message, string? previousStamp)
|
||||||
{
|
{
|
||||||
// B2-1/B2-2: render the sender through DrawChunks (the name-aware path
|
// Render the sender through DrawChunks (the name-aware path
|
||||||
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
|
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
|
||||||
// flat SenderSource.TextValue string. message.Sender already carries the
|
// flat SenderSource.TextValue string. message.Sender already carries the
|
||||||
// channel brackets/colon as ChunkSource.None wrappers (MessageManager
|
// channel brackets/colon as ChunkSource.None wrappers (MessageManager
|
||||||
@@ -566,7 +566,7 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
private void DrawCardRow(Message message, string? previousStamp)
|
private void DrawCardRow(Message message, string? previousStamp)
|
||||||
{
|
{
|
||||||
// B2-1/B2-2: sender via DrawChunks (name-aware path), on its own line
|
// Sender via DrawChunks (name-aware path), on its own line
|
||||||
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
|
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
|
||||||
// SameLine after the sender). The 1.5.6 channel-colour push on the
|
// SameLine after the sender). The 1.5.6 channel-colour push on the
|
||||||
// sender is deferred styling polish (deferred to v1.9.0); plain
|
// sender is deferred styling polish (deferred to v1.9.0); plain
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ internal sealed class AboutTab
|
|||||||
LastHonorificStatusKey = kind.ToString();
|
LastHonorificStatusKey = kind.ToString();
|
||||||
var colors = _themes.Active.Colors;
|
var colors = _themes.Active.Colors;
|
||||||
|
|
||||||
// Null-safety via the `is { } v` pattern, never `.Value` raw (spec SEC-2):
|
// Null-safety via the `is { } v` pattern, never `.Value` raw :
|
||||||
// the version is bound only on the arms that have it; the impossible
|
// the version is bound only on the arms that have it; the impossible
|
||||||
// Detected/Incompatible-without-version state falls through to default.
|
// Detected/Incompatible-without-version state falls through to default.
|
||||||
switch (kind)
|
switch (kind)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ internal sealed class ThemePicker
|
|||||||
(HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false),
|
(HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
// T2 ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs
|
// ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs
|
||||||
// to enforce coverage. Kept on the static map so the test does not pierce instance state.
|
// to enforce coverage. Kept on the static map so the test does not pierce instance state.
|
||||||
internal static IEnumerable<string> CategoryMapSlugs => CategoryMap.SelectMany(c => c.Slugs);
|
internal static IEnumerable<string> CategoryMapSlugs => CategoryMap.SelectMany(c => c.Slugs);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ internal sealed class Sidebar
|
|||||||
{
|
{
|
||||||
public const float IconOnlyWidth = 38f;
|
public const float IconOnlyWidth = 38f;
|
||||||
|
|
||||||
// B1-3a: expanded sidebar width is user-configurable (Config.SidebarWidth),
|
// Expanded sidebar width is user-configurable (Config.SidebarWidth),
|
||||||
// clamped to these bounds (matches the ChannelsTab slider range). Replaces
|
// clamped to these bounds (matches the ChannelsTab slider range). Replaces
|
||||||
// the old fixed 150px ExpandedWidth constant.
|
// the old fixed 150px ExpandedWidth constant.
|
||||||
public const float MinSidebarWidth = 40f;
|
public const float MinSidebarWidth = 40f;
|
||||||
@@ -38,7 +38,7 @@ internal sealed class Sidebar
|
|||||||
// previously active one -- that row was still active when it was painted.
|
// previously active one -- that row was still active when it was painted.
|
||||||
internal int LastRenderedActiveSurfaceCount { get; private set; }
|
internal int LastRenderedActiveSurfaceCount { get; private set; }
|
||||||
|
|
||||||
// B3-2 render observability: counts greeted glyphs actually drawn this frame.
|
// render observability: counts greeted glyphs actually drawn this frame.
|
||||||
// Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start.
|
// Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start.
|
||||||
// The SelfTest reads it after driving the real Draw — no dead service roundtrip.
|
// The SelfTest reads it after driving the real Draw — no dead service roundtrip.
|
||||||
internal int LastRenderedGreetedGlyphCount;
|
internal int LastRenderedGreetedGlyphCount;
|
||||||
@@ -48,7 +48,7 @@ internal sealed class Sidebar
|
|||||||
// beside it.
|
// beside it.
|
||||||
private const float PinGlyphScale = 0.6f;
|
private const float PinGlyphScale = 0.6f;
|
||||||
|
|
||||||
// B3-4 render observability: section headers actually drawn this frame.
|
// render observability: section headers actually drawn this frame.
|
||||||
// Incremented only in the real header branch; reset at Draw start.
|
// Incremented only in the real header branch; reset at Draw start.
|
||||||
internal int LastDrawnSectionHeaderCount;
|
internal int LastDrawnSectionHeaderCount;
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ internal sealed class Sidebar
|
|||||||
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
|
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
|
||||||
var dl = ImGui.GetWindowDrawList();
|
var dl = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
// B3-4 sectioned render order (1.5.6 parity): persistent → pinned
|
// sectioned render order (1.5.6 parity): persistent → pinned
|
||||||
// TempTabs → unpinned TempTabs. Only the display sequence regroups;
|
// TempTabs → unpinned TempTabs. Only the display sequence regroups;
|
||||||
// the tab list itself stays untouched and every row keeps its
|
// the tab list itself stays untouched and every row keeps its
|
||||||
// ORIGINAL list index for PushID, so an open context-menu popup
|
// ORIGINAL list index for PushID, so an open context-menu popup
|
||||||
@@ -250,7 +250,7 @@ internal sealed class Sidebar
|
|||||||
// Only split off a separate pop-out hit area when there's room for
|
// Only split off a separate pop-out hit area when there's room for
|
||||||
// both buttons. Below that, the whole row stays as a single
|
// both buttons. Below that, the whole row stays as a single
|
||||||
// selectable strip without the pop-out affordance.
|
// selectable strip without the pop-out affordance.
|
||||||
// A3: gate the pop-out affordance on the expanded sidebar too. In
|
// Gate the pop-out affordance on the expanded sidebar too. In
|
||||||
// icon-only mode avail still clears the width threshold, which used to
|
// icon-only mode avail still clears the width threshold, which used to
|
||||||
// paint the pop-out glyph over the tab icon. The row stays a single
|
// paint the pop-out glyph over the tab icon. The row stays a single
|
||||||
// selectable strip when collapsed; right-click pop-out is unaffected.
|
// selectable strip when collapsed; right-click pop-out is unaffected.
|
||||||
@@ -476,7 +476,7 @@ internal sealed class Sidebar
|
|||||||
// The hit area sits at the LEFT edge of the row, but the item must
|
// The hit area sits at the LEFT edge of the row, but the item must
|
||||||
// be submitted AFTER TabContextMenu.Draw — any interactive item
|
// be submitted AFTER TabContextMenu.Draw — any interactive item
|
||||||
// between the row button and the popup call would steal the
|
// between the row button and the popup call would steal the
|
||||||
// right-click trigger (B3-1 ordering constraint).
|
// right-click trigger (ordering constraint).
|
||||||
ImGui.SetCursorScreenPos(origin);
|
ImGui.SetCursorScreenPos(origin);
|
||||||
|
|
||||||
// CheckCircle = greeted, plain Check = still pending (1.5.6 mapping).
|
// CheckCircle = greeted, plain Check = still pending (1.5.6 mapping).
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ internal static class TabContextMenu
|
|||||||
ClearPendingRename();
|
ClearPendingRename();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-tab notification sound (B3-3). The checkbox gates the picker so
|
// Per-tab notification sound. The checkbox gates the picker so
|
||||||
// tabs that never want a sound keep the popup short.
|
// tabs that never want a sound keep the popup short.
|
||||||
if (
|
if (
|
||||||
ImGui.Checkbox(
|
ImGui.Checkbox(
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class DbViewer : Window
|
|||||||
private int CurrentPage = 1;
|
private int CurrentPage = 1;
|
||||||
private string SimpleSearchTerm = "";
|
private string SimpleSearchTerm = "";
|
||||||
|
|
||||||
// v1.4.8 H2: opt-in full-text search across the whole DB via FTS5.
|
// v1.4.8: opt-in full-text search across the whole DB via FTS5.
|
||||||
// Transient UI state (per-session), not persisted -- users opt in fresh
|
// Transient UI state (per-session), not persisted -- users opt in fresh
|
||||||
// every time so they always see the page-filter as the default mode.
|
// every time so they always see the page-filter as the default mode.
|
||||||
private bool UseFullTextSearch;
|
private bool UseFullTextSearch;
|
||||||
@@ -232,7 +232,7 @@ public class DbViewer : Window
|
|||||||
tooltipRight: Language.Page_ArrowRight_Tooltip
|
tooltipRight: Language.Page_ArrowRight_Tooltip
|
||||||
);
|
);
|
||||||
|
|
||||||
// Full-text search toggle (v1.4.8 H2). IsFtsIndexBuilt is a cached
|
// Full-text search toggle (v1.4.8). IsFtsIndexBuilt is a cached
|
||||||
// volatile bool in MessageStore -- single field read per frame, no
|
// volatile bool in MessageStore -- single field read per frame, no
|
||||||
// SELECT count(*). ImRaii.Disabled blocks any click while the index
|
// SELECT count(*). ImRaii.Disabled blocks any click while the index
|
||||||
// is still being built, so no defensive force-off branch needed
|
// is still being built, so no defensive force-off branch needed
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ internal sealed class InputPreview : Window
|
|||||||
{
|
{
|
||||||
ImGui.TextUnformatted(Language.Options_Preview_Header);
|
ImGui.TextUnformatted(Language.Options_Preview_Header);
|
||||||
|
|
||||||
// Primary path (A2) resets the Lender counter in MainWindow.Draw();
|
// Primary path resets the Lender counter in MainWindow.Draw();
|
||||||
// this fallback covers the edge-case where MainWindow is closed but
|
// this fallback covers the edge-case where MainWindow is closed but
|
||||||
// InputPreview is still open, preventing handler pool growth.
|
// InputPreview is still open, preventing handler pool growth.
|
||||||
if (!_mainWindow.IsOpen)
|
if (!_mainWindow.IsOpen)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ internal sealed class ChannelPopoutPool
|
|||||||
// A popped tab gets its own input bar, so strip stale tell state first —
|
// A popped tab gets its own input bar, so strip stale tell state first —
|
||||||
// otherwise a popped-out stale-tell tab would be a send surface that
|
// otherwise a popped-out stale-tell tab would be a send surface that
|
||||||
// bypasses the click-path activation strip. Previous = the main window's
|
// bypasses the click-path activation strip. Previous = the main window's
|
||||||
// active tab; popping the active tab itself must not strip (TR-4 guard).
|
// active tab; popping the active tab itself must not strip (guard).
|
||||||
TabLifecycleHelpers.OnTabActivated(tab, Plugin.Instance.MainWindow?.ActiveTab);
|
TabLifecycleHelpers.OnTabActivated(tab, Plugin.Instance.MainWindow?.ActiveTab);
|
||||||
|
|
||||||
var slot = _slots.TryReserve(tab.Identifier);
|
var slot = _slots.TryReserve(tab.Identifier);
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IFocusableChatWindow — this pop-out's own InputBar carries the focus state
|
// IFocusableChatWindow — this pop-out's own InputBar carries the focus state
|
||||||
// the keybind tail checks when deciding whether to route at this surface (C3).
|
// the keybind tail checks when deciding whether to route at this surface.
|
||||||
public bool HasFocusedInput => _input.IsFocused;
|
public bool HasFocusedInput => _input.IsFocused;
|
||||||
|
|
||||||
// Arm-and-hold the one-frame Activate flag; the pop-out's Draw applies the
|
// Arm-and-hold the one-frame Activate flag; the pop-out's Draw applies the
|
||||||
|
|||||||
@@ -82,14 +82,14 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
|
|||||||
RespectCloseHotkey = false;
|
RespectCloseHotkey = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI-12: per-window focus-dependent opacity. ResolveBgAlpha stays guard-free
|
// Per-window focus-dependent opacity. ResolveBgAlpha stays guard-free
|
||||||
// and pure so the self-test can drive it directly; PreDraw owns the guard +
|
// and pure so the self-test can drive it directly; PreDraw owns the guard +
|
||||||
// wiring. 1.5.6 parity (focused → WindowOpacity, unfocused →
|
// wiring. 1.5.6 parity (focused → WindowOpacity, unfocused →
|
||||||
// WindowOpacityInactive, ChatLogWindow.PreOpenCheck 1d3b429:724).
|
// WindowOpacityInactive, ChatLogWindow.PreOpenCheck 1d3b429:724).
|
||||||
internal float ResolveBgAlpha(bool isFocused) =>
|
internal float ResolveBgAlpha(bool isFocused) =>
|
||||||
isFocused ? Plugin.Config.WindowOpacity : Plugin.Config.WindowOpacityInactive;
|
isFocused ? Plugin.Config.WindowOpacity : Plugin.Config.WindowOpacityInactive;
|
||||||
|
|
||||||
// B1-2 / P7: rebuild flags from a fresh base every frame so toggling
|
// Rebuild flags from a fresh base every frame so toggling
|
||||||
// CanMove/CanResize/ShowTitleBar back on actually CLEARS NoMove/NoResize/
|
// CanMove/CanResize/ShowTitleBar back on actually CLEARS NoMove/NoResize/
|
||||||
// NoTitleBar (not accumulating). Move/resize/title-bar logic as 1.5.6
|
// NoTitleBar (not accumulating). Move/resize/title-bar logic as 1.5.6
|
||||||
// (ChatLogWindow.PreOpenCheck 1d3b429:703-710); base flags = today's
|
// (ChatLogWindow.PreOpenCheck 1d3b429:703-710); base flags = today's
|
||||||
@@ -240,7 +240,7 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IFocusableChatWindow — the keybind tail resolves which surface owns the
|
// IFocusableChatWindow — the keybind tail resolves which surface owns the
|
||||||
// input focus before routing a channel-set/REPLY/prefill at it (C3).
|
// input focus before routing a channel-set/REPLY/prefill at it.
|
||||||
public bool HasFocusedInput => _input.IsFocused;
|
public bool HasFocusedInput => _input.IsFocused;
|
||||||
|
|
||||||
// Arm-and-hold: field writes only, safe from the framework thread; the draw
|
// Arm-and-hold: field writes only, safe from the framework thread; the draw
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace HellionChat.Ui.Windows;
|
namespace HellionChat.Ui.Windows;
|
||||||
|
|
||||||
// `internal` to match the Plugin.SettingsWindow property in W2; `public` here
|
// `internal` to match the Plugin.SettingsWindow property; `public` here
|
||||||
// would raise CS0053 against the internal members. Matches MainWindow shape.
|
// would raise CS0053 against the internal members. Matches MainWindow shape.
|
||||||
internal sealed class SettingsWindow : Window
|
internal sealed class SettingsWindow : Window
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ internal static class ColourUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Modulates the alpha byte of an ABGR color by a factor in [0, 1].
|
// Modulates the alpha byte of an ABGR color by a factor in [0, 1].
|
||||||
// RGB stays intact. Used by the PM-3 hover-lerp path where each
|
// RGB stays intact. Used by the hover-lerp path where each
|
||||||
// frame produces a fractional alpha value but the colour itself
|
// frame produces a fractional alpha value but the colour itself
|
||||||
// must not shift.
|
// must not shift.
|
||||||
internal static uint ApplyAlpha(uint abgr, float alphaFactor)
|
internal static uint ApplyAlpha(uint abgr, float alphaFactor)
|
||||||
@@ -103,7 +103,7 @@ internal static class ColourUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mixes an ABGR colour's RGB channels toward white (0xFF) by factor t in
|
// Mixes an ABGR colour's RGB channels toward white (0xFF) by factor t in
|
||||||
// [0, 1]; the alpha byte is left untouched. A1 hover-sheen accent-tint:
|
// [0, 1]; the alpha byte is left untouched. Hover-sheen accent tint:
|
||||||
// a low factor nudges the sweep toward the element's accent hue without
|
// a low factor nudges the sweep toward the element's accent hue without
|
||||||
// going fully saturated (effect level stays "subtle"). RGB-only on
|
// going fully saturated (effect level stays "subtle"). RGB-only on
|
||||||
// purpose -- DrawHoverSheen owns the alpha falloff.
|
// purpose -- DrawHoverSheen owns the alpha falloff.
|
||||||
|
|||||||
@@ -90,10 +90,9 @@ internal static class ImGuiUtil
|
|||||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]))
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]))
|
||||||
ImGui.TextUnformatted("(?)");
|
ImGui.TextUnformatted("(?)");
|
||||||
|
|
||||||
// AllowWhenDisabled — ohne das Flag liefert IsItemHovered bei
|
// AllowWhenDisabled, because without it IsItemHovered returns false on a
|
||||||
// ausgegrauten Settings false, der User könnte nicht mehr lesen
|
// greyed-out setting -- and a reader who cannot find out why an option is
|
||||||
// warum eine Option nicht aktiv ist. Genau dann braucht er den
|
// inactive is exactly the reader who needs the tooltip most.
|
||||||
// Hover-Tooltip aber am dringendsten.
|
|
||||||
if (!ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
if (!ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using HellionChat._Helpers;
|
|||||||
|
|
||||||
namespace HellionChat.Util;
|
namespace HellionChat.Util;
|
||||||
|
|
||||||
// UI-7: produces a render-only view of a chunk list with the sender's name
|
// Produces a render-only view of a chunk list with the sender's name
|
||||||
// reformatted per the user's WorldSuffixMode / NameFormMode. Called from
|
// reformatted per the user's WorldSuffixMode / NameFormMode. Called from
|
||||||
// ChatLogWindow.DrawChunks on every draw — it never mutates the input, so the
|
// ChatLogWindow.DrawChunks on every draw — it never mutates the input, so the
|
||||||
// stored message (the Sender BLOB in the DB) stays byte-for-byte unchanged and
|
// stored message (the Sender BLOB in the DB) stays byte-for-byte unchanged and
|
||||||
@@ -76,7 +76,7 @@ internal static class SenderNameDisplay
|
|||||||
// text, and any cross-world icon) with one formatted chunk that keeps
|
// text, and any cross-world icon) with one formatted chunk that keeps
|
||||||
// the PlayerPayload link so the name stays clickable. Dropping the
|
// the PlayerPayload link so the name stays clickable. Dropping the
|
||||||
// original sender icon is an accepted trade-off and only happens once
|
// original sender icon is an accepted trade-off and only happens once
|
||||||
// the user moves UI-7 off its defaults.
|
// the user moves off its defaults.
|
||||||
// Channel brackets and colons are ChunkSource.None wrappers outside the
|
// Channel brackets and colons are ChunkSource.None wrappers outside the
|
||||||
// Sender span — they are preserved untouched by the copy loops below.
|
// Sender span — they are preserved untouched by the copy loops below.
|
||||||
var copy = new List<Chunk>(chunks.Count);
|
var copy = new List<Chunk>(chunks.Count);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ internal static class TabLifecycleHelpers
|
|||||||
|
|
||||||
public static bool ShouldStripOnSave(Tab t) => IsInUnpinnedPool(t);
|
public static bool ShouldStripOnSave(Tab t) => IsInUnpinnedPool(t);
|
||||||
|
|
||||||
// GP-04: clear every Tab.PopOut at load time. The pool binds later, so at
|
// Clear every Tab.PopOut at load time. The pool binds later, so at
|
||||||
// load NO tab can own a slot — a persisted PopOut=true is always a stale flag
|
// load NO tab can own a slot — a persisted PopOut=true is always a stale flag
|
||||||
// with no window. Unconditional (pinned included) because pinned TempTabs
|
// with no window. Unconditional (pinned included) because pinned TempTabs
|
||||||
// survive the load and are the main stale-flag source; a !IsPinned filter
|
// survive the load and are the main stale-flag source; a !IsPinned filter
|
||||||
@@ -91,7 +91,7 @@ internal static class TabLifecycleHelpers
|
|||||||
// partner-name label) so a normal typed line cannot route as a silent /tell
|
// partner-name label) so a normal typed line cannot route as a silent /tell
|
||||||
// to the old partner — the same privacy guard StripTellBindingOnPromote
|
// to the old partner — the same privacy guard StripTellBindingOnPromote
|
||||||
// applies on promote. Re-activating the already-active tab must NOT strip
|
// applies on promote. Re-activating the already-active tab must NOT strip
|
||||||
// (a live game-tell would lose its context, TR-4); a tab carrying its own
|
// (a live game-tell would lose its context); a tab carrying its own
|
||||||
// Tab.TellTarget is a real tell binding (leg1) and is left intact.
|
// Tab.TellTarget is a real tell binding (leg1) and is left intact.
|
||||||
internal static void OnTabActivated(Tab tab, Tab? previous)
|
internal static void OnTabActivated(Tab tab, Tab? previous)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace HellionChat._Helpers;
|
namespace HellionChat._Helpers;
|
||||||
|
|
||||||
// UI-11 pure decision helper: does a message about to be sent carry a glyph
|
// pure decision helper: does a message about to be sent carry a glyph
|
||||||
// that only renders correctly for players running HellionChat or a similar
|
// that only renders correctly for players running HellionChat or a similar
|
||||||
// plugin? Those are FFXIV Private-Use-Area icon codepoints (the same range
|
// plugin? Those are FFXIV Private-Use-Area icon codepoints (the same range
|
||||||
// SeIconChar covers); a recipient without a plugin sees an empty box.
|
// SeIconChar covers); a recipient without a plugin sees an empty box.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace HellionChat._Helpers;
|
namespace HellionChat._Helpers;
|
||||||
|
|
||||||
// UI-7 pure decision helper: builds the display string for a sender's name +
|
// pure decision helper: builds the display string for a sender's name +
|
||||||
// world per the user's NameFormMode / WorldSuffixMode. Dalamud-free so the
|
// world per the user's NameFormMode / WorldSuffixMode. Dalamud-free so the
|
||||||
// Build Suite can cover every combination; SenderNameDisplay feeds it the name
|
// Build Suite can cover every combination; SenderNameDisplay feeds it the name
|
||||||
// and world it pulled from the PlayerPayload.
|
// and world it pulled from the PlayerPayload.
|
||||||
|
|||||||
Reference in New Issue
Block a user