From 1125caabca0d7925ed02a474c8e48a40c3b3b7f5 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:00:09 +0200 Subject: [PATCH 01/20] feat(integrations): add HonorificTitleData DTO and ParseTitleJson --- HellionChat/Integrations/HonorificService.cs | 31 +++++++++++++++++++ .../Integrations/HonorificTitleData.cs | 17 ++++++++++ 2 files changed, 48 insertions(+) create mode 100644 HellionChat/Integrations/HonorificService.cs create mode 100644 HellionChat/Integrations/HonorificTitleData.cs diff --git a/HellionChat/Integrations/HonorificService.cs b/HellionChat/Integrations/HonorificService.cs new file mode 100644 index 0000000..1e3f755 --- /dev/null +++ b/HellionChat/Integrations/HonorificService.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; + +namespace HellionChat.Integrations; + +internal sealed class HonorificService +{ + // We pull Newtonsoft.Json into this single file for IPC compatibility: + // Honorific serialises with Newtonsoft (see Honorific-master/IpcProvider.cs:9 + // and CustomTitle.cs:12). Using the same library guarantees identical + // handling of System.Numerics.Vector3? and the enum fields we ignore. + // Newtonsoft is a transitive dependency via Dalamud, so no new NuGet + // entry is needed. The rest of HellionChat keeps using System.Text.Json. + + // Returns null when the JSON is empty (Honorific signals "no custom title" + // with string.Empty — see IpcProvider.cs:100), or when deserialisation + // throws (defensive: a malformed payload shouldn't crash the chat header). + internal static HonorificTitleData? ParseTitleJson(string json) + { + if (string.IsNullOrEmpty(json)) + return null; + + try + { + return JsonConvert.DeserializeObject(json); + } + catch (JsonException) + { + return null; + } + } +} diff --git a/HellionChat/Integrations/HonorificTitleData.cs b/HellionChat/Integrations/HonorificTitleData.cs new file mode 100644 index 0000000..3f67e32 --- /dev/null +++ b/HellionChat/Integrations/HonorificTitleData.cs @@ -0,0 +1,17 @@ +using System.Numerics; + +namespace HellionChat.Integrations; + +// Local DTO mirroring Honorific's TitleData shape. We replicate the structure +// instead of referencing Honorific.dll because a hard build-time dependency +// would couple the two assemblies and break HellionChat at load time when +// Honorific is missing. Glow, Color3, GradientColourSet and GradientAnimationStyle +// are intentionally omitted — Cycle 1 renders text in the primary Color only; +// the "Honorific Full Fidelity" backlog item adds them later as a pure +// extension that won't break this DTO's existing consumers. +internal sealed record HonorificTitleData( + string? Title, + bool IsPrefix, + bool IsOriginal, + Vector3? Color +); From fa91c4e847814a54b0faf1103cd01365b0ba1640 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:06:34 +0200 Subject: [PATCH 02/20] feat(branding): add BrandingLinks with Hellion Forge Discord invite --- HellionChat/Branding/BrandingLinks.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 HellionChat/Branding/BrandingLinks.cs diff --git a/HellionChat/Branding/BrandingLinks.cs b/HellionChat/Branding/BrandingLinks.cs new file mode 100644 index 0000000..0298469 --- /dev/null +++ b/HellionChat/Branding/BrandingLinks.cs @@ -0,0 +1,12 @@ +// HellionChat/Branding/BrandingLinks.cs +namespace HellionChat.Branding; + +// Centralised so a future invite rotation only touches one file. The same +// link is currently hard-coded in repo.json, README.md, SUPPORT.md, +// CONTRIBUTORS.md and HellionChat.yaml — those will be migrated to consume +// this constant in a separate housekeeping sweep, but that's out of scope +// for this Cycle. +internal static class BrandingLinks +{ + public const string HellionForgeDiscordInvite = "https://discord.gg/X9V7Kcv5gR"; +} From 74e2c655f04a07300162b748d19ebf024c670044 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:09:36 +0200 Subject: [PATCH 03/20] feat(integrations): add IsApiVersionCompatible and ShouldRenderSlot helpers --- HellionChat/Integrations/HonorificService.cs | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/HellionChat/Integrations/HonorificService.cs b/HellionChat/Integrations/HonorificService.cs index 1e3f755..eb93122 100644 --- a/HellionChat/Integrations/HonorificService.cs +++ b/HellionChat/Integrations/HonorificService.cs @@ -28,4 +28,35 @@ internal sealed class HonorificService return null; } } + + // Honorific has been on major version 3 since the IPC was introduced. + // We treat anything else as incompatible because a major bump from + // upstream signals a breaking IPC contract change, and rendering a + // title against the wrong shape is worse than rendering nothing. + // If Honorific later ships a non-breaking 4.x major, we relax this + // by extending the accepted-major set rather than removing the check. + internal static bool IsApiVersionCompatible((uint Major, uint Minor) apiVersion) + { + return apiVersion.Major == 3; + } + + // Single source of truth for whether the chat header should draw the + // Honorific slot in the current frame. Returning a single bool keeps + // the render call branch-free; all skip conditions are evaluated here. + // The IsOriginal short-circuit means: when the user has Honorific + // installed but is using the original FFXIV title, we render nothing — + // matches the design decision in the spec ("Empty-State A: silent + // auto-hide"). + internal static bool ShouldRenderSlot( + bool toggleEnabled, + bool isAvailable, + HonorificTitleData? title) + { + if (!toggleEnabled) return false; + if (!isAvailable) return false; + if (title is null) return false; + if (title.IsOriginal) return false; + if (string.IsNullOrEmpty(title.Title)) return false; + return true; + } } From 00deef01a4a330b93aadd5a23dcbcda5673e44f3 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:13:10 +0200 Subject: [PATCH 04/20] feat(integrations): wire HonorificService to Honorific IPC gates --- HellionChat/Integrations/HonorificService.cs | 177 ++++++++++++++++--- 1 file changed, 154 insertions(+), 23 deletions(-) diff --git a/HellionChat/Integrations/HonorificService.cs b/HellionChat/Integrations/HonorificService.cs index eb93122..c4770b3 100644 --- a/HellionChat/Integrations/HonorificService.cs +++ b/HellionChat/Integrations/HonorificService.cs @@ -1,19 +1,163 @@ +using System; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using Dalamud.Plugin.Services; using Newtonsoft.Json; namespace HellionChat.Integrations; -internal sealed class HonorificService +// We pull Newtonsoft.Json into this single file for IPC compatibility: +// Honorific serialises its TitleData with Newtonsoft (see +// Honorific-master/IpcProvider.cs:9 and CustomTitle.cs:12). Using the +// same library guarantees identical handling of System.Numerics.Vector3? +// and the enum fields we ignore. Newtonsoft is a transitive dependency +// via Dalamud, so no new NuGet entry is needed. The rest of HellionChat +// keeps using System.Text.Json. +internal sealed class HonorificService : IDisposable { - // We pull Newtonsoft.Json into this single file for IPC compatibility: - // Honorific serialises with Newtonsoft (see Honorific-master/IpcProvider.cs:9 - // and CustomTitle.cs:12). Using the same library guarantees identical - // handling of System.Numerics.Vector3? and the enum fields we ignore. - // Newtonsoft is a transitive dependency via Dalamud, so no new NuGet - // entry is needed. The rest of HellionChat keeps using System.Text.Json. + private const string IpcNamespace = "Honorific"; + + // IPC gates we subscribe to. Keep them as fields so Dispose can + // unsubscribe the same instances we subscribed in the constructor. + private readonly ICallGateSubscriber<(uint, uint)> _apiVersion; + private readonly ICallGateSubscriber _getLocalCharacterTitle; + private readonly ICallGateSubscriber _localCharacterTitleChanged; + private readonly ICallGateSubscriber _ready; + private readonly ICallGateSubscriber _disposing; + + private readonly IPluginLog _log; + private bool _versionWarningLogged; + + public HonorificTitleData? CurrentTitle { get; private set; } + public bool IsAvailable { get; private set; } + public (uint Major, uint Minor)? DetectedApiVersion { get; private set; } + + public HonorificService(IDalamudPluginInterface pluginInterface, IPluginLog log) + { + _log = log; + + // Dalamud caches gate objects per-name for the lifetime of the + // plugin interface, so we can register subscribers even when + // Honorific isn't loaded yet — the gate just won't fire. Calling + // InvokeFunc before Honorific is up will throw, which is why the + // initial pull below is wrapped in try-catch. + _apiVersion = pluginInterface + .GetIpcSubscriber<(uint, uint)>($"{IpcNamespace}.ApiVersion"); + _getLocalCharacterTitle = pluginInterface + .GetIpcSubscriber($"{IpcNamespace}.GetLocalCharacterTitle"); + _localCharacterTitleChanged = pluginInterface + .GetIpcSubscriber($"{IpcNamespace}.LocalCharacterTitleChanged"); + _ready = pluginInterface + .GetIpcSubscriber($"{IpcNamespace}.Ready"); + _disposing = pluginInterface + .GetIpcSubscriber($"{IpcNamespace}.Disposing"); + + _localCharacterTitleChanged.Subscribe(OnTitleChanged); + _ready.Subscribe(OnReady); + _disposing.Subscribe(OnDisposing); + + TryInitialPull(); + } + + public void Dispose() + { + // Honorific may already be gone by the time we dispose. Wrap each + // unsubscribe so a missing gate doesn't prevent the others from + // unsubscribing — leaking even one subscription leaves a callback + // alive that captures `this`, which keeps the whole service alive + // and breaks plugin reload. + TryUnsubscribe(() => _localCharacterTitleChanged.Unsubscribe(OnTitleChanged)); + TryUnsubscribe(() => _ready.Unsubscribe(OnReady)); + TryUnsubscribe(() => _disposing.Unsubscribe(OnDisposing)); + } + + private void TryInitialPull() + { + try + { + var version = _apiVersion.InvokeFunc(); + DetectedApiVersion = version; + + if (!IsApiVersionCompatible(version)) + { + if (!_versionWarningLogged) + { + _log.Warning( + "Honorific API version mismatch — expected major 3, " + + "found {Major}.{Minor}. Disabling Honorific integration.", + version.Item1, version.Item2); + _versionWarningLogged = true; + } + IsAvailable = false; + return; + } + + IsAvailable = true; + // Pull the current title once at startup; from here on we rely + // on LocalCharacterTitleChanged events. + var json = _getLocalCharacterTitle.InvokeFunc(); + CurrentTitle = ParseTitleJson(json); + } + catch (Exception ex) + { + // Honorific isn't installed or hasn't initialised yet. The Ready + // event will give us a second chance later. Log at Debug so + // users without Honorific don't see noise on every reload. + _log.Debug(ex, "Honorific not available at HellionChat startup; awaiting Ready."); + IsAvailable = false; + CurrentTitle = null; + } + } + + // Honorific fires LocalCharacterTitleChanged through its nameplate hook + // (Honorific-master/Plugin.cs:665), which means we get title updates on + // character switches automatically as soon as the new character is + // rendered. While the user is in the character-select menu, HellionChat's + // window is hidden by default via HideWhenNotLoggedIn (Configuration.cs:152), + // so the stale-title window between logout and login isn't user-visible. + private void OnTitleChanged(string json) + { + CurrentTitle = ParseTitleJson(json); + } + + private void OnReady() + { + // Honorific loaded after HellionChat; redo the version check and + // initial pull. Idempotent on purpose — Honorific can fire Ready + // more than once across reloads. + TryInitialPull(); + } + + private void OnDisposing() + { + // Honorific is unloading. Drop our cached state so the header + // hides on the next frame; subscriptions stay registered because + // the gates may come back later (Honorific reload). + CurrentTitle = null; + IsAvailable = false; + DetectedApiVersion = null; + } + + private void TryUnsubscribe(Action unsubscribe) + { + try + { + unsubscribe(); + } + catch (Exception ex) + { + _log.Debug(ex, "Honorific unsubscribe failed (likely already gone)."); + } + } + + // Threading note: Dalamud fires IPC events on the framework thread and + // ImGui renders on the framework thread, so OnTitleChanged and the + // render path that reads CurrentTitle never race. If a future change + // moves either side onto a worker thread, switch to volatile/Interlocked + // for the CurrentTitle field. + + // --- Pure-logic helpers below; tested via HellionChat.Tests/Integrations. --- - // Returns null when the JSON is empty (Honorific signals "no custom title" - // with string.Empty — see IpcProvider.cs:100), or when deserialisation - // throws (defensive: a malformed payload shouldn't crash the chat header). internal static HonorificTitleData? ParseTitleJson(string json) { if (string.IsNullOrEmpty(json)) @@ -29,24 +173,11 @@ internal sealed class HonorificService } } - // Honorific has been on major version 3 since the IPC was introduced. - // We treat anything else as incompatible because a major bump from - // upstream signals a breaking IPC contract change, and rendering a - // title against the wrong shape is worse than rendering nothing. - // If Honorific later ships a non-breaking 4.x major, we relax this - // by extending the accepted-major set rather than removing the check. internal static bool IsApiVersionCompatible((uint Major, uint Minor) apiVersion) { return apiVersion.Major == 3; } - // Single source of truth for whether the chat header should draw the - // Honorific slot in the current frame. Returning a single bool keeps - // the render call branch-free; all skip conditions are evaluated here. - // The IsOriginal short-circuit means: when the user has Honorific - // installed but is using the original FFXIV title, we render nothing — - // matches the design decision in the spec ("Empty-State A: silent - // auto-hide"). internal static bool ShouldRenderSlot( bool toggleEnabled, bool isAvailable, From 206b25b8d69fb6e6a1983232e3933fc56dd2750d Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:18:20 +0200 Subject: [PATCH 05/20] fix(integrations): address review findings on HonorificService --- HellionChat/Integrations/HonorificService.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/HellionChat/Integrations/HonorificService.cs b/HellionChat/Integrations/HonorificService.cs index c4770b3..2361697 100644 --- a/HellionChat/Integrations/HonorificService.cs +++ b/HellionChat/Integrations/HonorificService.cs @@ -93,6 +93,7 @@ internal sealed class HonorificService : IDisposable } IsAvailable = true; + _versionWarningLogged = false; // Pull the current title once at startup; from here on we rely // on LocalCharacterTitleChanged events. var json = _getLocalCharacterTitle.InvokeFunc(); @@ -117,6 +118,11 @@ internal sealed class HonorificService : IDisposable // so the stale-title window between logout and login isn't user-visible. private void OnTitleChanged(string json) { + // Don't update cached state when we've already decided we can't trust + // Honorific (e.g. version mismatch). Subscription stays live in case a + // compatible Honorific reloads, in which case Ready triggers TryInitialPull + // and sets IsAvailable back to true. + if (!IsAvailable) return; CurrentTitle = ParseTitleJson(json); } @@ -133,6 +139,12 @@ internal sealed class HonorificService : IDisposable // Honorific is unloading. Drop our cached state so the header // hides on the next frame; subscriptions stay registered because // the gates may come back later (Honorific reload). + // + // Race-note: Honorific's NotifyDisposing calls ChangedLocalCharacterTitle(null) + // BEFORE SendMessage on the Disposing gate (IpcProvider.cs:109-111), + // so OnTitleChanged is expected to fire first and already null out + // CurrentTitle. We re-clear here as belt-and-braces; should the + // ordering ever flip, ShouldRenderSlot would still gate on IsAvailable. CurrentTitle = null; IsAvailable = false; DetectedApiVersion = null; @@ -155,6 +167,11 @@ internal sealed class HonorificService : IDisposable // render path that reads CurrentTitle never race. If a future change // moves either side onto a worker thread, switch to volatile/Interlocked // for the CurrentTitle field. + // + // Divergence from ChatTwo/Ipc/ExtraChat.cs: that file uses `volatile` + // on its state fields out of caution. We don't, because the framework- + // thread delivery is the documented Dalamud contract. If the two files + // ever need to share a threading audit, this is the place to revisit. // --- Pure-logic helpers below; tested via HellionChat.Tests/Integrations. --- From af3caa9b96cc3f7595e3d12570901757563b699f Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:20:17 +0200 Subject: [PATCH 06/20] feat(config): add ShowHonorificTitleInHeader toggle (default on) --- HellionChat/Configuration.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs index 8b2120d..5ae47b6 100755 --- a/HellionChat/Configuration.cs +++ b/HellionChat/Configuration.cs @@ -92,6 +92,13 @@ public class Configuration : IPluginConfiguration // to fall back to the user's chosen system or Dalamud font. public bool UseHellionFont = true; + // Cycle 1 of the plugin-integration roadmap. When Honorific is installed + // and reports a custom title, render it in the chat header above the + // message log. Auto-hides regardless when Honorific is missing or the + // active title is original/empty, so leaving this on is safe even for + // users who don't run Honorific. + public bool ShowHonorificTitleInHeader = true; + // Hellion Chat — Auto-Tell-Tabs. When enabled, an incoming or outgoing // /tell spawns a session-only tab dedicated to that conversation // partner. See spec: Hellion Chat Auto-Tell-Tabs Spec (Obsidian). @@ -372,6 +379,7 @@ public class Configuration : IPluginConfiguration FirstRunCompleted = other.FirstRunCompleted; UseHellionFont = other.UseHellionFont; + ShowHonorificTitleInHeader = other.ShowHonorificTitleInHeader; // v1.1.0 theme engine fields Theme = other.Theme; From 5b5f52f86e8923e580f2ad267103686b30a3cc89 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:22:23 +0200 Subject: [PATCH 07/20] feat(integrations): wire HonorificService into Plugin lifecycle --- HellionChat/Plugin.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 64ea16f..f1635d1 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -65,6 +65,7 @@ public sealed class Plugin : IDalamudPlugin internal FontManager FontManager { get; } internal Themes.ThemeRegistry ThemeRegistry { get; private set; } = null!; internal Ui.StatusBar StatusBar { get; private set; } = null!; + internal Integrations.HonorificService HonorificService { get; private set; } = null!; internal int DeferredSaveFrames = -1; @@ -438,6 +439,12 @@ public sealed class Plugin : IDalamudPlugin ThemeRegistry = new Themes.ThemeRegistry(customThemesDir); ThemeRegistry.Switch(Config.Theme); + // Plugin integrations register their IPC subscribers up-front so + // Ready/Disposing events from the target plugins are caught from + // the very first frame, even if the user's Honorific reloads + // mid-session. See HellionChat/Integrations/HonorificService.cs. + HonorificService = new Integrations.HonorificService(Interface, Log); + StatusBar = new Ui.StatusBar(); MessageManager = new MessageManager(this); // Does it require UI? @@ -529,6 +536,8 @@ public sealed class Plugin : IDalamudPlugin Framework.Update -= FrameworkUpdate; GameFunctions.GameFunctions.SetChatInteractable(true); + HonorificService?.Dispose(); + WindowSystem?.RemoveAllWindows(); ChatLogWindow?.Dispose(); DbViewer?.Dispose(); From 8da05c308027426b9bc69432bfe61cd6c29e4043 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:27:04 +0200 Subject: [PATCH 08/20] feat(i18n): add localisation keys for Integrations settings tab --- .../Resources/HellionStrings.Designer.cs | 25 +++++++ HellionChat/Resources/HellionStrings.de.resx | 69 +++++++++++++++++++ HellionChat/Resources/HellionStrings.resx | 69 +++++++++++++++++++ 3 files changed, 163 insertions(+) diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index 803f4cb..66b7489 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -356,4 +356,29 @@ internal class HellionStrings // Hellion Chat — v1.2.1 Migration v15 → v16 toast internal static string Migration_v16_OverrideStyle_Toast => Get(nameof(Migration_v16_OverrideStyle_Toast)); + + // Hellion Chat — v1.3.0 Integrations tab (Honorific + Coming-Soon roadmap) + internal static string Settings_Tab_Integrations => Get(nameof(Settings_Tab_Integrations)); + internal static string Settings_Integrations_Intro => Get(nameof(Settings_Integrations_Intro)); + internal static string Settings_Integrations_Honorific_SectionHeader => Get(nameof(Settings_Integrations_Honorific_SectionHeader)); + internal static string Settings_Integrations_Honorific_Status_Detected => Get(nameof(Settings_Integrations_Honorific_Status_Detected)); + internal static string Settings_Integrations_Honorific_Status_NotInstalled => Get(nameof(Settings_Integrations_Honorific_Status_NotInstalled)); + internal static string Settings_Integrations_Honorific_Status_Incompatible => Get(nameof(Settings_Integrations_Honorific_Status_Incompatible)); + internal static string Settings_Integrations_Honorific_Toggle => Get(nameof(Settings_Integrations_Honorific_Toggle)); + internal static string Settings_Integrations_Honorific_ToggleHint => Get(nameof(Settings_Integrations_Honorific_ToggleHint)); + internal static string Settings_Integrations_ComingSoon_SectionHeader => Get(nameof(Settings_Integrations_ComingSoon_SectionHeader)); + internal static string Settings_Integrations_ComingSoon_Intro => Get(nameof(Settings_Integrations_ComingSoon_Intro)); + internal static string Settings_Integrations_ComingSoon_ContextMenu_Title => Get(nameof(Settings_Integrations_ComingSoon_ContextMenu_Title)); + internal static string Settings_Integrations_ComingSoon_ContextMenu_Description => Get(nameof(Settings_Integrations_ComingSoon_ContextMenu_Description)); + internal static string Settings_Integrations_ComingSoon_Notifications_Title => Get(nameof(Settings_Integrations_ComingSoon_Notifications_Title)); + internal static string Settings_Integrations_ComingSoon_Notifications_Description => Get(nameof(Settings_Integrations_ComingSoon_Notifications_Description)); + internal static string Settings_Integrations_ComingSoon_RPStatus_Title => Get(nameof(Settings_Integrations_ComingSoon_RPStatus_Title)); + internal static string Settings_Integrations_ComingSoon_RPStatus_Description => Get(nameof(Settings_Integrations_ComingSoon_RPStatus_Description)); + internal static string Settings_Integrations_ComingSoon_ExtraChat_Title => Get(nameof(Settings_Integrations_ComingSoon_ExtraChat_Title)); + internal static string Settings_Integrations_ComingSoon_ExtraChat_Description => Get(nameof(Settings_Integrations_ComingSoon_ExtraChat_Description)); + internal static string Settings_Integrations_ComingSoon_QuickDM_Title => Get(nameof(Settings_Integrations_ComingSoon_QuickDM_Title)); + internal static string Settings_Integrations_ComingSoon_QuickDM_Description => Get(nameof(Settings_Integrations_ComingSoon_QuickDM_Description)); + internal static string Settings_Integrations_GotAnIdea_SectionHeader => Get(nameof(Settings_Integrations_GotAnIdea_SectionHeader)); + internal static string Settings_Integrations_GotAnIdea_Body => Get(nameof(Settings_Integrations_GotAnIdea_Body)); + internal static string Settings_Integrations_GotAnIdea_LinkLabel => Get(nameof(Settings_Integrations_GotAnIdea_LinkLabel)); } diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index f27f9d0..3912854 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -791,4 +791,73 @@ Hellion Chat 1.2.1 hat das Settings-Menü neu sortiert und die alte „Stilüberschreiben"-Option entfernt (überholt durch das Theme-System aus 1.1.0). Deine restlichen Einstellungen bleiben unverändert. Die Fenster-Transparenz ist nach „Theme & Layout" migriert. Ein Backup der vorherigen Config liegt unter pluginConfigs/HellionChat.json.pre-v16-backup neben der aktiven HellionChat.json. + + Integrationen + + + Plugin-Integrationen lassen HellionChat mit anderen installierten Dalamud-Plugins zusammenarbeiten. Jede Integration erkennt ihr Ziel automatisch und deaktiviert sich still, wenn das Ziel-Plugin fehlt. + + + Honorific + + + Erkannt (v{0}.{1}) + + + Nicht installiert + + + Inkompatible API-Version ({0} erwartet, {1}.{2} gefunden) + + + Honorific-Titel im Chat-Header anzeigen + + + Zeigt deinen Custom-Titel aus Honorific im Header über dem Chat-Log an, in der von dir gewählten Farbe. + + + Demnächst + + + Diese Integrationen sind auf der Roadmap. Die Einstellungen erscheinen automatisch, sobald das jeweilige Plugin angebunden ist. + + + Kontextmenü-Aktionen + + + Rechtsklick auf einen Namen im Chat: zu PlayerTrack springen, Lodestone-Profil öffnen oder mit einem Klick eine DM verfassen. + + + Smart Notifications (NotificationMaster) + + + Mentions und DMs über NotificationMaster: System-Toasts, Taskbar-Flash und Sounds pro Channel. + + + RP-Status-Block (Moodles · LightlessClient) + + + Moodles-Status-Icons und Pair-Badges direkt neben den Chat-Namen anzeigen, für mehr Roleplay-Kontext. + + + ExtraChat-Channels + + + End-to-End-verschlüsselte Cross-Datacenter-Linkshells nativ in HellionChat hosten. + + + Quick-DM-Button (XIVInstantMessenger) + + + DM-Schnellzugriff direkt aus dem Chat-Fenster, ein Klick. + + + Idee? + + + Idee für eine Plugin-Integration, die nicht auf der Liste steht? Komm auf den Hellion-Forge-Discord und schreib mir — Community-Input bestimmt die Roadmap. + + + discord.gg/X9V7Kcv5gR + diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index c109223..63d6931 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -791,4 +791,73 @@ Hellion Chat 1.2.1 reorganised the Settings menu and removed the legacy "Style override" option (made obsolete by the Themes system in 1.1.0). Your other settings are unchanged. Window opacity was migrated to Theme & Layout. A backup of your previous config is at pluginConfigs/HellionChat.json.pre-v16-backup next to the live HellionChat.json. + + Integrations + + + Plugin integrations let HellionChat react to other installed Dalamud plugins. Each integration auto-detects its target and silently disables itself when the target plugin is not present. + + + Honorific + + + Detected (v{0}.{1}) + + + Not installed + + + Incompatible API version ({0} expected, {1}.{2} detected) + + + Show Honorific title in chat header + + + Displays your custom title from Honorific in the header above the chat log, in your chosen colour. + + + Coming soon + + + These integrations are on the roadmap. The settings for each appear automatically once the underlying plugin is wired up. + + + Context menu actions + + + Right-click a name in chat to jump to PlayerTrack, open the Lodestone profile, or compose a DM in one click. + + + Smart notifications (NotificationMaster) + + + Route mentions and DMs through NotificationMaster for system toasts, taskbar flash, and per-channel sounds. + + + RP status block (Moodles · LightlessClient) + + + Show Moodles status icons and pair-badges inline next to chat names for richer roleplay context. + + + ExtraChat channels + + + Host end-to-end-encrypted cross-datacenter linkshells natively in HellionChat. + + + Quick DM button (XIVInstantMessenger) + + + One-click DM compose without leaving the chat window. + + + Got an idea? + + + Got an idea for a plugin integration that's not on this list? Hop on the Hellion Forge Discord and tell me — community input drives the roadmap. + + + discord.gg/X9V7Kcv5gR + From 9f0a40bedcc8891c641e4efd1e4d07bab122bc20 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:30:59 +0200 Subject: [PATCH 09/20] feat(ui): add Integrations settings tab --- HellionChat/Ui/SettingsTabs/Integrations.cs | 185 ++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 HellionChat/Ui/SettingsTabs/Integrations.cs diff --git a/HellionChat/Ui/SettingsTabs/Integrations.cs b/HellionChat/Ui/SettingsTabs/Integrations.cs new file mode 100644 index 0000000..5edd241 --- /dev/null +++ b/HellionChat/Ui/SettingsTabs/Integrations.cs @@ -0,0 +1,185 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility.Raii; +using HellionChat.Branding; +using HellionChat.Resources; +using HellionChat.Util; + +namespace HellionChat.Ui.SettingsTabs; + +// First settings tab introduced in v1.3.0 (Plugin Integrations Cycle 1). +// Designed to grow organically: each future cycle adds a new section above +// the "Coming soon" block and removes the corresponding stub item. +internal sealed class Integrations : ISettingsTab +{ + private Plugin Plugin { get; } + private Configuration Mutable { get; } + + public string Name => HellionStrings.Settings_Tab_Integrations + "###tabs-integrations"; + + internal Integrations(Plugin plugin, Configuration mutable) + { + Plugin = plugin; + Mutable = mutable; + } + + public void Draw(bool changed) + { + ImGui.TextWrapped(HellionStrings.Settings_Integrations_Intro); + ImGui.Spacing(); + ImGui.Spacing(); + + DrawHonorificSection(); + ImGui.Spacing(); + ImGui.Spacing(); + + DrawComingSoonSection(); + ImGui.Spacing(); + ImGui.Spacing(); + + DrawGotAnIdeaSection(); + } + + private void DrawHonorificSection() + { + DrawSectionHeader(HellionStrings.Settings_Integrations_Honorific_SectionHeader); + + DrawHonorificStatus(); + ImGui.Spacing(); + + // The toggle is enabled regardless of detection state — leaving it + // on means "render when available, hide otherwise". Disabling the + // toggle when Honorific is missing would force the user to retoggle + // it every time Honorific is reloaded, which is worse UX than the + // silent auto-hide. + if (ImGui.Checkbox( + HellionStrings.Settings_Integrations_Honorific_Toggle, + ref Mutable.ShowHonorificTitleInHeader)) + { + Plugin.SaveConfig(); + } + + using (ImRaii.PushIndent()) + { + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(Plugin.ThemeRegistry.Active.Colors.TextMuted))) + { + ImGui.TextWrapped(HellionStrings.Settings_Integrations_Honorific_ToggleHint); + } + } + } + + private void DrawHonorificStatus() + { + var theme = Plugin.ThemeRegistry.Active; + var service = Plugin.HonorificService; + + if (service.IsAvailable && service.DetectedApiVersion is { } version) + { + DrawStatusGlyph('●', theme.Colors.StatusSuccess); + ImGui.SameLine(); + ImGui.TextUnformatted(string.Format( + HellionStrings.Settings_Integrations_Honorific_Status_Detected, + version.Major, version.Minor)); + } + else if (service.DetectedApiVersion is { } incompatibleVersion) + { + DrawStatusGlyph('⚠', theme.Colors.StatusWarning); + ImGui.SameLine(); + ImGui.TextUnformatted(string.Format( + HellionStrings.Settings_Integrations_Honorific_Status_Incompatible, + 3, incompatibleVersion.Major, incompatibleVersion.Minor)); + } + else + { + DrawStatusGlyph('○', theme.Colors.TextMuted); + ImGui.SameLine(); + ImGui.TextUnformatted(HellionStrings.Settings_Integrations_Honorific_Status_NotInstalled); + } + } + + private static void DrawStatusGlyph(char glyph, uint rgba) + { + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(rgba))) + { + ImGui.TextUnformatted(glyph.ToString()); + } + } + + private void DrawComingSoonSection() + { + DrawSectionHeader(HellionStrings.Settings_Integrations_ComingSoon_SectionHeader); + ImGui.TextWrapped(HellionStrings.Settings_Integrations_ComingSoon_Intro); + ImGui.Spacing(); + + // Static list maintained in code (not Configuration). Each cycle + // that lands a real integration removes its stub here and adds a + // full section above the Coming Soon block. + DrawComingSoonItem( + HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Title, + HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Description); + DrawComingSoonItem( + HellionStrings.Settings_Integrations_ComingSoon_Notifications_Title, + HellionStrings.Settings_Integrations_ComingSoon_Notifications_Description); + DrawComingSoonItem( + HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Title, + HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Description); + DrawComingSoonItem( + HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Title, + HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Description); + DrawComingSoonItem( + HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Title, + HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Description); + } + + private void DrawComingSoonItem(string title, string description) + { + var theme = Plugin.ThemeRegistry.Active; + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted))) + { + ImGui.TextUnformatted("◌"); + } + ImGui.SameLine(); + ImGui.TextUnformatted(title); + using (ImRaii.PushIndent()) + { + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted))) + { + ImGui.TextWrapped(description); + } + } + ImGui.Spacing(); + } + + private void DrawGotAnIdeaSection() + { + DrawSectionHeader(HellionStrings.Settings_Integrations_GotAnIdea_SectionHeader); + ImGui.TextWrapped(HellionStrings.Settings_Integrations_GotAnIdea_Body); + ImGui.Spacing(); + + var theme = Plugin.ThemeRegistry.Active; + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.Primary))) + { + // Selectable so the whole "→ link label" line is clickable and + // shows a hover state, matching the affordance users expect from + // hyperlinks in ImGui-driven plugins. Fully-qualified + // Dalamud.Utility.Util.OpenLink because HellionChat.Util is in + // scope here and an unqualified Util would clash. + if (ImGui.Selectable("→ " + HellionStrings.Settings_Integrations_GotAnIdea_LinkLabel)) + { + Dalamud.Utility.Util.OpenLink(BrandingLinks.HellionForgeDiscordInvite); + } + } + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + } + } + + private void DrawSectionHeader(string label) + { + var theme = Plugin.ThemeRegistry.Active; + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.Primary))) + { + ImGui.TextUnformatted("── " + label + " ──"); + } + } +} From 7494b001a20a880db56188779cfe329bd0d82342 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:41:50 +0200 Subject: [PATCH 10/20] fix(integrations): schedule Honorific initial pull on framework thread --- HellionChat/Integrations/HonorificService.cs | 41 +++++++++++++++++--- HellionChat/Plugin.cs | 2 +- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/HellionChat/Integrations/HonorificService.cs b/HellionChat/Integrations/HonorificService.cs index 2361697..957c827 100644 --- a/HellionChat/Integrations/HonorificService.cs +++ b/HellionChat/Integrations/HonorificService.cs @@ -26,14 +26,16 @@ internal sealed class HonorificService : IDisposable private readonly ICallGateSubscriber _disposing; private readonly IPluginLog _log; + private readonly IFramework _framework; private bool _versionWarningLogged; public HonorificTitleData? CurrentTitle { get; private set; } public bool IsAvailable { get; private set; } public (uint Major, uint Minor)? DetectedApiVersion { get; private set; } - public HonorificService(IDalamudPluginInterface pluginInterface, IPluginLog log) + public HonorificService(IDalamudPluginInterface pluginInterface, IPluginLog log, IFramework framework) { + _framework = framework; _log = log; // Dalamud caches gate objects per-name for the lifetime of the @@ -41,6 +43,18 @@ internal sealed class HonorificService : IDisposable // Honorific isn't loaded yet — the gate just won't fire. Calling // InvokeFunc before Honorific is up will throw, which is why the // initial pull below is wrapped in try-catch. + // + // Thread-context: plugin constructors run on Dalamud's plugin-loader + // thread, NOT the framework thread. Honorific's IPC handlers read + // ObjectTable.LocalPlayer (Honorific IpcProvider.cs:61), which throws + // "Not on main thread!" outside the framework thread. If Honorific is + // already loaded when HellionChat starts, a synchronous InvokeFunc + // here would surface that exception, the broad catch below would + // mark IsAvailable=false, and OnTitleChanged's `if (!IsAvailable)` + // gate would block every subsequent title update. We therefore + // schedule the initial pull onto the framework thread via + // IFramework.RunOnFrameworkThread so the IPC call sees the right + // thread context. _apiVersion = pluginInterface .GetIpcSubscriber<(uint, uint)>($"{IpcNamespace}.ApiVersion"); _getLocalCharacterTitle = pluginInterface @@ -56,7 +70,7 @@ internal sealed class HonorificService : IDisposable _ready.Subscribe(OnReady); _disposing.Subscribe(OnDisposing); - TryInitialPull(); + _framework.RunOnFrameworkThread(TryInitialPull); } public void Dispose() @@ -131,7 +145,12 @@ internal sealed class HonorificService : IDisposable // Honorific loaded after HellionChat; redo the version check and // initial pull. Idempotent on purpose — Honorific can fire Ready // more than once across reloads. - TryInitialPull(); + // + // Honorific's NotifyReady may dispatch from any thread, and + // TryInitialPull eventually calls IPC handlers that read + // ObjectTable.LocalPlayer — same "Not on main thread!" hazard as + // the constructor path. Schedule onto the framework thread. + _framework.RunOnFrameworkThread(TryInitialPull); } private void OnDisposing() @@ -164,9 +183,19 @@ internal sealed class HonorificService : IDisposable // Threading note: Dalamud fires IPC events on the framework thread and // ImGui renders on the framework thread, so OnTitleChanged and the - // render path that reads CurrentTitle never race. If a future change - // moves either side onto a worker thread, switch to volatile/Interlocked - // for the CurrentTitle field. + // render path that reads CurrentTitle never race — OnTitleChanged is + // safe to keep direct (no RunOnFrameworkThread wrap needed) because + // LocalCharacterTitleChanged delivery is framework-thread by Dalamud + // contract. If a future change moves either side onto a worker thread, + // switch to volatile/Interlocked for the CurrentTitle field. + // + // The constructor's initial pull and OnReady, on the other hand, are + // explicitly scheduled via IFramework.RunOnFrameworkThread because + // they run outside that contract: the constructor executes on the + // plugin-loader thread, and Honorific's NotifyReady can dispatch from + // any thread. Both call paths eventually invoke IPC handlers that read + // ObjectTable.LocalPlayer, which throws "Not on main thread!" off the + // framework thread — see the constructor comment block for context. // // Divergence from ChatTwo/Ipc/ExtraChat.cs: that file uses `volatile` // on its state fields out of caution. We don't, because the framework- diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index f1635d1..9744593 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -443,7 +443,7 @@ public sealed class Plugin : IDalamudPlugin // Ready/Disposing events from the target plugins are caught from // the very first frame, even if the user's Honorific reloads // mid-session. See HellionChat/Integrations/HonorificService.cs. - HonorificService = new Integrations.HonorificService(Interface, Log); + HonorificService = new Integrations.HonorificService(Interface, Log, Framework); StatusBar = new Ui.StatusBar(); From ddb293399e89573db46cb0b3c27ccebd5ea10f0f Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 19:59:13 +0200 Subject: [PATCH 11/20] feat(ui): register Integrations tab in settings window --- HellionChat/Ui/Settings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/HellionChat/Ui/Settings.cs b/HellionChat/Ui/Settings.cs index 734e6a6..db53079 100755 --- a/HellionChat/Ui/Settings.cs +++ b/HellionChat/Ui/Settings.cs @@ -51,6 +51,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window new SettingsTabs.Tabs(Plugin, Mutable), new SettingsTabs.Privacy(Plugin, Mutable), new DataManagement(Plugin, Mutable), + new SettingsTabs.Integrations(Plugin, Mutable), new Information(Mutable), ]; From 477591e2fa99cebb08a10cc40ee838eeabacd71b Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 20:02:10 +0200 Subject: [PATCH 12/20] feat(ui): render Honorific title in chat header above message log --- HellionChat/Ui/ChatLogWindow.cs | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs index e62719e..8f4a4fa 100644 --- a/HellionChat/Ui/ChatLogWindow.cs +++ b/HellionChat/Ui/ChatLogWindow.cs @@ -6,6 +6,7 @@ using System.Text; using HellionChat.Code; using HellionChat.GameFunctions; using HellionChat.GameFunctions.Types; +using HellionChat.Integrations; using HellionChat.Resources; using HellionChat.Util; using Dalamud.Game.Addon.Lifecycle; @@ -1681,7 +1682,17 @@ public sealed class ChatLogWindow : Window // log so users discover the feature without having to right-click the tab. // Renders only for the active tab in the main ChatLogWindow; pop-out // windows have their own render path and skip this toolbar. + // + // Hellion Chat v1.3.0 — also renders the optional Honorific title slot + // left of the pop-out button, when HonorificService reports an active + // custom title and the user has ShowHonorificTitleInHeader enabled. private void DrawChatHeaderToolbar(Tab tab) + { + DrawHonorificTitleSlot(); + DrawPopOutButton(tab); + } + + private void DrawPopOutButton(Tab tab) { var avail = ImGui.GetContentRegionAvail().X; var iconWidth = ImGui.GetFrameHeight(); @@ -1694,6 +1705,82 @@ public sealed class ChatLogWindow : Window } } + // Renders the Honorific custom title to the left of the pop-out button, + // wrapped in guillemets to match how the game itself displays titles. + // We lay out the title first, then DrawPopOutButton uses + // GetContentRegionAvail to anchor itself flush right — that's why the + // call order in DrawChatHeaderToolbar matters: title first, button second. + // + // The slot stays on the same line as the pop-out button so the chat + // log doesn't lose vertical space; we use ImGui.SameLine after our + // text so the cursor X is still on the toolbar row when the pop-out + // button takes over. + private void DrawHonorificTitleSlot() + { + var service = Plugin.HonorificService; + var title = service.CurrentTitle; + if (!HonorificService.ShouldRenderSlot( + Plugin.Config.ShowHonorificTitleInHeader, + service.IsAvailable, + title)) + { + return; + } + + // Truncate with ellipsis when the title would overlap the pop-out + // button. We reserve the icon width plus a small gap so the title + // never touches the button edge — readability over information density. + const float gapPx = 8f; + var avail = ImGui.GetContentRegionAvail().X; + var iconWidth = ImGui.GetFrameHeight(); + var maxTitleWidth = avail - iconWidth - gapPx; + if (maxTitleWidth <= 0) + { + return; + } + + var rendered = "«" + title!.Title + "»"; + rendered = TruncateToWidth(rendered, maxTitleWidth); + + var color = title.Color is { } c + ? new Vector4(c.X, c.Y, c.Z, 1f) + : ImGui.GetStyle().Colors[(int)ImGuiCol.Text]; + + using (ImRaii.PushColor(ImGuiCol.Text, color)) + { + ImGui.TextUnformatted(rendered); + } + ImGui.SameLine(); + } + + private static string TruncateToWidth(string text, float maxWidth) + { + if (ImGui.CalcTextSize(text).X <= maxWidth) + { + return text; + } + + // Binary-search the longest prefix that fits with an ellipsis. + const string ellipsis = "…"; + var lo = 0; + var hi = text.Length; + while (lo < hi) + { + var mid = (lo + hi + 1) / 2; + var candidate = text[..mid] + ellipsis; + if (ImGui.CalcTextSize(candidate).X <= maxWidth) + { + lo = mid; + } + else + { + hi = mid - 1; + } + } + + return lo == 0 ? ellipsis : text[..lo] + ellipsis; + } + // Hellion Chat v0.6.1 — One-Time-Hint-Banner introducing the chat header // pop-out toolbar button and the right-click pathway. Reuses the visual // pattern from Popout.cs DrawHintBannerIfNeeded so users see a familiar From da6da3265118ceb60a852bc5c25bda5b2661a29f Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 20:14:29 +0200 Subject: [PATCH 13/20] fix(ui): add Integrations card to settings overview grid --- HellionChat/Resources/HellionStrings.Designer.cs | 2 ++ HellionChat/Resources/HellionStrings.de.resx | 6 ++++++ HellionChat/Resources/HellionStrings.resx | 6 ++++++ HellionChat/Ui/SettingsOverview.cs | 1 + 4 files changed, 15 insertions(+) diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index 66b7489..fdcde43 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -331,6 +331,8 @@ internal class HellionStrings internal static string Settings_Card_FontsAndColours_Subtext => Get(nameof(Settings_Card_FontsAndColours_Subtext)); internal static string Settings_Card_DataManagement_Title => Get(nameof(Settings_Card_DataManagement_Title)); internal static string Settings_Card_DataManagement_Subtext => Get(nameof(Settings_Card_DataManagement_Subtext)); + internal static string Settings_Card_Integrations_Title => Get(nameof(Settings_Card_Integrations_Title)); + internal static string Settings_Card_Integrations_Subtext => Get(nameof(Settings_Card_Integrations_Subtext)); // Hellion Chat — v1.2.1 Theme & Layout tab section headings + WindowOpacity slider internal static string Settings_ThemeAndLayout_Theme_Heading => Get(nameof(Settings_ThemeAndLayout_Theme_Heading)); diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index 3912854..88b978a 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -746,6 +746,12 @@ Was passiert mit gespeicherten Daten — Aufbewahrung, Aufräumen, Export, DB-Stats. + + Integrationen + + + Andere Dalamud-Plugins, mit denen HellionChat zusammenarbeitet — auto-detected, mit Vorschau auf kommende Integrationen. + Theme diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index 63d6931..ea54085 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -746,6 +746,12 @@ What happens to stored data — retention, cleanup, export, DB stats. + + Integrations + + + Other Dalamud plugins HellionChat reacts to — auto-detected, with a "coming soon" preview of upcoming integrations. + Theme diff --git a/HellionChat/Ui/SettingsOverview.cs b/HellionChat/Ui/SettingsOverview.cs index f8fcf3a..2e757f4 100644 --- a/HellionChat/Ui/SettingsOverview.cs +++ b/HellionChat/Ui/SettingsOverview.cs @@ -26,6 +26,7 @@ internal sealed class SettingsOverview (FontAwesomeIcon.FolderTree, "Settings_Card_Tabs_Title", "Settings_Card_Tabs_Subtext"), (FontAwesomeIcon.ShieldAlt, "Settings_Card_Privacy_Title", "Settings_Card_Privacy_Subtext"), (FontAwesomeIcon.Database, "Settings_Card_DataManagement_Title", "Settings_Card_DataManagement_Subtext"), + (FontAwesomeIcon.Plug, "Settings_Card_Integrations_Title", "Settings_Card_Integrations_Subtext"), (FontAwesomeIcon.InfoCircle, "Settings_Card_Information_Title", "Settings_Card_Information_Subtext"), ]; From b2f158f89347c0b4bd256af32413851597c35632 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 20:17:22 +0200 Subject: [PATCH 14/20] fix(ui): add Crown icon and hover tooltip to Honorific title slot --- .../Resources/HellionStrings.Designer.cs | 3 ++ HellionChat/Resources/HellionStrings.de.resx | 3 ++ HellionChat/Resources/HellionStrings.resx | 3 ++ HellionChat/Ui/ChatLogWindow.cs | 41 +++++++++++++++---- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index fdcde43..547a5b5 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -383,4 +383,7 @@ internal class HellionStrings internal static string Settings_Integrations_GotAnIdea_SectionHeader => Get(nameof(Settings_Integrations_GotAnIdea_SectionHeader)); internal static string Settings_Integrations_GotAnIdea_Body => Get(nameof(Settings_Integrations_GotAnIdea_Body)); internal static string Settings_Integrations_GotAnIdea_LinkLabel => Get(nameof(Settings_Integrations_GotAnIdea_LinkLabel)); + + // Hellion Chat — v1.3.0 Honorific title slot tooltip + internal static string ChatHeader_HonorificTitle_Tooltip => Get(nameof(ChatHeader_HonorificTitle_Tooltip)); } diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index 88b978a..031615b 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -866,4 +866,7 @@ discord.gg/X9V7Kcv5gR + + Custom-Titel von Honorific + diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index ea54085..cb9857c 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -866,4 +866,7 @@ discord.gg/X9V7Kcv5gR + + Honorific custom title + diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs index 8f4a4fa..95cf343 100644 --- a/HellionChat/Ui/ChatLogWindow.cs +++ b/HellionChat/Ui/ChatLogWindow.cs @@ -1727,13 +1727,22 @@ public sealed class ChatLogWindow : Window return; } - // Truncate with ellipsis when the title would overlap the pop-out - // button. We reserve the icon width plus a small gap so the title - // never touches the button edge — readability over information density. - const float gapPx = 8f; + // Reserve space for the crown icon plus a small gap before the title, + // then the title itself, then the gap-to-pop-out-button. We measure the + // crown width inside the FontAwesome font push because FontAwesome + // glyphs render in a different font than the regular ImGui text. + const float gapAfterCrown = 4f; + const float gapBeforeButton = 8f; var avail = ImGui.GetContentRegionAvail().X; var iconWidth = ImGui.GetFrameHeight(); - var maxTitleWidth = avail - iconWidth - gapPx; + + float crownWidth; + using (Plugin.FontManager.FontAwesome.Push()) + { + crownWidth = ImGui.CalcTextSize(FontAwesomeIcon.Crown.ToIconString()).X; + } + + var maxTitleWidth = avail - iconWidth - gapBeforeButton - crownWidth - gapAfterCrown; if (maxTitleWidth <= 0) { return; @@ -1742,14 +1751,32 @@ public sealed class ChatLogWindow : Window var rendered = "«" + title!.Title + "»"; rendered = TruncateToWidth(rendered, maxTitleWidth); - var color = title.Color is { } c + var titleColor = title.Color is { } c ? new Vector4(c.X, c.Y, c.Z, 1f) : ImGui.GetStyle().Colors[(int)ImGuiCol.Text]; - using (ImRaii.PushColor(ImGuiCol.Text, color)) + var theme = Plugin.ThemeRegistry.Active; + + // Group so the tooltip's IsItemHovered check fires for hover anywhere + // on the crown-plus-title pair, not just one of the two. + ImGui.BeginGroup(); + using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted))) + using (Plugin.FontManager.FontAwesome.Push()) + { + ImGui.TextUnformatted(FontAwesomeIcon.Crown.ToIconString()); + } + ImGui.SameLine(0f, gapAfterCrown); + using (ImRaii.PushColor(ImGuiCol.Text, titleColor)) { ImGui.TextUnformatted(rendered); } + ImGui.EndGroup(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(HellionStrings.ChatHeader_HonorificTitle_Tooltip); + } + ImGui.SameLine(); } From 33a4d94c44e449097813d070baf9e6f2af772507 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 20:25:44 +0200 Subject: [PATCH 15/20] fix(ui): use FontAwesome Hourglass for coming-soon items --- HellionChat/Ui/SettingsTabs/Integrations.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HellionChat/Ui/SettingsTabs/Integrations.cs b/HellionChat/Ui/SettingsTabs/Integrations.cs index 5edd241..fbe1419 100644 --- a/HellionChat/Ui/SettingsTabs/Integrations.cs +++ b/HellionChat/Ui/SettingsTabs/Integrations.cs @@ -1,4 +1,5 @@ using Dalamud.Bindings.ImGui; +using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using HellionChat.Branding; using HellionChat.Resources; @@ -134,8 +135,9 @@ internal sealed class Integrations : ISettingsTab { var theme = Plugin.ThemeRegistry.Active; using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted))) + using (Plugin.FontManager.FontAwesome.Push()) { - ImGui.TextUnformatted("◌"); + ImGui.TextUnformatted(FontAwesomeIcon.Hourglass.ToIconString()); } ImGui.SameLine(); ImGui.TextUnformatted(title); From dceb0281849c2d2969feea44143322b9b5104b6b Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 20:34:03 +0200 Subject: [PATCH 16/20] feat(integrations): link Honorific repo and Caraxi attribution --- HellionChat/Integrations/IntegrationLinks.cs | 12 ++++++++++++ .../Resources/HellionStrings.Designer.cs | 2 ++ HellionChat/Resources/HellionStrings.de.resx | 6 ++++++ HellionChat/Resources/HellionStrings.resx | 6 ++++++ HellionChat/Ui/SettingsTabs/Integrations.cs | 17 +++++++++++++++++ 5 files changed, 43 insertions(+) create mode 100644 HellionChat/Integrations/IntegrationLinks.cs diff --git a/HellionChat/Integrations/IntegrationLinks.cs b/HellionChat/Integrations/IntegrationLinks.cs new file mode 100644 index 0000000..f9e467e --- /dev/null +++ b/HellionChat/Integrations/IntegrationLinks.cs @@ -0,0 +1,12 @@ +namespace HellionChat.Integrations; + +// External URLs for the third-party plugins HellionChat integrates with. +// Kept separate from BrandingLinks (which is for Hellion-owned URLs) so +// future cycles can extend this file with maintainer attribution links +// for Moodles, NotificationMaster, ExtraChat, etc. without polluting the +// brand-links class. +internal static class IntegrationLinks +{ + public const string Honorific_Repo = "https://github.com/Caraxi/Honorific"; + public const string Honorific_Author = "https://github.com/Caraxi"; +} diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index 547a5b5..6cc0eec 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -368,6 +368,8 @@ internal class HellionStrings internal static string Settings_Integrations_Honorific_Status_Incompatible => Get(nameof(Settings_Integrations_Honorific_Status_Incompatible)); internal static string Settings_Integrations_Honorific_Toggle => Get(nameof(Settings_Integrations_Honorific_Toggle)); internal static string Settings_Integrations_Honorific_ToggleHint => Get(nameof(Settings_Integrations_Honorific_ToggleHint)); + internal static string Settings_Integrations_Honorific_LinkRepo => Get(nameof(Settings_Integrations_Honorific_LinkRepo)); + internal static string Settings_Integrations_Honorific_LinkAuthor => Get(nameof(Settings_Integrations_Honorific_LinkAuthor)); internal static string Settings_Integrations_ComingSoon_SectionHeader => Get(nameof(Settings_Integrations_ComingSoon_SectionHeader)); internal static string Settings_Integrations_ComingSoon_Intro => Get(nameof(Settings_Integrations_ComingSoon_Intro)); internal static string Settings_Integrations_ComingSoon_ContextMenu_Title => Get(nameof(Settings_Integrations_ComingSoon_ContextMenu_Title)); diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index 031615b..a94ec38 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -821,6 +821,12 @@ Zeigt deinen Custom-Titel aus Honorific im Header über dem Chat-Log an, in der von dir gewählten Farbe. + + Honorific auf GitHub + + + von Caraxi + Demnächst diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index cb9857c..93dbc50 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -821,6 +821,12 @@ Displays your custom title from Honorific in the header above the chat log, in your chosen colour. + + Honorific on GitHub + + + by Caraxi + Coming soon diff --git a/HellionChat/Ui/SettingsTabs/Integrations.cs b/HellionChat/Ui/SettingsTabs/Integrations.cs index fbe1419..40f47b8 100644 --- a/HellionChat/Ui/SettingsTabs/Integrations.cs +++ b/HellionChat/Ui/SettingsTabs/Integrations.cs @@ -2,6 +2,7 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using HellionChat.Branding; +using HellionChat.Integrations; using HellionChat.Resources; using HellionChat.Util; @@ -66,6 +67,22 @@ internal sealed class Integrations : ISettingsTab ImGui.TextWrapped(HellionStrings.Settings_Integrations_Honorific_ToggleHint); } } + + // Maintainer attribution. Honorific has no LICENSE in its repo so we + // can't bundle its assets, but linking to the upstream and the + // author's profile is the polite minimum. Plain ImGui buttons keep + // the visual weight modest, the FontAwesome Brands subset is not + // guaranteed in Dalamud's font set so we use text labels. + ImGui.Spacing(); + if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkRepo)) + { + Dalamud.Utility.Util.OpenLink(IntegrationLinks.Honorific_Repo); + } + ImGui.SameLine(); + if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkAuthor)) + { + Dalamud.Utility.Util.OpenLink(IntegrationLinks.Honorific_Author); + } } private void DrawHonorificStatus() From e58376bf50aa1d436fb42aa5bc80b8a37b5b316c Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 20:39:41 +0200 Subject: [PATCH 17/20] fix(ui): use ImGui.Button for Hellion Forge Discord link --- HellionChat/Resources/HellionStrings.de.resx | 2 +- HellionChat/Resources/HellionStrings.resx | 2 +- HellionChat/Ui/SettingsTabs/Integrations.cs | 17 ++--------------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index a94ec38..b848362 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -870,7 +870,7 @@ Idee für eine Plugin-Integration, die nicht auf der Liste steht? Komm auf den Hellion-Forge-Discord und schreib mir — Community-Input bestimmt die Roadmap. - discord.gg/X9V7Kcv5gR + Hellion Forge öffnen Custom-Titel von Honorific diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index 93dbc50..18b7974 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -870,7 +870,7 @@ Got an idea for a plugin integration that's not on this list? Hop on the Hellion Forge Discord and tell me — community input drives the roadmap. - discord.gg/X9V7Kcv5gR + Open Hellion Forge Honorific custom title diff --git a/HellionChat/Ui/SettingsTabs/Integrations.cs b/HellionChat/Ui/SettingsTabs/Integrations.cs index 40f47b8..010d540 100644 --- a/HellionChat/Ui/SettingsTabs/Integrations.cs +++ b/HellionChat/Ui/SettingsTabs/Integrations.cs @@ -174,22 +174,9 @@ internal sealed class Integrations : ISettingsTab ImGui.TextWrapped(HellionStrings.Settings_Integrations_GotAnIdea_Body); ImGui.Spacing(); - var theme = Plugin.ThemeRegistry.Active; - using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.Primary))) + if (ImGui.Button(HellionStrings.Settings_Integrations_GotAnIdea_LinkLabel)) { - // Selectable so the whole "→ link label" line is clickable and - // shows a hover state, matching the affordance users expect from - // hyperlinks in ImGui-driven plugins. Fully-qualified - // Dalamud.Utility.Util.OpenLink because HellionChat.Util is in - // scope here and an unqualified Util would clash. - if (ImGui.Selectable("→ " + HellionStrings.Settings_Integrations_GotAnIdea_LinkLabel)) - { - Dalamud.Utility.Util.OpenLink(BrandingLinks.HellionForgeDiscordInvite); - } - } - if (ImGui.IsItemHovered()) - { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + Dalamud.Utility.Util.OpenLink(BrandingLinks.HellionForgeDiscordInvite); } } From 2d768e4edbfcdbbc3060e52de95a0400cb4a6a6a Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 21:08:50 +0200 Subject: [PATCH 18/20] refactor(integrations): apply review findings (constant, util move, prose cleanup) --- HellionChat/Integrations/HonorificService.cs | 8 ++++- HellionChat/Integrations/IntegrationLinks.cs | 4 +-- HellionChat/Resources/HellionStrings.de.resx | 4 +-- HellionChat/Resources/HellionStrings.resx | 4 +-- HellionChat/Ui/ChatLogWindow.cs | 34 ++------------------ HellionChat/Ui/SettingsTabs/Integrations.cs | 6 ++-- HellionChat/Util/StringUtil.cs | 33 +++++++++++++++++++ 7 files changed, 52 insertions(+), 41 deletions(-) diff --git a/HellionChat/Integrations/HonorificService.cs b/HellionChat/Integrations/HonorificService.cs index 957c827..14294b2 100644 --- a/HellionChat/Integrations/HonorificService.cs +++ b/HellionChat/Integrations/HonorificService.cs @@ -17,6 +17,12 @@ internal sealed class HonorificService : IDisposable { private const string IpcNamespace = "Honorific"; + // Major version of the Honorific IPC contract HellionChat is built against. + // Used both by the runtime compatibility check and by the settings tab when + // it tells the user which major version we expected, so the literal lives + // in exactly one place. + internal const uint ExpectedApiMajor = 3; + // IPC gates we subscribe to. Keep them as fields so Dispose can // unsubscribe the same instances we subscribed in the constructor. private readonly ICallGateSubscriber<(uint, uint)> _apiVersion; @@ -221,7 +227,7 @@ internal sealed class HonorificService : IDisposable internal static bool IsApiVersionCompatible((uint Major, uint Minor) apiVersion) { - return apiVersion.Major == 3; + return apiVersion.Major == ExpectedApiMajor; } internal static bool ShouldRenderSlot( diff --git a/HellionChat/Integrations/IntegrationLinks.cs b/HellionChat/Integrations/IntegrationLinks.cs index f9e467e..fd21bc2 100644 --- a/HellionChat/Integrations/IntegrationLinks.cs +++ b/HellionChat/Integrations/IntegrationLinks.cs @@ -7,6 +7,6 @@ namespace HellionChat.Integrations; // brand-links class. internal static class IntegrationLinks { - public const string Honorific_Repo = "https://github.com/Caraxi/Honorific"; - public const string Honorific_Author = "https://github.com/Caraxi"; + public const string HonorificRepo = "https://github.com/Caraxi/Honorific"; + public const string HonorificAuthor = "https://github.com/Caraxi"; } diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index b848362..0729fa8 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -750,7 +750,7 @@ Integrationen - Andere Dalamud-Plugins, mit denen HellionChat zusammenarbeitet — auto-detected, mit Vorschau auf kommende Integrationen. + Andere Dalamud-Plugins, mit denen HellionChat zusammenarbeitet. Auto-detected, mit Vorschau auf kommende Integrationen. Theme @@ -867,7 +867,7 @@ Idee? - Idee für eine Plugin-Integration, die nicht auf der Liste steht? Komm auf den Hellion-Forge-Discord und schreib mir — Community-Input bestimmt die Roadmap. + Idee für eine Plugin-Integration, die nicht auf der Liste steht? Komm auf den Hellion-Forge-Discord und schreib mir. Community-Input bestimmt die Roadmap. Hellion Forge öffnen diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index 18b7974..c8071b2 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -750,7 +750,7 @@ Integrations - Other Dalamud plugins HellionChat reacts to — auto-detected, with a "coming soon" preview of upcoming integrations. + Other Dalamud plugins HellionChat reacts to. Auto-detected, with a "coming soon" preview of upcoming integrations. Theme @@ -867,7 +867,7 @@ Got an idea? - Got an idea for a plugin integration that's not on this list? Hop on the Hellion Forge Discord and tell me — community input drives the roadmap. + Got an idea for a plugin integration that's not on this list? Hop on the Hellion Forge Discord and tell me. Community input drives the roadmap. Open Hellion Forge diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs index 95cf343..94cc0a9 100644 --- a/HellionChat/Ui/ChatLogWindow.cs +++ b/HellionChat/Ui/ChatLogWindow.cs @@ -1683,7 +1683,7 @@ public sealed class ChatLogWindow : Window // Renders only for the active tab in the main ChatLogWindow; pop-out // windows have their own render path and skip this toolbar. // - // Hellion Chat v1.3.0 — also renders the optional Honorific title slot + // Hellion Chat v1.3.0 also renders the optional Honorific title slot // left of the pop-out button, when HonorificService reports an active // custom title and the user has ShowHonorificTitleInHeader enabled. private void DrawChatHeaderToolbar(Tab tab) @@ -1708,7 +1708,7 @@ public sealed class ChatLogWindow : Window // Renders the Honorific custom title to the left of the pop-out button, // wrapped in guillemets to match how the game itself displays titles. // We lay out the title first, then DrawPopOutButton uses - // GetContentRegionAvail to anchor itself flush right — that's why the + // GetContentRegionAvail to anchor itself flush right, which is why the // call order in DrawChatHeaderToolbar matters: title first, button second. // // The slot stays on the same line as the pop-out button so the chat @@ -1749,7 +1749,7 @@ public sealed class ChatLogWindow : Window } var rendered = "«" + title!.Title + "»"; - rendered = TruncateToWidth(rendered, maxTitleWidth); + rendered = StringUtil.TruncateToFitWidth(rendered, maxTitleWidth); var titleColor = title.Color is { } c ? new Vector4(c.X, c.Y, c.Z, 1f) @@ -1780,34 +1780,6 @@ public sealed class ChatLogWindow : Window ImGui.SameLine(); } - private static string TruncateToWidth(string text, float maxWidth) - { - if (ImGui.CalcTextSize(text).X <= maxWidth) - { - return text; - } - - // Binary-search the longest prefix that fits with an ellipsis. - const string ellipsis = "…"; - var lo = 0; - var hi = text.Length; - while (lo < hi) - { - var mid = (lo + hi + 1) / 2; - var candidate = text[..mid] + ellipsis; - if (ImGui.CalcTextSize(candidate).X <= maxWidth) - { - lo = mid; - } - else - { - hi = mid - 1; - } - } - - return lo == 0 ? ellipsis : text[..lo] + ellipsis; - } - // Hellion Chat v0.6.1 — One-Time-Hint-Banner introducing the chat header // pop-out toolbar button and the right-click pathway. Reuses the visual // pattern from Popout.cs DrawHintBannerIfNeeded so users see a familiar diff --git a/HellionChat/Ui/SettingsTabs/Integrations.cs b/HellionChat/Ui/SettingsTabs/Integrations.cs index 010d540..314a32a 100644 --- a/HellionChat/Ui/SettingsTabs/Integrations.cs +++ b/HellionChat/Ui/SettingsTabs/Integrations.cs @@ -76,12 +76,12 @@ internal sealed class Integrations : ISettingsTab ImGui.Spacing(); if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkRepo)) { - Dalamud.Utility.Util.OpenLink(IntegrationLinks.Honorific_Repo); + Dalamud.Utility.Util.OpenLink(IntegrationLinks.HonorificRepo); } ImGui.SameLine(); if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkAuthor)) { - Dalamud.Utility.Util.OpenLink(IntegrationLinks.Honorific_Author); + Dalamud.Utility.Util.OpenLink(IntegrationLinks.HonorificAuthor); } } @@ -104,7 +104,7 @@ internal sealed class Integrations : ISettingsTab ImGui.SameLine(); ImGui.TextUnformatted(string.Format( HellionStrings.Settings_Integrations_Honorific_Status_Incompatible, - 3, incompatibleVersion.Major, incompatibleVersion.Minor)); + HonorificService.ExpectedApiMajor, incompatibleVersion.Major, incompatibleVersion.Minor)); } else { diff --git a/HellionChat/Util/StringUtil.cs b/HellionChat/Util/StringUtil.cs index fdddc0a..b1a54d5 100755 --- a/HellionChat/Util/StringUtil.cs +++ b/HellionChat/Util/StringUtil.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Text; +using Dalamud.Bindings.ImGui; namespace HellionChat.Util; @@ -29,4 +30,36 @@ internal static class StringUtil // separator to '.' so a German locale doesn't render "1,5GB". return (Math.Sign(byteCount) * num).ToString("0.#", CultureInfo.InvariantCulture) + suf[place]; } + + // Returns the text unchanged when it already fits the width budget, + // otherwise the longest prefix plus a horizontal-ellipsis character that + // still fits. Used by the chat header Honorific title slot and reused by + // the chat-line truncation path in later cycles. + public static string TruncateToFitWidth(string text, float maxWidth) + { + if (ImGui.CalcTextSize(text).X <= maxWidth) + { + return text; + } + + // Binary-search the longest prefix that fits with an ellipsis. + const string ellipsis = "…"; + var lo = 0; + var hi = text.Length; + while (lo < hi) + { + var mid = (lo + hi + 1) / 2; + var candidate = text[..mid] + ellipsis; + if (ImGui.CalcTextSize(candidate).X <= maxWidth) + { + lo = mid; + } + else + { + hi = mid - 1; + } + } + + return lo == 0 ? ellipsis : text[..lo] + ellipsis; + } } From c8485233d5d5b706829749fc8f36062237e776cf Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Wed, 6 May 2026 21:54:58 +0200 Subject: [PATCH 19/20] chore: bump version to 1.3.0 and document Plugin Integrations Cycle 1 --- HellionChat/HellionChat.csproj | 2 +- HellionChat/HellionChat.yaml | 24 ++++++++++++++++++++++++ README.md | 8 ++++++-- docs/CHANGELOG.md | 22 ++++++++++++++++++++++ docs/ROADMAP.md | 21 ++++++++++++++++++--- repo.json | 14 +++++++------- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/HellionChat/HellionChat.csproj b/HellionChat/HellionChat.csproj index cd6f6d6..1190e83 100644 --- a/HellionChat/HellionChat.csproj +++ b/HellionChat/HellionChat.csproj @@ -4,7 +4,7 @@ 0.1.0 is our bootstrap release; the underlying Chat 2 base is called out in the yaml changelog so users can see what it derives from. --> - 1.2.3 + 1.3.0 enable enable