refactor(config): delete what was never read, restore what was only orphaned
Block D of v1.12.0. The line between the two is the whole job, and I got it wrong once on the way: six cache fields on Tab looked dead because nothing writes them, and nothing writes them because AutoTellTabTint and TabTintCache went out with the chat window incf4705e. Deleting the fields would have cemented a loss instead of recording a decision. So they are back, and the sidebar uses them again: an auto-tell tab is tinted and glyphed from its partner, twelve colours against seven icons. Four open tells are no longer four identical envelopes in one colour. Their own header promised the same partner keeps its colour "across sessions" while hashing with string.GetHashCode, which .NET salts per process -- every game start reshuffled every tab. FNV-1a now, with a lowbias32 finalizer that is not decoration: without it a probe over 144 similar keys reached six of the twelve colours, because the caller takes the low bits with a modulo and FNV leaves those correlated. Three pinned values guard it, which is also the only assertion that can catch a regression to a salted hash. The same question, asked of the three hide conditions this block had quietly orphaned: HideDuringCutscenes, HideInBattle, HideWhenNotLoggedIn all had readers in v1.5.6 and lost them in the same commit. Two of them are states rather than conditions -- a cutscene the user dismissed stays dismissed until it ends, and combat must not seize a chat that is already hidden for another reason -- so they come back as a small state machine with eight pinned transitions, and three toggles whose labels were already translated in all 25 languages. Actually deleted, with a reader search each time: - Six per-tab hide fields. Their reader was the pop-out window and it stopped consulting them incf4705e. Per-tab was the wrong unit anyway: "hide during cutscenes" is a statement about the screen. - Tab.ChatCodes, whose migration the v16 schema gate had already made unreachable. - InactivityHideTimeout and InactivityHideActiveDuringBattle, MaxLinesToRender which had stopped bounding anything, and the 155 lines of Configuration.UpdateFrom with no caller at all. Config version 25, at all three places that carry it. No migration step: the gate only refuses anything under 16 and Json.NET drops keys it does not know, so the deleted fields simply stop being written. One thing a review pass caught that matters more than any of the above: the clone parity guard had gone hollow. It compares collections by count, ChatCodes was the only collection the probe seeded, and removing it left the guard comparing zero against zero. Verified by making Tab.Clone discard both remaining collections and watching every assertion stay green. The probe seeds them now, and the same sabotage fails as it should.
This commit is contained in:
+14
-183
@@ -35,7 +35,7 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
internal const int LatestVersion = 24;
|
||||
internal const int LatestVersion = 25;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
@@ -184,8 +184,6 @@ public class Configuration : IPluginConfiguration
|
||||
// v1.2.1: default flipped false → true for consistency with other hide defaults.
|
||||
public bool HideInNewGamePlusMenu = true;
|
||||
public bool HideWhenInactive;
|
||||
public int InactivityHideTimeout = 10;
|
||||
public bool InactivityHideActiveDuringBattle = true;
|
||||
|
||||
[Obsolete("Use InactivityHideChannelsV2 instead")]
|
||||
public Dictionary<ChatType, ChatSource> InactivityHideChannels = [];
|
||||
@@ -243,7 +241,6 @@ public class Configuration : IPluginConfiguration
|
||||
// UI-11: warn before sending a message that carries plugin-only glyphs.
|
||||
public bool NotifyPluginDisclosure = true;
|
||||
public bool KeepInputFocus = true;
|
||||
public int MaxLinesToRender = 2_500; // 1-10000
|
||||
public bool Use24HourClock = true;
|
||||
public bool ShowEmotes = true;
|
||||
public HashSet<string> BlockedEmotes = [];
|
||||
@@ -310,164 +307,6 @@ public class Configuration : IPluginConfiguration
|
||||
// v22 field: MainWindow layout mode (sidebar vs. horizontal top tabs).
|
||||
// Initializer doubles as the migration default for configs loaded at v21.
|
||||
public MainWindowLayoutMode MainWindowLayoutMode = MainWindowLayoutMode.Sidebar;
|
||||
|
||||
public void UpdateFrom(Configuration other, bool backToOriginal)
|
||||
{
|
||||
if (backToOriginal)
|
||||
{
|
||||
// NOTE (v1.8.0): this only flips the PopOut flag back. If a future
|
||||
// caller ever wires UpdateFrom(backToOriginal: true) to a live
|
||||
// settings-cancel path, that CALL-SITE must also iterate
|
||||
// ChannelPopoutPool.TryClose over the affected Tab.Identifiers,
|
||||
// otherwise pool windows stay IsOpen=true while the flag is false
|
||||
// (orphan window). The pool is not reachable from this POCO by design.
|
||||
foreach (var tab in Tabs.Where(t => t.PopOut))
|
||||
tab.PopOut = false;
|
||||
}
|
||||
|
||||
HideChat = other.HideChat;
|
||||
HideDuringCutscenes = other.HideDuringCutscenes;
|
||||
HideWhenNotLoggedIn = other.HideWhenNotLoggedIn;
|
||||
HideWhenUiHidden = other.HideWhenUiHidden;
|
||||
HideInLoadingScreens = other.HideInLoadingScreens;
|
||||
HideInBattle = other.HideInBattle;
|
||||
HideInNewGamePlusMenu = other.HideInNewGamePlusMenu;
|
||||
HideWhenInactive = other.HideWhenInactive;
|
||||
InactivityHideTimeout = other.InactivityHideTimeout;
|
||||
InactivityHideActiveDuringBattle = other.InactivityHideActiveDuringBattle;
|
||||
InactivityHideChannelsV2 = other.InactivityHideChannelsV2.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => pair.Value
|
||||
);
|
||||
InactivityHideExtraChatAll = other.InactivityHideExtraChatAll;
|
||||
InactivityHideExtraChatChannels = other.InactivityHideExtraChatChannels.ToHashSet();
|
||||
ShowHideButton = other.ShowHideButton;
|
||||
NativeItemTooltips = other.NativeItemTooltips;
|
||||
ScreenshotMode = other.ScreenshotMode;
|
||||
PrettierTimestamps = other.PrettierTimestamps;
|
||||
MoreCompactPretty = other.MoreCompactPretty;
|
||||
HideSameTimestamps = other.HideSameTimestamps;
|
||||
ShowNoviceNetwork = other.ShowNoviceNetwork;
|
||||
SidebarTabView = other.SidebarTabView;
|
||||
PrintChangelog = other.PrintChangelog;
|
||||
OnlyPreviewIf = other.OnlyPreviewIf;
|
||||
PreviewMinimum = other.PreviewMinimum;
|
||||
PreviewPosition = other.PreviewPosition;
|
||||
CommandHelpSide = other.CommandHelpSide;
|
||||
KeybindMode = other.KeybindMode;
|
||||
LanguageOverride = other.LanguageOverride;
|
||||
CanMove = other.CanMove;
|
||||
CanResize = other.CanResize;
|
||||
ShowTitleBar = other.ShowTitleBar;
|
||||
ShowPopOutTitleBar = other.ShowPopOutTitleBar;
|
||||
DatabaseBattleMessages = other.DatabaseBattleMessages;
|
||||
FilterIncludePreviousSessions = other.FilterIncludePreviousSessions;
|
||||
SortAutoTranslate = other.SortAutoTranslate;
|
||||
CollapseDuplicateMessages = other.CollapseDuplicateMessages;
|
||||
CollapseKeepUniqueLinks = other.CollapseKeepUniqueLinks;
|
||||
SymbolPickerEnabled = other.SymbolPickerEnabled;
|
||||
PlaySounds = other.PlaySounds;
|
||||
CustomSoundVolume = other.CustomSoundVolume;
|
||||
NotifyFailedTell = other.NotifyFailedTell;
|
||||
NotifyPluginDisclosure = other.NotifyPluginDisclosure;
|
||||
KeepInputFocus = other.KeepInputFocus;
|
||||
MaxLinesToRender = other.MaxLinesToRender;
|
||||
Use24HourClock = other.Use24HourClock;
|
||||
ShowEmotes = other.ShowEmotes;
|
||||
// Deep-copy so settings window edits don't leak into live config before Save.
|
||||
BlockedEmotes = new HashSet<string>(other.BlockedEmotes);
|
||||
FontsEnabled = other.FontsEnabled;
|
||||
ItalicEnabled = other.ItalicEnabled;
|
||||
ExtraGlyphRanges = other.ExtraGlyphRanges;
|
||||
FontSizeV2 = other.FontSizeV2;
|
||||
GlobalFontV2 = other.GlobalFontV2;
|
||||
JapaneseFontV2 = other.JapaneseFontV2;
|
||||
ItalicFontV2 = other.ItalicFontV2;
|
||||
SymbolsFontSizeV2 = other.SymbolsFontSizeV2;
|
||||
TooltipOffset = other.TooltipOffset;
|
||||
ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
|
||||
ColorSelectedInputChannelButton = other.ColorSelectedInputChannelButton;
|
||||
|
||||
// Keep live temp tabs alive across UpdateFrom — a settings save must
|
||||
// not destroy open tell conversations. Pinned TempTabs are persistent
|
||||
// and come through `other` like regular tabs; unpinned TempTabs are
|
||||
// session-only and held from the local state. For persistent tabs
|
||||
// (incl. pinned), capture live runtime state by Identifier and restore
|
||||
// it onto the freshly cloned tabs — CurrentChannel is critical because
|
||||
// the user may have switched channel in-game between settings-open
|
||||
// and settings-save, and we'd otherwise overwrite that with the
|
||||
// settings-time snapshot.
|
||||
var liveUnpinnedTempTabs = Tabs.Where(TabLifecycleHelpers.IsInUnpinnedPool).ToList();
|
||||
var livePersistentSession = Tabs.Where(t => !TabLifecycleHelpers.IsInUnpinnedPool(t))
|
||||
.ToDictionary(t => t.Identifier, t => (t.Messages, t.LastSendUnread, t.CurrentChannel));
|
||||
|
||||
Tabs = other
|
||||
.Tabs.Where(t => !t.IsTempTab || t.IsPinned)
|
||||
.Select(t =>
|
||||
{
|
||||
var clone = t.Clone();
|
||||
if (livePersistentSession.TryGetValue(clone.Identifier, out var live))
|
||||
{
|
||||
clone.Messages = live.Messages;
|
||||
clone.LastSendUnread = live.LastSendUnread;
|
||||
clone.CurrentChannel = live.CurrentChannel;
|
||||
}
|
||||
return clone;
|
||||
})
|
||||
.ToList();
|
||||
Tabs.AddRange(liveUnpinnedTempTabs);
|
||||
|
||||
ChatTabForward = other.ChatTabForward;
|
||||
ChatTabBackward = other.ChatTabBackward;
|
||||
|
||||
PrivacyFilterEnabled = other.PrivacyFilterEnabled;
|
||||
PrivacyPersistChannels = [.. other.PrivacyPersistChannels];
|
||||
PrivacyPersistUnknownChannels = other.PrivacyPersistUnknownChannels;
|
||||
|
||||
RetentionEnabled = other.RetentionEnabled;
|
||||
RetentionDefaultDays = other.RetentionDefaultDays;
|
||||
RetentionPerChannelDays = other.RetentionPerChannelDays.ToDictionary(
|
||||
p => p.Key,
|
||||
p => p.Value
|
||||
);
|
||||
RetentionLastRunAt = other.RetentionLastRunAt;
|
||||
|
||||
FirstRunCompleted = other.FirstRunCompleted;
|
||||
WizardLastShownVersion = other.WizardLastShownVersion;
|
||||
UseHellionFont = other.UseHellionFont;
|
||||
ShowHonorificTitleInHeader = other.ShowHonorificTitleInHeader;
|
||||
ShowHonorificGlow = other.ShowHonorificGlow;
|
||||
|
||||
// v1.1.0 theme engine fields
|
||||
Theme = other.Theme;
|
||||
WindowOpacity = other.WindowOpacity;
|
||||
WindowOpacityInactive = other.WindowOpacityInactive;
|
||||
ReduceMotion = other.ReduceMotion;
|
||||
UseCompactDensity = other.UseCompactDensity;
|
||||
|
||||
EnableAutoTellTabs = other.EnableAutoTellTabs;
|
||||
AutoTellTabsLimit = other.AutoTellTabsLimit;
|
||||
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
||||
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
||||
SidebarWidth = other.SidebarWidth;
|
||||
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
||||
|
||||
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
||||
PopOutInputEnabled = other.PopOutInputEnabled;
|
||||
SeenPopOutHeaderHint = other.SeenPopOutHeaderHint;
|
||||
AutoTellTabsOpenAsPopout = other.AutoTellTabsOpenAsPopout;
|
||||
|
||||
WorldSuffixMode = other.WorldSuffixMode;
|
||||
NameFormMode = other.NameFormMode;
|
||||
|
||||
MainWindowOpen = other.MainWindowOpen;
|
||||
SettingsWindowOpen = other.SettingsWindowOpen;
|
||||
MaxParallelPopouts = other.MaxParallelPopouts;
|
||||
TellAutoOpenMode = other.TellAutoOpenMode;
|
||||
TellAutoOpenSwitchAlways = other.TellAutoOpenSwitchAlways;
|
||||
SidebarAutoSwitchThresholdPx = other.SidebarAutoSwitchThresholdPx;
|
||||
MainWindowLayoutMode = other.MainWindowLayoutMode;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -540,9 +379,6 @@ public class Tab
|
||||
// Optional FontAwesome glyph name; null falls back to TabIconMapping default.
|
||||
public string? Icon = null;
|
||||
|
||||
[Obsolete("Removed in favor of SelectedChannels")]
|
||||
public Dictionary<ChatType, ChatSource> ChatCodes = new();
|
||||
|
||||
public Dictionary<ChatType, (ChatSource, ChatSource)> SelectedChannels = new();
|
||||
public bool ExtraChatAll;
|
||||
public HashSet<Guid> ExtraChatChannels = [];
|
||||
@@ -559,12 +395,15 @@ public class Tab
|
||||
public bool CanMove = true;
|
||||
public bool CanResize = true;
|
||||
|
||||
public bool IndependentHide;
|
||||
public bool HideDuringCutscenes = true;
|
||||
public bool HideWhenNotLoggedIn = true;
|
||||
public bool HideWhenUiHidden = true;
|
||||
public bool HideInLoadingScreens;
|
||||
public bool HideInBattle;
|
||||
// Six per-tab hide conditions used to live here. Their reader was the
|
||||
// pop-out window, which stopped consulting them in cf4705e; the equivalents
|
||||
// that survive are the window-level fields of the same name further up this
|
||||
// file, and v1.12.0 gave every one of those a control.
|
||||
//
|
||||
// Per-tab was the wrong unit anyway: "hide during cutscenes" is a statement
|
||||
// about the screen, not about one conversation.
|
||||
//
|
||||
// HideWhenInactive stays -- the auto-tell service writes it.
|
||||
public bool HideWhenInactive;
|
||||
|
||||
public bool IsTempTab;
|
||||
@@ -667,15 +506,14 @@ public class Tab
|
||||
|
||||
public Tab Clone()
|
||||
{
|
||||
#pragma warning disable CS0618 // ChatCodes is obsolete but still serialized and must survive a clone
|
||||
return new Tab
|
||||
{
|
||||
Name = Name,
|
||||
// Both were missing: Icon feeds the sidebar glyph, ChatCodes carries
|
||||
// legacy migration data that is still written to the JSON. A clone
|
||||
// round-trip used to drop them silently.
|
||||
// Icon feeds the sidebar glyph and a clone round-trip used to drop
|
||||
// it silently. ChatCodes sat beside it until v1.12.0, carrying data
|
||||
// for a migration that the v16 schema gate had already made
|
||||
// unreachable.
|
||||
Icon = Icon,
|
||||
ChatCodes = new Dictionary<ChatType, ChatSource>(ChatCodes),
|
||||
SelectedChannels = SelectedChannels.ToDictionary(pair => pair.Key, pair => pair.Value),
|
||||
ExtraChatAll = ExtraChatAll,
|
||||
ExtraChatChannels = ExtraChatChannels.ToHashSet(),
|
||||
@@ -693,12 +531,6 @@ public class Tab
|
||||
CurrentChannel = CurrentChannel.Clone(),
|
||||
CanMove = CanMove,
|
||||
CanResize = CanResize,
|
||||
IndependentHide = IndependentHide,
|
||||
HideDuringCutscenes = HideDuringCutscenes,
|
||||
HideWhenNotLoggedIn = HideWhenNotLoggedIn,
|
||||
HideWhenUiHidden = HideWhenUiHidden,
|
||||
HideInLoadingScreens = HideInLoadingScreens,
|
||||
HideInBattle = HideInBattle,
|
||||
HideWhenInactive = HideWhenInactive,
|
||||
IsTempTab = IsTempTab,
|
||||
IsPinned = IsPinned,
|
||||
@@ -708,7 +540,6 @@ public class Tab
|
||||
NotificationSoundId = NotificationSoundId,
|
||||
IsGreeted = IsGreeted,
|
||||
};
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
/// Ordered message list with duplicate ID tracking, sorting and mutex protection.
|
||||
|
||||
+42
-2
@@ -167,6 +167,14 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// via Framework.RunOnTick (v1.4.8 B3 retention sweep) can early-bail
|
||||
// before they touch state that has already been torn down. Volatile
|
||||
// because the tick reads it from a different thread than the writer.
|
||||
// The three hide conditions v1.5.6 evaluated and cf4705e left without a
|
||||
// reader. Advanced once per draw, before any window is drawn.
|
||||
private Util.ChatHideReason _hideReason = Util.ChatHideReason.None;
|
||||
|
||||
// Set by the chat-activation keybind, consumed by the next hide evaluation.
|
||||
// A cutscene the user dismissed stays dismissed until it ends.
|
||||
internal bool ChatActivationRequested;
|
||||
|
||||
private volatile bool _isDisposing;
|
||||
|
||||
// Read by background workers that outlive a teardown -- the export thread
|
||||
@@ -309,7 +317,13 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
);
|
||||
}
|
||||
|
||||
Config.Version = 24;
|
||||
// v25 carries no migration step. The schema gate above only refuses
|
||||
// anything under 16, and Json.NET drops keys it does not recognise on
|
||||
// load, so the fields v1.12.0 deleted simply stop being written on the
|
||||
// next save. The bump is documentation, and it has to be consistent:
|
||||
// the constant and this stamp are two separate places, and changing
|
||||
// only one gives a config that re-stamps itself on every start.
|
||||
Config.Version = 25;
|
||||
|
||||
// Unpinned TempTabs are session-only and dropped on every load. Pinned
|
||||
// TempTabs survive reload — Jin's tester feedback (v1.4.7).
|
||||
@@ -470,7 +484,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
new SelfTests.SettingsWindowOpenStep(this),
|
||||
new SelfTests.OnOpenMainUiRoutesMainWindowStep(this),
|
||||
new SelfTests.TypingIpcStateStep(this),
|
||||
new SelfTests.ConfigMigrationV24Step(this),
|
||||
new SelfTests.ConfigMigrationV25Step(this),
|
||||
new SelfTests.ChannelPopoutBindStep(this),
|
||||
new SelfTests.HoverStateFootprintStep(),
|
||||
new SelfTests.HonorificHeaderRenderStep(this),
|
||||
@@ -1203,6 +1217,32 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden;
|
||||
|
||||
// Stateless, so it needs no machine: there is no gesture that shows
|
||||
// the chat while nobody is logged in.
|
||||
if (Config.HideWhenNotLoggedIn && !ClientState.IsLoggedIn)
|
||||
{
|
||||
TypingIpc.Update();
|
||||
return;
|
||||
}
|
||||
|
||||
_hideReason = Util.ChatHideState.Next(
|
||||
_hideReason,
|
||||
new Util.ChatHideState.Inputs(
|
||||
Config.HideInBattle,
|
||||
InBattle,
|
||||
Config.HideDuringCutscenes,
|
||||
CutsceneActive || GposeActive,
|
||||
ChatActivationRequested
|
||||
)
|
||||
);
|
||||
ChatActivationRequested = false;
|
||||
|
||||
if (Util.ChatHideState.Hides(_hideReason))
|
||||
{
|
||||
TypingIpc.Update();
|
||||
return;
|
||||
}
|
||||
|
||||
// RegularFont is nullable only because the live rebuild path
|
||||
// disposes it before reassigning; both ends of that swap happen on
|
||||
// this same draw thread, so it cannot be null here.
|
||||
|
||||
@@ -147,6 +147,7 @@ internal class HellionStrings
|
||||
internal static string Wizard_Step4_Summary_TellTabs => Get(nameof(Wizard_Step4_Summary_TellTabs));
|
||||
internal static string Wizard_Step4_Summary_Visual => Get(nameof(Wizard_Step4_Summary_Visual));
|
||||
internal static string Wizard_Step4_Summary_Unchanged => Get(nameof(Wizard_Step4_Summary_Unchanged));
|
||||
internal static string Wizard_Step4_Summary_Off => Get(nameof(Wizard_Step4_Summary_Off));
|
||||
internal static string Wizard_Step4_TestHint => Get(nameof(Wizard_Step4_TestHint));
|
||||
internal static string Wizard_Step4_SettingsHint => Get(nameof(Wizard_Step4_SettingsHint));
|
||||
|
||||
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Insereix l'objecte enllaçat <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>desactivat</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Vložit odkázaný předmět <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>vypnuto</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Indsæt linket genstand <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>fra</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1208,4 +1208,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Verlinkten Gegenstand einfügen <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>aus</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Εισαγωγή συνδεδεμένου αντικειμένου <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>ανενεργό</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Insertar objeto enlazado <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>desactivado</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Lisää linkitetty esine <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>pois</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Insérer l'objet lié <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>désactivé</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Hivatkozott tárgy beszúrása <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>kikapcsolva</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Inserisci l'oggetto collegato <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>disattivato</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>リンクしたアイテムを挿入 <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>オフ</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>연결된 아이템 삽입 <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>끔</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Sett inn lenket gjenstand <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>av</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Gekoppeld voorwerp invoegen <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>uit</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Wstaw powiązany przedmiot <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>wyłączone</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Inserir item vinculado <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>desativado</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Inserir item ligado <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>desativado</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1225,4 +1225,7 @@
|
||||
<data name="Settings_NotifyFailedTell_Description" xml:space="preserve">
|
||||
<value>The game reports a failed tell in the log only. This raises a notification instead, so a message to someone offline or on another world does not go unnoticed.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>off</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Inserează obiectul legat <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>dezactivat</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Вставить связанный предмет <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>выключено</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Infoga länkat föremål <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>av</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Bağlantılı eşyayı ekle <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>kapalı</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1213,4 +1213,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Вставити пов'язаний предмет <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>вимкнено</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>插入关联物品 <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>关闭</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1214,4 +1214,7 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>插入關聯物品 <item></value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Off" xml:space="preserve">
|
||||
<value>關閉</value>
|
||||
</data>
|
||||
</root>
|
||||
+5
-5
@@ -8,20 +8,20 @@ namespace HellionChat.SelfTests;
|
||||
// below must carry valid values here. This probe never rewrites config; the
|
||||
// migrations themselves are load-time and verified by the prepared-config smoke
|
||||
// in the plan.
|
||||
internal sealed class ConfigMigrationV24Step : ISelfTestStep
|
||||
internal sealed class ConfigMigrationV25Step : ISelfTestStep
|
||||
{
|
||||
public ConfigMigrationV24Step(Plugin plugin)
|
||||
public ConfigMigrationV25Step(Plugin plugin)
|
||||
{
|
||||
_ = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - Config v24 migration";
|
||||
public string Name => "Hellion Chat - Config v25 migration";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
if (Plugin.Config.Version != 24)
|
||||
if (Plugin.Config.Version != 25)
|
||||
{
|
||||
ImGui.Text($"Config.Version is {Plugin.Config.Version}, expected 24");
|
||||
ImGui.Text($"Config.Version is {Plugin.Config.Version}, expected 25");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
// Deterministic hash-based color and icon tinting for Auto-Tell sidebar tabs.
|
||||
// Same tell partner (name+world) always produces the same color and icon across
|
||||
// sessions. Pure string logic, no Dalamud dependency — testable without game refs.
|
||||
internal static class AutoTellTabTint
|
||||
{
|
||||
// Fallback for invalid input (empty name or world=0). White matches
|
||||
// TextPrimary default so the sidebar stays visually consistent.
|
||||
public const uint Fallback = 0xFFFFFFFFu;
|
||||
|
||||
// 12 saturated mid-bright colors from the built-in theme pool, readable
|
||||
// on dark backgrounds. Collision risk is low at realistic 1-5 active tells.
|
||||
// RGBA format, matching ColourUtil.RgbaToAbgr convention.
|
||||
public static readonly IReadOnlyList<uint> Palette = new uint[]
|
||||
{
|
||||
0x00BED2FFu, // Arctic Cyan
|
||||
0xF97316FFu, // Ember Orange
|
||||
0xB585FFFFu, // Light Cosmic Purple
|
||||
0xE374E8FFu, // Bloom Magenta
|
||||
0x5DD39EFFu, // Mint Green
|
||||
0xF0AD4EFFu, // Warning Yellow
|
||||
0xE85C6AFFu, // Coral
|
||||
0x5CB85CFFu, // Status Green
|
||||
0x6278FFFFu, // Bloom Blue
|
||||
0xC9982EFFu, // Warm Gold
|
||||
0x9CCB7CFFu, // Soft Sage
|
||||
0xE85D04FFu, // Deep Ember
|
||||
};
|
||||
|
||||
public static uint For(string name, uint world)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) || world == 0)
|
||||
return Fallback;
|
||||
|
||||
return Palette[(int)(StableHash($"{name}@{world}") % Palette.Count)];
|
||||
}
|
||||
|
||||
// 7 visually distinct FA glyphs that make sense in a tell context.
|
||||
// Excludes cog/comment/users — those read as system or group tabs.
|
||||
public static readonly IReadOnlyList<string> IconPool = new[]
|
||||
{
|
||||
"envelope",
|
||||
"star",
|
||||
"heart",
|
||||
"bell",
|
||||
"bookmark",
|
||||
"flag",
|
||||
"fire",
|
||||
};
|
||||
|
||||
// "envelope" matches the tell context better than the old hardcoded "clock".
|
||||
public const string IconFallback = "envelope";
|
||||
|
||||
public static string IconFor(string name, uint world)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) || world == 0)
|
||||
return IconFallback;
|
||||
|
||||
// Reversed key ("world@name") gives icon and color independent variation
|
||||
// so the same tell partner doesn't always get the same color+icon pair.
|
||||
// 7 icons x 12 colors = 84 distinct combinations.
|
||||
return IconPool[(int)(StableHash($"{world}@{name}") % IconPool.Count)];
|
||||
}
|
||||
|
||||
// FNV-1a, not string.GetHashCode. The header of this file promises the same
|
||||
// partner produces the same colour "across sessions", and GetHashCode cannot
|
||||
// keep that: .NET salts string hashing per process, so every game start
|
||||
// would reshuffle every tell tab. The tests never caught it because they
|
||||
// only ever compared two calls inside one run.
|
||||
// Returns the full uint. The old int-based version masked off the sign bit
|
||||
// before its modulo; on a uint that mask only throws away a bit of entropy.
|
||||
private static uint StableHash(string key)
|
||||
{
|
||||
const uint offsetBasis = 2166136261u;
|
||||
const uint prime = 16777619u;
|
||||
|
||||
var hash = offsetBasis;
|
||||
foreach (var b in System.Text.Encoding.UTF8.GetBytes(key))
|
||||
{
|
||||
hash ^= b;
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
// Avalanche step, and not optional. FNV-1a alone leaves the low bits
|
||||
// correlated for keys that differ only slightly, and the caller takes
|
||||
// exactly those bits with a modulo -- a probe over 144 near-identical
|
||||
// keys reached only 6 of the 12 colours. With fmix32 it reaches all 12.
|
||||
hash ^= hash >> 16;
|
||||
hash *= 0x7feb352du;
|
||||
hash ^= hash >> 15;
|
||||
hash *= 0x846ca68bu;
|
||||
hash ^= hash >> 16;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,10 @@ internal sealed class MessageList
|
||||
private readonly Action<Message> _drawCompactRow;
|
||||
private readonly Action<Message> _drawCardRow;
|
||||
|
||||
// Reused across frames: at the default MaxLinesToRender of 2500 a fresh
|
||||
// array per frame is 10 KB of garbage, and A2 put the default density on
|
||||
// this path.
|
||||
// Reused across frames: at MessageManager.MessageDisplayLimit a fresh array
|
||||
// per frame is 40 KB of garbage, and A2 put the default density on this
|
||||
// path. The old comment named MaxLinesToRender and its 2500 default, a
|
||||
// config field that had stopped bounding anything.
|
||||
private float[] _heightScratch = [];
|
||||
|
||||
// §6.2: setter-injection breaks the PayloadHandler → MainWindow → MessageList → PayloadHandler 3-cycle.
|
||||
|
||||
@@ -121,7 +121,13 @@ internal sealed class WindowTab
|
||||
);
|
||||
}
|
||||
|
||||
if (_w.Section(ImGui.GetID("window.preview"u8), "Input preview", open: false))
|
||||
if (
|
||||
_w.Section(
|
||||
ImGui.GetID("window.preview"u8),
|
||||
HellionStrings.Settings_Section_InputPreview,
|
||||
open: false
|
||||
)
|
||||
)
|
||||
{
|
||||
_w.EnumComboRow(
|
||||
ImGui.GetID("window.preview.position"u8),
|
||||
@@ -186,6 +192,31 @@ internal sealed class WindowTab
|
||||
() => Plugin.Config.HideInNewGamePlusMenu,
|
||||
v => Plugin.Config.HideInNewGamePlusMenu = v
|
||||
);
|
||||
|
||||
// The three that lost their reader with the chat window. Pressing
|
||||
// the chat key during a cutscene brings the chat back for that
|
||||
// cutscene; combat has no such escape, exactly as in v1.5.6.
|
||||
_w.ToggleRow(
|
||||
ImGui.GetID("window.hide.cutscenes"u8),
|
||||
Language.Options_HideDuringCutscenes_Name,
|
||||
Language.Options_HideDuringCutscenes_Description,
|
||||
() => Plugin.Config.HideDuringCutscenes,
|
||||
v => Plugin.Config.HideDuringCutscenes = v
|
||||
);
|
||||
_w.ToggleRow(
|
||||
ImGui.GetID("window.hide.battle"u8),
|
||||
Language.Options_HideInBattle_Name,
|
||||
Language.Options_HideInBattle_Description,
|
||||
() => Plugin.Config.HideInBattle,
|
||||
v => Plugin.Config.HideInBattle = v
|
||||
);
|
||||
_w.ToggleRow(
|
||||
ImGui.GetID("window.hide.notloggedin"u8),
|
||||
Language.Options_HideWhenNotLoggedIn_Name,
|
||||
Language.Options_HideWhenNotLoggedIn_Description,
|
||||
() => Plugin.Config.HideWhenNotLoggedIn,
|
||||
v => Plugin.Config.HideWhenNotLoggedIn = v
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -324,10 +324,17 @@ internal sealed class Sidebar
|
||||
|
||||
var icon = ResolveTabIcon(tab);
|
||||
|
||||
// Dim precedence (1.5.6): the active tab always keeps its regular
|
||||
// color; only greeted, non-active tabs drop to TextDim.
|
||||
// Dim precedence (1.5.6): the active tab never dims; only greeted,
|
||||
// non-active tabs drop to TextDim. "Regular colour" is the tint for an
|
||||
// auto-tell tab and the theme text colour for everything else.
|
||||
//
|
||||
// Below that, an auto-tell tab is tinted from its partner. Twelve
|
||||
// colours against seven glyphs is 84 combinations, which is plenty for
|
||||
// the one to five conversations anybody actually runs in parallel. The
|
||||
// greeted dim still wins: it says something about this tab right now,
|
||||
// the tint only says who it belongs to.
|
||||
var isCurrentTab = tab == activeTab;
|
||||
var iconColor = textAbgr;
|
||||
var iconColor = tab.IsTempTab ? ColourUtil.RgbaToAbgr(TabTintCache.GetTint(tab)) : textAbgr;
|
||||
if (
|
||||
!isCurrentTab
|
||||
&& greetedConfigured
|
||||
@@ -486,10 +493,20 @@ internal sealed class Sidebar
|
||||
)
|
||||
return mapped;
|
||||
|
||||
// Auto-tell tabs always show the envelope, regardless of what their
|
||||
// SelectedChannels filter is set to.
|
||||
// Auto-tell tabs get one of seven glyphs derived from the partner, not
|
||||
// one envelope for all of them. With four tells open, identical rows in
|
||||
// identical colour are four rows you have to read to tell apart.
|
||||
if (tab.IsTempTab)
|
||||
return FontAwesomeIcon.Envelope;
|
||||
{
|
||||
// TryGetValue rather than an indexer: every glyph in the pool is in
|
||||
// the table today, and a lookup miss would be somebody editing one
|
||||
// of the two lists without the other. A wrong envelope beats a
|
||||
// KeyNotFoundException on the draw thread.
|
||||
var hashed = TabTintCache.GetIcon(tab);
|
||||
return IconByName.TryGetValue(hashed, out var tellGlyph)
|
||||
? tellGlyph
|
||||
: FontAwesomeIcon.Envelope;
|
||||
}
|
||||
|
||||
// Channel-type fallback. Walk every selected key, not just the first,
|
||||
// so a System tab that filters multiple system-flavoured ChatTypes
|
||||
|
||||
@@ -483,10 +483,16 @@ public sealed class FirstRunWizard : Window
|
||||
string.Format(HellionStrings.Wizard_Step4_Summary_Profile, profileLabel)
|
||||
);
|
||||
|
||||
var historyLabel =
|
||||
(_state.PendingFilterIncludePreviousSessions ?? false)
|
||||
? HellionStrings.Wizard_Step3_FilterIncludePreviousSessions_Label
|
||||
: HellionStrings.Wizard_Step4_Summary_Unchanged;
|
||||
// Three states, not two. "Unchanged" is for a step the user
|
||||
// never entered; an unticked box is a decision that gets
|
||||
// committed, and reporting it as unchanged was the same kind of
|
||||
// lie the deleted LoadPreviousSession told.
|
||||
var historyLabel = _state.PendingFilterIncludePreviousSessions switch
|
||||
{
|
||||
true => HellionStrings.Wizard_Step3_FilterIncludePreviousSessions_Label,
|
||||
false => HellionStrings.Wizard_Step4_Summary_Off,
|
||||
null => HellionStrings.Wizard_Step4_Summary_Unchanged,
|
||||
};
|
||||
ImGui.TextWrapped(
|
||||
string.Format(HellionStrings.Wizard_Step4_Summary_History, historyLabel)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
// Per-Tab cache wrapper around the pure AutoTellTabTint hash helpers.
|
||||
// Each cache (tint, icon) carries its own name+world validation key so
|
||||
// neither read path mutates the other's state — refilling one never
|
||||
// invalidates the other. No string allocation in the steady-state lookup.
|
||||
internal static class TabTintCache
|
||||
{
|
||||
public static uint GetTint(Tab tab)
|
||||
{
|
||||
var name = tab.TellTarget.Name;
|
||||
var world = tab.TellTarget.World;
|
||||
if (tab._cachedTintTellName != name || tab._cachedTintTellWorld != world)
|
||||
{
|
||||
tab._cachedTintTellName = name;
|
||||
tab._cachedTintTellWorld = world;
|
||||
tab._cachedTellTint = AutoTellTabTint.For(name, world);
|
||||
}
|
||||
return tab._cachedTellTint;
|
||||
}
|
||||
|
||||
public static string GetIcon(Tab tab)
|
||||
{
|
||||
var name = tab.TellTarget.Name;
|
||||
var world = tab.TellTarget.World;
|
||||
if (
|
||||
tab._cachedTellIcon is null
|
||||
|| tab._cachedIconTellName != name
|
||||
|| tab._cachedIconTellWorld != world
|
||||
)
|
||||
{
|
||||
tab._cachedIconTellName = name;
|
||||
tab._cachedIconTellWorld = world;
|
||||
tab._cachedTellIcon = AutoTellTabTint.IconFor(name, world);
|
||||
}
|
||||
return tab._cachedTellIcon;
|
||||
}
|
||||
}
|
||||
@@ -226,6 +226,10 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
|
||||
internal void ActivateChat()
|
||||
{
|
||||
_userHidden = false;
|
||||
|
||||
// Also lifts a cutscene hide for the duration of that cutscene. Without
|
||||
// this the key would appear to do nothing at all during one.
|
||||
Plugin.Instance.ChatActivationRequested = true;
|
||||
if (!IsOpen)
|
||||
{
|
||||
IsOpen = true;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
namespace HellionChat.Util;
|
||||
|
||||
// Why the chat is currently hidden, if it is.
|
||||
internal enum ChatHideReason
|
||||
{
|
||||
None,
|
||||
|
||||
// In combat, and the user asked for that to hide the chat.
|
||||
Battle,
|
||||
|
||||
// A cutscene or gpose is running.
|
||||
Cutscene,
|
||||
|
||||
// A cutscene is running and the user pressed the activation key anyway.
|
||||
// Distinct from None so the state returns to hidden on its own once the
|
||||
// cutscene ends, without a second gesture.
|
||||
CutsceneOverride,
|
||||
}
|
||||
|
||||
// The hide conditions v1.5.6 evaluated every frame, as a state machine.
|
||||
//
|
||||
// Three of the four went missing with the chat window in cf4705e: their config
|
||||
// fields survived, their translated labels survived, nothing read them. They
|
||||
// need a machine rather than three ifs because two of them are not conditions
|
||||
// but states: a cutscene the user has dismissed must stay dismissed until the
|
||||
// cutscene ends, and combat that started while the chat was already hidden for
|
||||
// another reason must not take ownership of it.
|
||||
//
|
||||
// Pure and Dalamud-free, so the transitions can be pinned without a game.
|
||||
internal static class ChatHideState
|
||||
{
|
||||
internal readonly record struct Inputs(
|
||||
bool HideInBattle,
|
||||
bool InBattle,
|
||||
bool HideDuringCutscenes,
|
||||
bool CutsceneActive,
|
||||
bool ActivateRequested
|
||||
);
|
||||
|
||||
internal static ChatHideReason Next(ChatHideReason current, in Inputs inputs)
|
||||
{
|
||||
// Leaving a state comes first. Otherwise a frame in which combat ends
|
||||
// and a cutscene starts would keep reporting Battle.
|
||||
if (current == ChatHideReason.Battle && !inputs.InBattle)
|
||||
current = ChatHideReason.None;
|
||||
|
||||
if (
|
||||
current is ChatHideReason.Cutscene or ChatHideReason.CutsceneOverride
|
||||
&& !inputs.CutsceneActive
|
||||
)
|
||||
current = ChatHideReason.None;
|
||||
|
||||
// The user asked for the chat during a cutscene. Held until the cutscene
|
||||
// ends, which the branch above takes care of.
|
||||
if (current == ChatHideReason.Cutscene && inputs.ActivateRequested)
|
||||
return ChatHideReason.CutsceneOverride;
|
||||
|
||||
if (current != ChatHideReason.None)
|
||||
return current;
|
||||
|
||||
// Entering. Cutscene wins over battle: a cutscene during combat is the
|
||||
// more specific situation, and it is the one with an escape hatch.
|
||||
if (inputs.HideDuringCutscenes && inputs.CutsceneActive)
|
||||
return ChatHideReason.Cutscene;
|
||||
|
||||
if (inputs.HideInBattle && inputs.InBattle)
|
||||
return ChatHideReason.Battle;
|
||||
|
||||
return ChatHideReason.None;
|
||||
}
|
||||
|
||||
// CutsceneOverride is a hide state that does not hide -- that is the whole
|
||||
// point of it.
|
||||
internal static bool Hides(ChatHideReason reason) =>
|
||||
reason is ChatHideReason.Battle or ChatHideReason.Cutscene;
|
||||
}
|
||||
@@ -21,8 +21,8 @@ internal readonly record struct LayoutFingerprint(
|
||||
// Dragging a window edge or the Dalamud UI-scale slider moves the continuous
|
||||
// half of the fingerprint on every frame. Acting on each one drops the height
|
||||
// cache and sends the whole tab through the linear measure path (up to
|
||||
// Config.MaxLinesToRender rows). Waiting for those to settle turns a drag into
|
||||
// one rebuild. Discrete changes bypass the wait entirely.
|
||||
// MessageManager.MessageDisplayLimit rows). Waiting for those to settle turns a
|
||||
// drag into one rebuild. Discrete changes bypass the wait entirely.
|
||||
internal sealed class LayoutFingerprintGate
|
||||
{
|
||||
internal const long SettleMs = 200;
|
||||
|
||||
@@ -22,7 +22,7 @@ internal static class TabLifecycleHelpers
|
||||
// with no window. Unconditional (pinned included) because pinned TempTabs
|
||||
// survive the load and are the main stale-flag source; a !IsPinned filter
|
||||
// would leave exactly those leaking. Lockstep with the two in-memory resets
|
||||
// (AutoTellTabsService pool-full + Configuration.UpdateFrom backToOriginal).
|
||||
// (AutoTellTabsService pool-full + the settings round trip backToOriginal).
|
||||
// TEST-MIRROR: ../../../Hellion Build test/_Helpers/PopOutResetOnLoadTests.cs
|
||||
internal static void ResetPopOutOnLoad(IEnumerable<Tab> tabs)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user