chore: housekeeping — linter & formatter setup

Add .prettierrc.json, .markdownlint.json, .yamllint.yaml, .gitattributes
Run CSharpier, Prettier and markdownlint across the entire codebase.
No logic changes — formatting, using order and line endings only.
This commit is contained in:
2026-05-10 13:01:00 +02:00
parent cd01fa63a1
commit 699d4ede1d
141 changed files with 8833 additions and 5733 deletions
+42 -28
View File
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
namespace HellionChat;
@@ -76,7 +76,10 @@ internal sealed class AutoTellTabsService : IDisposable
return;
}
if (message.Code.Type != ChatType.TellIncoming && message.Code.Type != ChatType.TellOutgoing)
if (
message.Code.Type != ChatType.TellIncoming
&& message.Code.Type != ChatType.TellOutgoing
)
{
return;
}
@@ -89,10 +92,11 @@ internal sealed class AutoTellTabsService : IDisposable
// (FFXIV changing tell payload shape, new edge cases) findable
// without having to crank up debug logging at the source.
Plugin.Log.Warning(
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, " +
$"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, " +
$"senderSourcePayloads={message.SenderSource?.Payloads?.Count ?? 0}, " +
$"contentSourcePayloads={message.ContentSource?.Payloads?.Count ?? 0}");
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, "
+ $"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, "
+ $"senderSourcePayloads={message.SenderSource?.Payloads?.Count ?? 0}, "
+ $"contentSourcePayloads={message.ContentSource?.Payloads?.Count ?? 0}"
);
return;
}
@@ -124,8 +128,9 @@ internal sealed class AutoTellTabsService : IDisposable
// PlayerPayload normally rides on a chunk's Link slot, but for
// some tell types FFXIV only puts it in the raw SeString —
// fall back to that before giving up.
var fromSender = ChunkUtil.TryGetPlayerPayload(message.Sender)
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
var fromSender =
ChunkUtil.TryGetPlayerPayload(message.Sender)
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
if (fromSender != null)
{
return (fromSender.PlayerName, fromSender.World.RowId);
@@ -137,17 +142,19 @@ internal sealed class AutoTellTabsService : IDisposable
// up either as a payload in the content (for tells typed via the
// Chat 2 input bar) or as the channel's tracked tell target (set by
// the SetContextTellTarget game hook). Same SeString fallback.
var fromContent = ChunkUtil.TryGetPlayerPayload(message.Content)
?? ChunkUtil.TryGetPlayerPayload(message.ContentSource)
?? ChunkUtil.TryGetPlayerPayload(message.Sender)
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
var fromContent =
ChunkUtil.TryGetPlayerPayload(message.Content)
?? ChunkUtil.TryGetPlayerPayload(message.ContentSource)
?? ChunkUtil.TryGetPlayerPayload(message.Sender)
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
if (fromContent != null)
{
return (fromContent.PlayerName, fromContent.World.RowId);
}
var current = _plugin.CurrentTab.CurrentChannel.TellTarget
?? _plugin.CurrentTab.CurrentChannel.TempTellTarget;
var current =
_plugin.CurrentTab.CurrentChannel.TellTarget
?? _plugin.CurrentTab.CurrentChannel.TempTellTarget;
if (current != null && current.IsSet())
{
return (current.Name, current.World);
@@ -162,7 +169,8 @@ internal sealed class AutoTellTabsService : IDisposable
t.IsTempTab
&& t.TellTarget != null
&& string.Equals(t.TellTarget.Name, name, StringComparison.OrdinalIgnoreCase)
&& t.TellTarget.World == world);
&& t.TellTarget.World == world
);
}
private void DropOldestTempTab()
@@ -171,8 +179,8 @@ internal sealed class AutoTellTabsService : IDisposable
// "I'm done with that conversation"), and within each bucket we
// pick the oldest LastActivity. This protects active conversations
// and unfinished greetings while still freeing up a slot.
var victim = Plugin.Config.Tabs
.Select((tab, idx) => (Tab: tab, Index: idx))
var victim = Plugin
.Config.Tabs.Select((tab, idx) => (Tab: tab, Index: idx))
.Where(t => t.Tab.IsTempTab)
.OrderByDescending(t => t.Tab.IsGreeted)
.ThenBy(t => t.Tab.LastActivity)
@@ -191,8 +199,9 @@ internal sealed class AutoTellTabsService : IDisposable
// popped tab is now a routine code path.
if (victim.Tab.PopOut)
{
var popout = _plugin.ChatLogWindow.ActivePopouts
.FirstOrDefault(p => p.TabIdentifier == victim.Tab.Identifier);
var popout = _plugin.ChatLogWindow.ActivePopouts.FirstOrDefault(p =>
p.TabIdentifier == victim.Tab.Identifier
);
if (popout != null)
{
popout.IsOpen = false;
@@ -286,7 +295,8 @@ internal sealed class AutoTellTabsService : IDisposable
_messageManager.CurrentContentId,
senderName,
senderWorld,
preloadCount + 1);
preloadCount + 1
);
var historicMessages = history
.Where(m => m.Id != currentMessageId)
@@ -314,7 +324,8 @@ internal sealed class AutoTellTabsService : IDisposable
// after the historical messages but before the current one.
tab.Messages.AddPrune(
MakeSystemMarker(HellionStrings.AutoTellTabs_HistorySeparator),
MessageManager.MessageDisplayLimit);
MessageManager.MessageDisplayLimit
);
}
catch (Exception ex)
{
@@ -324,7 +335,8 @@ internal sealed class AutoTellTabsService : IDisposable
Plugin.Log.Error(ex, "[AutoTellTabs] History preload failed");
tab.Messages.AddPrune(
MakeSystemMarker(HellionStrings.AutoTellTabs_HistoryLoadError),
MessageManager.MessageDisplayLimit);
MessageManager.MessageDisplayLimit
);
}
}
@@ -388,16 +400,18 @@ internal sealed class AutoTellTabsService : IDisposable
// the next plugin reload. Especially relevant once Auto-Pop-Out is
// enabled — every logout would otherwise leak as many ghosts as
// there were active /tell pop-outs.
var poppedTempTabIds = Plugin.Config.Tabs
.Where(t => t.IsTempTab && t.PopOut)
var poppedTempTabIds = Plugin
.Config.Tabs.Where(t => t.IsTempTab && t.PopOut)
.Select(t => t.Identifier)
.ToList();
if (poppedTempTabIds.Count > 0)
{
var poppedSet = poppedTempTabIds.ToHashSet();
foreach (var popout in _plugin.ChatLogWindow.ActivePopouts
.Where(p => poppedSet.Contains(p.TabIdentifier))
.ToList())
foreach (
var popout in _plugin
.ChatLogWindow.ActivePopouts.Where(p => poppedSet.Contains(p.TabIdentifier))
.ToList()
)
{
popout.IsOpen = false;
}
+10 -8
View File
@@ -1,6 +1,6 @@
using System.Linq;
using HellionChat.Resources;
using Dalamud.Plugin;
using HellionChat.Resources;
namespace HellionChat;
@@ -10,17 +10,19 @@ internal static class ChatTwoConflictDetector
public static void ThrowIfChatTwoIsLoaded(IDalamudPluginInterface pluginInterface)
{
var conflict = pluginInterface.InstalledPlugins
.FirstOrDefault(p =>
p.InternalName == UpstreamInternalName &&
p.IsLoaded);
var conflict = pluginInterface.InstalledPlugins.FirstOrDefault(p =>
p.InternalName == UpstreamInternalName && p.IsLoaded
);
if (conflict is null)
return;
var message = HellionStrings.ChatTwoConflictTitle + "\n\n" +
HellionStrings.ChatTwoConflictBody + "\n\n" +
HellionStrings.ChatTwoConflictAction;
var message =
HellionStrings.ChatTwoConflictTitle
+ "\n\n"
+ HellionStrings.ChatTwoConflictBody
+ "\n\n"
+ HellionStrings.ChatTwoConflictAction;
throw new System.InvalidOperationException(message);
}
+40 -18
View File
@@ -1,5 +1,5 @@
using HellionChat.Code;
using Dalamud.Game.Text.SeStringHandling;
using HellionChat.Code;
using MessagePack;
namespace HellionChat;
@@ -25,13 +25,14 @@ public abstract class Chunk
Link = link;
}
internal SeString? GetSeString() => Source switch
{
ChunkSource.None => null,
ChunkSource.Sender => Message?.SenderSource,
ChunkSource.Content => Message?.ContentSource,
_ => null,
};
internal SeString? GetSeString() =>
Source switch
{
ChunkSource.None => null,
ChunkSource.Sender => Message?.SenderSource,
ChunkSource.Content => Message?.ContentSource,
_ => null,
};
/// <summary>
/// Get some basic text for use in generating hashes.
@@ -42,7 +43,7 @@ public abstract class Chunk
{
TextChunk text => text.Content,
IconChunk icon => icon.Icon.ToString(),
_ => ""
_ => "",
};
}
}
@@ -57,18 +58,29 @@ public enum ChunkSource
[MessagePackObject(AllowPrivate = true)]
public class TextChunk : Chunk
{
[Key(2)] public ChatType? FallbackColour;
[Key(3)] public uint? Foreground;
[Key(4)] public uint? Glow;
[Key(5)] public bool Italic;
[Key(6)] public string Content;
[Key(2)]
public ChatType? FallbackColour;
private TextChunk(Chunk chunk, string content) : base(chunk.Source, chunk.Link)
[Key(3)]
public uint? Foreground;
[Key(4)]
public uint? Glow;
[Key(5)]
public bool Italic;
[Key(6)]
public string Content;
private TextChunk(Chunk chunk, string content)
: base(chunk.Source, chunk.Link)
{
Content = content;
}
internal TextChunk(ChunkSource source, Payload? link, string content) : base(source, link)
internal TextChunk(ChunkSource source, Payload? link, string content)
: base(source, link)
{
// This has been null in the past, and it broke rendering code.
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
@@ -76,7 +88,16 @@ public class TextChunk : Chunk
}
// ReSharper disable once UnusedMember.Global // Used by MessagePack
public TextChunk(ChunkSource source, Payload? link, ChatType? fallbackColour, uint? foreground, uint? glow, bool italic, string content) : base(source, link)
public TextChunk(
ChunkSource source,
Payload? link,
ChatType? fallbackColour,
uint? foreground,
uint? glow,
bool italic,
string content
)
: base(source, link)
{
FallbackColour = fallbackColour;
Foreground = foreground;
@@ -122,7 +143,8 @@ public class IconChunk : Chunk
[Key(2)]
public BitmapFontIcon Icon { get; set; }
public IconChunk(ChunkSource source, Payload? link, BitmapFontIcon icon) : base(source, link)
public IconChunk(ChunkSource source, Payload? link, BitmapFontIcon icon)
: base(source, link)
{
Icon = icon;
}
+1 -1
View File
@@ -16,7 +16,7 @@ public class ChatCode
}
public ChatCode(byte type, byte source, byte target)
: this((XivChatType)type, (XivChatRelationKind)source, (XivChatRelationKind)target) {}
: this((XivChatType)type, (XivChatRelationKind)source, (XivChatRelationKind)target) { }
public bool IsBattle()
{
+27 -19
View File
@@ -5,24 +5,32 @@ namespace HellionChat.Code;
internal static class ChatSourceExt
{
internal const ChatSource All =
ChatSource.LocalPlayer | ChatSource.PartyMember | ChatSource.AllianceMember |
ChatSource.OtherPlayer | ChatSource.EngagedEnemy | ChatSource.UnengagedEnemy |
ChatSource.FriendlyNpc | ChatSource.PetOrCompanion | ChatSource.PetOrCompanionParty |
ChatSource.PetOrCompanionAlliance | ChatSource.PetOrCompanionOther;
ChatSource.LocalPlayer
| ChatSource.PartyMember
| ChatSource.AllianceMember
| ChatSource.OtherPlayer
| ChatSource.EngagedEnemy
| ChatSource.UnengagedEnemy
| ChatSource.FriendlyNpc
| ChatSource.PetOrCompanion
| ChatSource.PetOrCompanionParty
| ChatSource.PetOrCompanionAlliance
| ChatSource.PetOrCompanionOther;
internal static string Name(this ChatSource source) => source switch
{
ChatSource.LocalPlayer => Language.ChatSource_Self,
ChatSource.PartyMember => Language.ChatSource_PartyMember,
ChatSource.AllianceMember => Language.ChatSource_AllianceMember,
ChatSource.OtherPlayer => Language.ChatSource_Other,
ChatSource.EngagedEnemy => Language.ChatSource_EngagedEnemy,
ChatSource.UnengagedEnemy => Language.ChatSource_UnengagedEnemy,
ChatSource.FriendlyNpc => Language.ChatSource_FriendlyNpc,
ChatSource.PetOrCompanion => Language.ChatSource_SelfPet,
ChatSource.PetOrCompanionParty => Language.ChatSource_PartyPet,
ChatSource.PetOrCompanionAlliance => Language.ChatSource_AlliancePet,
ChatSource.PetOrCompanionOther => Language.ChatSource_OtherPet,
_ => throw new ArgumentOutOfRangeException(nameof(source), source, null),
};
internal static string Name(this ChatSource source) =>
source switch
{
ChatSource.LocalPlayer => Language.ChatSource_Self,
ChatSource.PartyMember => Language.ChatSource_PartyMember,
ChatSource.AllianceMember => Language.ChatSource_AllianceMember,
ChatSource.OtherPlayer => Language.ChatSource_Other,
ChatSource.EngagedEnemy => Language.ChatSource_EngagedEnemy,
ChatSource.UnengagedEnemy => Language.ChatSource_UnengagedEnemy,
ChatSource.FriendlyNpc => Language.ChatSource_FriendlyNpc,
ChatSource.PetOrCompanion => Language.ChatSource_SelfPet,
ChatSource.PetOrCompanionParty => Language.ChatSource_PartyPet,
ChatSource.PetOrCompanionAlliance => Language.ChatSource_AlliancePet,
ChatSource.PetOrCompanionOther => Language.ChatSource_OtherPet,
_ => throw new ArgumentOutOfRangeException(nameof(source), source, null),
};
}
+265 -252
View File
@@ -1,92 +1,98 @@
using Dalamud.Game.Config;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Game.Config;
namespace HellionChat.Code;
internal static class ChatTypeExt
{
internal static IEnumerable<(string, ChatType[])> SortOrder =>
[
(Language.Options_Tabs_ChannelTypes_Special, [ChatType.Debug, ChatType.Urgent, ChatType.Notice]),
(Language.Options_Tabs_ChannelTypes_Chat,
[
ChatType.Say,
ChatType.Yell,
ChatType.Shout,
ChatType.TellIncoming,
ChatType.TellOutgoing,
ChatType.Party,
ChatType.CrossParty,
ChatType.Alliance,
ChatType.FreeCompany,
ChatType.PvpTeam,
ChatType.CrossLinkshell1,
ChatType.CrossLinkshell2,
ChatType.CrossLinkshell3,
ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5,
ChatType.CrossLinkshell6,
ChatType.CrossLinkshell7,
ChatType.CrossLinkshell8,
ChatType.Linkshell1,
ChatType.Linkshell2,
ChatType.Linkshell3,
ChatType.Linkshell4,
ChatType.Linkshell5,
ChatType.Linkshell6,
ChatType.Linkshell7,
ChatType.Linkshell8,
ChatType.NoviceNetwork,
ChatType.StandardEmote,
ChatType.CustomEmote
]),
(Language.Options_Tabs_ChannelTypes_Battle,
[
ChatType.Damage,
ChatType.Miss,
ChatType.Action,
ChatType.Item,
ChatType.Healing,
ChatType.GainBuff,
ChatType.LoseBuff,
ChatType.GainDebuff,
ChatType.LoseDebuff
]),
(Language.Options_Tabs_ChannelTypes_Announcements,
[
ChatType.System,
ChatType.BattleSystem,
ChatType.GatheringSystem,
ChatType.Error,
ChatType.Echo,
ChatType.NoviceNetworkSystem,
ChatType.FreeCompanyAnnouncement,
ChatType.PvpTeamAnnouncement,
ChatType.FreeCompanyLoginLogout,
ChatType.PvpTeamLoginLogout,
ChatType.RetainerSale,
ChatType.NpcDialogue,
ChatType.NpcAnnouncement,
ChatType.LootNotice,
ChatType.Progress,
ChatType.LootRoll,
ChatType.Crafting,
ChatType.Gathering,
ChatType.PeriodicRecruitmentNotification,
ChatType.Sign,
ChatType.RandomNumber,
ChatType.Orchestrion,
ChatType.MessageBook,
ChatType.Alarm,
ChatType.GlamourNotifications
])
// Note: ExtraChat linkshells are handled separately in the tab settings
// UI.
];
(
Language.Options_Tabs_ChannelTypes_Special,
[ChatType.Debug, ChatType.Urgent, ChatType.Notice]
),
(
Language.Options_Tabs_ChannelTypes_Chat,
[
ChatType.Say,
ChatType.Yell,
ChatType.Shout,
ChatType.TellIncoming,
ChatType.TellOutgoing,
ChatType.Party,
ChatType.CrossParty,
ChatType.Alliance,
ChatType.FreeCompany,
ChatType.PvpTeam,
ChatType.CrossLinkshell1,
ChatType.CrossLinkshell2,
ChatType.CrossLinkshell3,
ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5,
ChatType.CrossLinkshell6,
ChatType.CrossLinkshell7,
ChatType.CrossLinkshell8,
ChatType.Linkshell1,
ChatType.Linkshell2,
ChatType.Linkshell3,
ChatType.Linkshell4,
ChatType.Linkshell5,
ChatType.Linkshell6,
ChatType.Linkshell7,
ChatType.Linkshell8,
ChatType.NoviceNetwork,
ChatType.StandardEmote,
ChatType.CustomEmote,
]
),
(
Language.Options_Tabs_ChannelTypes_Battle,
[
ChatType.Damage,
ChatType.Miss,
ChatType.Action,
ChatType.Item,
ChatType.Healing,
ChatType.GainBuff,
ChatType.LoseBuff,
ChatType.GainDebuff,
ChatType.LoseDebuff,
]
),
(
Language.Options_Tabs_ChannelTypes_Announcements,
[
ChatType.System,
ChatType.BattleSystem,
ChatType.GatheringSystem,
ChatType.Error,
ChatType.Echo,
ChatType.NoviceNetworkSystem,
ChatType.FreeCompanyAnnouncement,
ChatType.PvpTeamAnnouncement,
ChatType.FreeCompanyLoginLogout,
ChatType.PvpTeamLoginLogout,
ChatType.RetainerSale,
ChatType.NpcDialogue,
ChatType.NpcAnnouncement,
ChatType.LootNotice,
ChatType.Progress,
ChatType.LootRoll,
ChatType.Crafting,
ChatType.Gathering,
ChatType.PeriodicRecruitmentNotification,
ChatType.Sign,
ChatType.RandomNumber,
ChatType.Orchestrion,
ChatType.MessageBook,
ChatType.Alarm,
ChatType.GlamourNotifications,
]
),
// Note: ExtraChat linkshells are handled separately in the tab settings
// UI.
];
internal static string Name(this ChatType type)
{
@@ -143,7 +149,8 @@ internal static class ChatTypeExt
ChatType.FreeCompanyAnnouncement => Language.ChatType_FreeCompanyAnnouncement,
ChatType.FreeCompanyLoginLogout => Language.ChatType_FreeCompanyLoginLogout,
ChatType.RetainerSale => Language.ChatType_RetainerSale,
ChatType.PeriodicRecruitmentNotification => Language.ChatType_PeriodicRecruitmentNotification,
ChatType.PeriodicRecruitmentNotification =>
Language.ChatType_PeriodicRecruitmentNotification,
ChatType.Sign => Language.ChatType_Sign,
ChatType.RandomNumber => Language.ChatType_RandomNumber,
ChatType.NoviceNetworkSystem => Language.ChatType_NoviceNetworkSystem,
@@ -306,181 +313,187 @@ internal static class ChatTypeExt
}
}
internal static InputChannel? ToInputChannel(this ChatType type) => type switch
{
ChatType.TellOutgoing => InputChannel.Tell,
ChatType.Say => InputChannel.Say,
ChatType.Party => InputChannel.Party,
ChatType.Alliance => InputChannel.Alliance,
ChatType.Yell => InputChannel.Yell,
ChatType.Shout => InputChannel.Shout,
ChatType.FreeCompany => InputChannel.FreeCompany,
ChatType.PvpTeam => InputChannel.PvpTeam,
ChatType.NoviceNetwork => InputChannel.NoviceNetwork,
ChatType.CrossLinkshell1 => InputChannel.CrossLinkshell1,
ChatType.CrossLinkshell2 => InputChannel.CrossLinkshell2,
ChatType.CrossLinkshell3 => InputChannel.CrossLinkshell3,
ChatType.CrossLinkshell4 => InputChannel.CrossLinkshell4,
ChatType.CrossLinkshell5 => InputChannel.CrossLinkshell5,
ChatType.CrossLinkshell6 => InputChannel.CrossLinkshell6,
ChatType.CrossLinkshell7 => InputChannel.CrossLinkshell7,
ChatType.CrossLinkshell8 => InputChannel.CrossLinkshell8,
ChatType.Linkshell1 => InputChannel.Linkshell1,
ChatType.Linkshell2 => InputChannel.Linkshell2,
ChatType.Linkshell3 => InputChannel.Linkshell3,
ChatType.Linkshell4 => InputChannel.Linkshell4,
ChatType.Linkshell5 => InputChannel.Linkshell5,
ChatType.Linkshell6 => InputChannel.Linkshell6,
ChatType.Linkshell7 => InputChannel.Linkshell7,
ChatType.Linkshell8 => InputChannel.Linkshell8,
_ => null,
};
internal static InputChannel? ToInputChannel(this ChatType type) =>
type switch
{
ChatType.TellOutgoing => InputChannel.Tell,
ChatType.Say => InputChannel.Say,
ChatType.Party => InputChannel.Party,
ChatType.Alliance => InputChannel.Alliance,
ChatType.Yell => InputChannel.Yell,
ChatType.Shout => InputChannel.Shout,
ChatType.FreeCompany => InputChannel.FreeCompany,
ChatType.PvpTeam => InputChannel.PvpTeam,
ChatType.NoviceNetwork => InputChannel.NoviceNetwork,
ChatType.CrossLinkshell1 => InputChannel.CrossLinkshell1,
ChatType.CrossLinkshell2 => InputChannel.CrossLinkshell2,
ChatType.CrossLinkshell3 => InputChannel.CrossLinkshell3,
ChatType.CrossLinkshell4 => InputChannel.CrossLinkshell4,
ChatType.CrossLinkshell5 => InputChannel.CrossLinkshell5,
ChatType.CrossLinkshell6 => InputChannel.CrossLinkshell6,
ChatType.CrossLinkshell7 => InputChannel.CrossLinkshell7,
ChatType.CrossLinkshell8 => InputChannel.CrossLinkshell8,
ChatType.Linkshell1 => InputChannel.Linkshell1,
ChatType.Linkshell2 => InputChannel.Linkshell2,
ChatType.Linkshell3 => InputChannel.Linkshell3,
ChatType.Linkshell4 => InputChannel.Linkshell4,
ChatType.Linkshell5 => InputChannel.Linkshell5,
ChatType.Linkshell6 => InputChannel.Linkshell6,
ChatType.Linkshell7 => InputChannel.Linkshell7,
ChatType.Linkshell8 => InputChannel.Linkshell8,
_ => null,
};
internal static bool IsGm(this ChatType type) => type switch
{
ChatType.GmTell => true,
ChatType.GmSay => true,
ChatType.GmShout => true,
ChatType.GmYell => true,
ChatType.GmParty => true,
ChatType.GmFreeCompany => true,
ChatType.GmLinkshell1 => true,
ChatType.GmLinkshell2 => true,
ChatType.GmLinkshell3 => true,
ChatType.GmLinkshell4 => true,
ChatType.GmLinkshell5 => true,
ChatType.GmLinkshell6 => true,
ChatType.GmLinkshell7 => true,
ChatType.GmLinkshell8 => true,
ChatType.GmNoviceNetwork => true,
_ => false,
};
internal static bool IsGm(this ChatType type) =>
type switch
{
ChatType.GmTell => true,
ChatType.GmSay => true,
ChatType.GmShout => true,
ChatType.GmYell => true,
ChatType.GmParty => true,
ChatType.GmFreeCompany => true,
ChatType.GmLinkshell1 => true,
ChatType.GmLinkshell2 => true,
ChatType.GmLinkshell3 => true,
ChatType.GmLinkshell4 => true,
ChatType.GmLinkshell5 => true,
ChatType.GmLinkshell6 => true,
ChatType.GmLinkshell7 => true,
ChatType.GmLinkshell8 => true,
ChatType.GmNoviceNetwork => true,
_ => false,
};
internal static bool IsExtraChatLinkshell(this ChatType type) => type switch
{
ChatType.ExtraChatLinkshell1 => true,
ChatType.ExtraChatLinkshell2 => true,
ChatType.ExtraChatLinkshell3 => true,
ChatType.ExtraChatLinkshell4 => true,
ChatType.ExtraChatLinkshell5 => true,
ChatType.ExtraChatLinkshell6 => true,
ChatType.ExtraChatLinkshell7 => true,
ChatType.ExtraChatLinkshell8 => true,
_ => false,
};
internal static bool IsExtraChatLinkshell(this ChatType type) =>
type switch
{
ChatType.ExtraChatLinkshell1 => true,
ChatType.ExtraChatLinkshell2 => true,
ChatType.ExtraChatLinkshell3 => true,
ChatType.ExtraChatLinkshell4 => true,
ChatType.ExtraChatLinkshell5 => true,
ChatType.ExtraChatLinkshell6 => true,
ChatType.ExtraChatLinkshell7 => true,
ChatType.ExtraChatLinkshell8 => true,
_ => false,
};
public static UiConfigOption ToConfigEntry(this ChatType type) => type switch
{
ChatType.Say => UiConfigOption.ColorSay,
ChatType.Shout => UiConfigOption.ColorShout,
ChatType.TellOutgoing => UiConfigOption.ColorTell,
ChatType.Party => UiConfigOption.ColorParty,
ChatType.Linkshell1 => UiConfigOption.ColorLS1,
ChatType.Linkshell2 => UiConfigOption.ColorLS2,
ChatType.Linkshell3 => UiConfigOption.ColorLS3,
ChatType.Linkshell4 => UiConfigOption.ColorLS4,
ChatType.Linkshell5 => UiConfigOption.ColorLS5,
ChatType.Linkshell6 => UiConfigOption.ColorLS6,
ChatType.Linkshell7 => UiConfigOption.ColorLS7,
ChatType.Linkshell8 => UiConfigOption.ColorLS8,
ChatType.FreeCompany => UiConfigOption.ColorFCompany,
ChatType.NoviceNetwork => UiConfigOption.ColorBeginner,
ChatType.CustomEmote => UiConfigOption.ColorEmoteUser,
ChatType.StandardEmote => UiConfigOption.ColorEmote,
ChatType.Yell => UiConfigOption.ColorYell,
ChatType.GainBuff => UiConfigOption.ColorBuffGive,
ChatType.GainDebuff => UiConfigOption.ColorDebuffGive,
ChatType.System => UiConfigOption.ColorSysMsg,
ChatType.NpcDialogue => UiConfigOption.ColorNpcSay,
ChatType.LootRoll => UiConfigOption.ColorLoot,
ChatType.FreeCompanyAnnouncement => UiConfigOption.ColorFCAnnounce,
ChatType.PvpTeamAnnouncement => UiConfigOption.ColorPvPGroupAnnounce,
_ => UiConfigOption.ColorSay,
};
public static UiConfigOption ToConfigEntry(this ChatType type) =>
type switch
{
ChatType.Say => UiConfigOption.ColorSay,
ChatType.Shout => UiConfigOption.ColorShout,
ChatType.TellOutgoing => UiConfigOption.ColorTell,
ChatType.Party => UiConfigOption.ColorParty,
ChatType.Linkshell1 => UiConfigOption.ColorLS1,
ChatType.Linkshell2 => UiConfigOption.ColorLS2,
ChatType.Linkshell3 => UiConfigOption.ColorLS3,
ChatType.Linkshell4 => UiConfigOption.ColorLS4,
ChatType.Linkshell5 => UiConfigOption.ColorLS5,
ChatType.Linkshell6 => UiConfigOption.ColorLS6,
ChatType.Linkshell7 => UiConfigOption.ColorLS7,
ChatType.Linkshell8 => UiConfigOption.ColorLS8,
ChatType.FreeCompany => UiConfigOption.ColorFCompany,
ChatType.NoviceNetwork => UiConfigOption.ColorBeginner,
ChatType.CustomEmote => UiConfigOption.ColorEmoteUser,
ChatType.StandardEmote => UiConfigOption.ColorEmote,
ChatType.Yell => UiConfigOption.ColorYell,
ChatType.GainBuff => UiConfigOption.ColorBuffGive,
ChatType.GainDebuff => UiConfigOption.ColorDebuffGive,
ChatType.System => UiConfigOption.ColorSysMsg,
ChatType.NpcDialogue => UiConfigOption.ColorNpcSay,
ChatType.LootRoll => UiConfigOption.ColorLoot,
ChatType.FreeCompanyAnnouncement => UiConfigOption.ColorFCAnnounce,
ChatType.PvpTeamAnnouncement => UiConfigOption.ColorPvPGroupAnnounce,
_ => UiConfigOption.ColorSay,
};
internal static bool HasSource(this ChatType type) => type switch
{
// Battle
ChatType.Damage => true,
ChatType.Miss => true,
ChatType.Action => true,
ChatType.Item => true,
ChatType.Healing => true,
ChatType.GainBuff => true,
ChatType.LoseBuff => true,
ChatType.GainDebuff => true,
ChatType.LoseDebuff => true,
internal static bool HasSource(this ChatType type) =>
type switch
{
// Battle
ChatType.Damage => true,
ChatType.Miss => true,
ChatType.Action => true,
ChatType.Item => true,
ChatType.Healing => true,
ChatType.GainBuff => true,
ChatType.LoseBuff => true,
ChatType.GainDebuff => true,
ChatType.LoseDebuff => true,
// Announcements
ChatType.System => true,
ChatType.BattleSystem => true,
ChatType.Error => true,
ChatType.LootNotice => true,
ChatType.Progress => true,
ChatType.LootRoll => true,
ChatType.Crafting => true,
ChatType.Gathering => true,
ChatType.FreeCompanyLoginLogout => true,
ChatType.PvpTeamLoginLogout => true,
_ => false,
};
// Announcements
ChatType.System => true,
ChatType.BattleSystem => true,
ChatType.Error => true,
ChatType.LootNotice => true,
ChatType.Progress => true,
ChatType.LootRoll => true,
ChatType.Crafting => true,
ChatType.Gathering => true,
ChatType.FreeCompanyLoginLogout => true,
ChatType.PvpTeamLoginLogout => true,
_ => false,
};
internal static ChatType Parent(this ChatType type) => type switch
{
ChatType.Say => ChatType.Say,
ChatType.GmSay => ChatType.Say,
ChatType.Shout => ChatType.Shout,
ChatType.GmShout => ChatType.Shout,
ChatType.TellOutgoing => ChatType.TellOutgoing,
ChatType.TellIncoming => ChatType.TellOutgoing,
ChatType.GmTell => ChatType.TellOutgoing,
ChatType.Party => ChatType.Party,
ChatType.CrossParty => ChatType.Party,
ChatType.GmParty => ChatType.Party,
ChatType.Linkshell1 => ChatType.Linkshell1,
ChatType.GmLinkshell1 => ChatType.Linkshell1,
ChatType.Linkshell2 => ChatType.Linkshell2,
ChatType.GmLinkshell2 => ChatType.Linkshell2,
ChatType.Linkshell3 => ChatType.Linkshell3,
ChatType.GmLinkshell3 => ChatType.Linkshell3,
ChatType.Linkshell4 => ChatType.Linkshell4,
ChatType.GmLinkshell4 => ChatType.Linkshell4,
ChatType.Linkshell5 => ChatType.Linkshell5,
ChatType.GmLinkshell5 => ChatType.Linkshell5,
ChatType.Linkshell6 => ChatType.Linkshell6,
ChatType.GmLinkshell6 => ChatType.Linkshell6,
ChatType.Linkshell7 => ChatType.Linkshell7,
ChatType.GmLinkshell7 => ChatType.Linkshell7,
ChatType.Linkshell8 => ChatType.Linkshell8,
ChatType.GmLinkshell8 => ChatType.Linkshell8,
ChatType.FreeCompany => ChatType.FreeCompany,
ChatType.GmFreeCompany => ChatType.FreeCompany,
ChatType.NoviceNetwork => ChatType.NoviceNetwork,
ChatType.GmNoviceNetwork => ChatType.NoviceNetwork,
ChatType.CustomEmote => ChatType.CustomEmote,
ChatType.StandardEmote => ChatType.StandardEmote,
ChatType.Yell => ChatType.Yell,
ChatType.GmYell => ChatType.Yell,
ChatType.GainBuff => ChatType.GainBuff,
ChatType.LoseBuff => ChatType.GainBuff,
ChatType.GainDebuff => ChatType.GainDebuff,
ChatType.LoseDebuff => ChatType.GainDebuff,
ChatType.System => ChatType.System,
ChatType.Alarm => ChatType.System,
ChatType.GlamourNotifications => ChatType.System,
ChatType.RetainerSale => ChatType.System,
ChatType.PeriodicRecruitmentNotification => ChatType.System,
ChatType.Sign => ChatType.System,
ChatType.Orchestrion => ChatType.System,
ChatType.MessageBook => ChatType.System,
ChatType.NpcDialogue => ChatType.NpcDialogue,
ChatType.NpcAnnouncement => ChatType.NpcDialogue,
ChatType.LootRoll => ChatType.LootRoll,
ChatType.RandomNumber => ChatType.LootRoll,
ChatType.FreeCompanyAnnouncement => ChatType.FreeCompanyAnnouncement,
ChatType.FreeCompanyLoginLogout => ChatType.FreeCompanyAnnouncement,
ChatType.PvpTeamAnnouncement => ChatType.PvpTeamAnnouncement,
ChatType.PvpTeamLoginLogout => ChatType.PvpTeamAnnouncement,
_ => type,
};
internal static ChatType Parent(this ChatType type) =>
type switch
{
ChatType.Say => ChatType.Say,
ChatType.GmSay => ChatType.Say,
ChatType.Shout => ChatType.Shout,
ChatType.GmShout => ChatType.Shout,
ChatType.TellOutgoing => ChatType.TellOutgoing,
ChatType.TellIncoming => ChatType.TellOutgoing,
ChatType.GmTell => ChatType.TellOutgoing,
ChatType.Party => ChatType.Party,
ChatType.CrossParty => ChatType.Party,
ChatType.GmParty => ChatType.Party,
ChatType.Linkshell1 => ChatType.Linkshell1,
ChatType.GmLinkshell1 => ChatType.Linkshell1,
ChatType.Linkshell2 => ChatType.Linkshell2,
ChatType.GmLinkshell2 => ChatType.Linkshell2,
ChatType.Linkshell3 => ChatType.Linkshell3,
ChatType.GmLinkshell3 => ChatType.Linkshell3,
ChatType.Linkshell4 => ChatType.Linkshell4,
ChatType.GmLinkshell4 => ChatType.Linkshell4,
ChatType.Linkshell5 => ChatType.Linkshell5,
ChatType.GmLinkshell5 => ChatType.Linkshell5,
ChatType.Linkshell6 => ChatType.Linkshell6,
ChatType.GmLinkshell6 => ChatType.Linkshell6,
ChatType.Linkshell7 => ChatType.Linkshell7,
ChatType.GmLinkshell7 => ChatType.Linkshell7,
ChatType.Linkshell8 => ChatType.Linkshell8,
ChatType.GmLinkshell8 => ChatType.Linkshell8,
ChatType.FreeCompany => ChatType.FreeCompany,
ChatType.GmFreeCompany => ChatType.FreeCompany,
ChatType.NoviceNetwork => ChatType.NoviceNetwork,
ChatType.GmNoviceNetwork => ChatType.NoviceNetwork,
ChatType.CustomEmote => ChatType.CustomEmote,
ChatType.StandardEmote => ChatType.StandardEmote,
ChatType.Yell => ChatType.Yell,
ChatType.GmYell => ChatType.Yell,
ChatType.GainBuff => ChatType.GainBuff,
ChatType.LoseBuff => ChatType.GainBuff,
ChatType.GainDebuff => ChatType.GainDebuff,
ChatType.LoseDebuff => ChatType.GainDebuff,
ChatType.System => ChatType.System,
ChatType.Alarm => ChatType.System,
ChatType.GlamourNotifications => ChatType.System,
ChatType.RetainerSale => ChatType.System,
ChatType.PeriodicRecruitmentNotification => ChatType.System,
ChatType.Sign => ChatType.System,
ChatType.Orchestrion => ChatType.System,
ChatType.MessageBook => ChatType.System,
ChatType.NpcDialogue => ChatType.NpcDialogue,
ChatType.NpcAnnouncement => ChatType.NpcDialogue,
ChatType.LootRoll => ChatType.LootRoll,
ChatType.RandomNumber => ChatType.LootRoll,
ChatType.FreeCompanyAnnouncement => ChatType.FreeCompanyAnnouncement,
ChatType.FreeCompanyLoginLogout => ChatType.FreeCompanyAnnouncement,
ChatType.PvpTeamAnnouncement => ChatType.PvpTeamAnnouncement,
ChatType.PvpTeamLoginLogout => ChatType.PvpTeamAnnouncement,
_ => type,
};
}
+153 -145
View File
@@ -4,111 +4,114 @@ namespace HellionChat.Code;
internal static class InputChannelExt
{
internal static ChatType ToChatType(this InputChannel input) => input switch
{
InputChannel.Tell => ChatType.TellOutgoing,
InputChannel.Say => ChatType.Say,
InputChannel.Party => ChatType.Party,
InputChannel.Alliance => ChatType.Alliance,
InputChannel.Yell => ChatType.Yell,
InputChannel.Shout => ChatType.Shout,
InputChannel.FreeCompany => ChatType.FreeCompany,
InputChannel.PvpTeam => ChatType.PvpTeam,
InputChannel.NoviceNetwork => ChatType.NoviceNetwork,
InputChannel.CrossLinkshell1 => ChatType.CrossLinkshell1,
InputChannel.CrossLinkshell2 => ChatType.CrossLinkshell2,
InputChannel.CrossLinkshell3 => ChatType.CrossLinkshell3,
InputChannel.CrossLinkshell4 => ChatType.CrossLinkshell4,
InputChannel.CrossLinkshell5 => ChatType.CrossLinkshell5,
InputChannel.CrossLinkshell6 => ChatType.CrossLinkshell6,
InputChannel.CrossLinkshell7 => ChatType.CrossLinkshell7,
InputChannel.CrossLinkshell8 => ChatType.CrossLinkshell8,
InputChannel.Linkshell1 => ChatType.Linkshell1,
InputChannel.Linkshell2 => ChatType.Linkshell2,
InputChannel.Linkshell3 => ChatType.Linkshell3,
InputChannel.Linkshell4 => ChatType.Linkshell4,
InputChannel.Linkshell5 => ChatType.Linkshell5,
InputChannel.Linkshell6 => ChatType.Linkshell6,
InputChannel.Linkshell7 => ChatType.Linkshell7,
InputChannel.Linkshell8 => ChatType.Linkshell8,
InputChannel.ExtraChatLinkshell1 => ChatType.ExtraChatLinkshell1,
InputChannel.ExtraChatLinkshell2 => ChatType.ExtraChatLinkshell2,
InputChannel.ExtraChatLinkshell3 => ChatType.ExtraChatLinkshell3,
InputChannel.ExtraChatLinkshell4 => ChatType.ExtraChatLinkshell4,
InputChannel.ExtraChatLinkshell5 => ChatType.ExtraChatLinkshell5,
InputChannel.ExtraChatLinkshell6 => ChatType.ExtraChatLinkshell6,
InputChannel.ExtraChatLinkshell7 => ChatType.ExtraChatLinkshell7,
InputChannel.ExtraChatLinkshell8 => ChatType.ExtraChatLinkshell8,
InputChannel.Invalid => ChatType.Echo,
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null),
};
internal static ChatType ToChatType(this InputChannel input) =>
input switch
{
InputChannel.Tell => ChatType.TellOutgoing,
InputChannel.Say => ChatType.Say,
InputChannel.Party => ChatType.Party,
InputChannel.Alliance => ChatType.Alliance,
InputChannel.Yell => ChatType.Yell,
InputChannel.Shout => ChatType.Shout,
InputChannel.FreeCompany => ChatType.FreeCompany,
InputChannel.PvpTeam => ChatType.PvpTeam,
InputChannel.NoviceNetwork => ChatType.NoviceNetwork,
InputChannel.CrossLinkshell1 => ChatType.CrossLinkshell1,
InputChannel.CrossLinkshell2 => ChatType.CrossLinkshell2,
InputChannel.CrossLinkshell3 => ChatType.CrossLinkshell3,
InputChannel.CrossLinkshell4 => ChatType.CrossLinkshell4,
InputChannel.CrossLinkshell5 => ChatType.CrossLinkshell5,
InputChannel.CrossLinkshell6 => ChatType.CrossLinkshell6,
InputChannel.CrossLinkshell7 => ChatType.CrossLinkshell7,
InputChannel.CrossLinkshell8 => ChatType.CrossLinkshell8,
InputChannel.Linkshell1 => ChatType.Linkshell1,
InputChannel.Linkshell2 => ChatType.Linkshell2,
InputChannel.Linkshell3 => ChatType.Linkshell3,
InputChannel.Linkshell4 => ChatType.Linkshell4,
InputChannel.Linkshell5 => ChatType.Linkshell5,
InputChannel.Linkshell6 => ChatType.Linkshell6,
InputChannel.Linkshell7 => ChatType.Linkshell7,
InputChannel.Linkshell8 => ChatType.Linkshell8,
InputChannel.ExtraChatLinkshell1 => ChatType.ExtraChatLinkshell1,
InputChannel.ExtraChatLinkshell2 => ChatType.ExtraChatLinkshell2,
InputChannel.ExtraChatLinkshell3 => ChatType.ExtraChatLinkshell3,
InputChannel.ExtraChatLinkshell4 => ChatType.ExtraChatLinkshell4,
InputChannel.ExtraChatLinkshell5 => ChatType.ExtraChatLinkshell5,
InputChannel.ExtraChatLinkshell6 => ChatType.ExtraChatLinkshell6,
InputChannel.ExtraChatLinkshell7 => ChatType.ExtraChatLinkshell7,
InputChannel.ExtraChatLinkshell8 => ChatType.ExtraChatLinkshell8,
InputChannel.Invalid => ChatType.Echo,
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null),
};
public static uint LinkshellIndex(this InputChannel channel) => channel switch
{
InputChannel.Linkshell1 => 0,
InputChannel.Linkshell2 => 1,
InputChannel.Linkshell3 => 2,
InputChannel.Linkshell4 => 3,
InputChannel.Linkshell5 => 4,
InputChannel.Linkshell6 => 5,
InputChannel.Linkshell7 => 6,
InputChannel.Linkshell8 => 7,
InputChannel.CrossLinkshell1 => 0,
InputChannel.CrossLinkshell2 => 1,
InputChannel.CrossLinkshell3 => 2,
InputChannel.CrossLinkshell4 => 3,
InputChannel.CrossLinkshell5 => 4,
InputChannel.CrossLinkshell6 => 5,
InputChannel.CrossLinkshell7 => 6,
InputChannel.CrossLinkshell8 => 7,
InputChannel.ExtraChatLinkshell1 => 0,
InputChannel.ExtraChatLinkshell2 => 1,
InputChannel.ExtraChatLinkshell3 => 2,
InputChannel.ExtraChatLinkshell4 => 3,
InputChannel.ExtraChatLinkshell5 => 4,
InputChannel.ExtraChatLinkshell6 => 5,
InputChannel.ExtraChatLinkshell7 => 6,
InputChannel.ExtraChatLinkshell8 => 7,
_ => uint.MaxValue,
};
public static uint LinkshellIndex(this InputChannel channel) =>
channel switch
{
InputChannel.Linkshell1 => 0,
InputChannel.Linkshell2 => 1,
InputChannel.Linkshell3 => 2,
InputChannel.Linkshell4 => 3,
InputChannel.Linkshell5 => 4,
InputChannel.Linkshell6 => 5,
InputChannel.Linkshell7 => 6,
InputChannel.Linkshell8 => 7,
InputChannel.CrossLinkshell1 => 0,
InputChannel.CrossLinkshell2 => 1,
InputChannel.CrossLinkshell3 => 2,
InputChannel.CrossLinkshell4 => 3,
InputChannel.CrossLinkshell5 => 4,
InputChannel.CrossLinkshell6 => 5,
InputChannel.CrossLinkshell7 => 6,
InputChannel.CrossLinkshell8 => 7,
InputChannel.ExtraChatLinkshell1 => 0,
InputChannel.ExtraChatLinkshell2 => 1,
InputChannel.ExtraChatLinkshell3 => 2,
InputChannel.ExtraChatLinkshell4 => 3,
InputChannel.ExtraChatLinkshell5 => 4,
InputChannel.ExtraChatLinkshell6 => 5,
InputChannel.ExtraChatLinkshell7 => 6,
InputChannel.ExtraChatLinkshell8 => 7,
_ => uint.MaxValue,
};
public static string Prefix(this InputChannel channel) => channel switch
{
InputChannel.Tell => "/t",
InputChannel.Say => "/s",
InputChannel.Party => "/p",
InputChannel.Alliance => "/a",
InputChannel.Yell => "/y",
InputChannel.Shout => "/sh",
InputChannel.FreeCompany => "/fc",
InputChannel.PvpTeam => "/pt",
InputChannel.NoviceNetwork => "/b",
InputChannel.CrossLinkshell1 => "/cwl1",
InputChannel.CrossLinkshell2 => "/cwl2",
InputChannel.CrossLinkshell3 => "/cwl3",
InputChannel.CrossLinkshell4 => "/cwl4",
InputChannel.CrossLinkshell5 => "/cwl5",
InputChannel.CrossLinkshell6 => "/cwl6",
InputChannel.CrossLinkshell7 => "/cwl7",
InputChannel.CrossLinkshell8 => "/cwl8",
InputChannel.Linkshell1 => "/l1",
InputChannel.Linkshell2 => "/l2",
InputChannel.Linkshell3 => "/l3",
InputChannel.Linkshell4 => "/l4",
InputChannel.Linkshell5 => "/l5",
InputChannel.Linkshell6 => "/l6",
InputChannel.Linkshell7 => "/l7",
InputChannel.Linkshell8 => "/l8",
InputChannel.ExtraChatLinkshell1 => "/ecl1",
InputChannel.ExtraChatLinkshell2 => "/ecl2",
InputChannel.ExtraChatLinkshell3 => "/ecl3",
InputChannel.ExtraChatLinkshell4 => "/ecl4",
InputChannel.ExtraChatLinkshell5 => "/ecl5",
InputChannel.ExtraChatLinkshell6 => "/ecl6",
InputChannel.ExtraChatLinkshell7 => "/ecl7",
InputChannel.ExtraChatLinkshell8 => "/ecl8",
_ => "/e",
};
public static string Prefix(this InputChannel channel) =>
channel switch
{
InputChannel.Tell => "/t",
InputChannel.Say => "/s",
InputChannel.Party => "/p",
InputChannel.Alliance => "/a",
InputChannel.Yell => "/y",
InputChannel.Shout => "/sh",
InputChannel.FreeCompany => "/fc",
InputChannel.PvpTeam => "/pt",
InputChannel.NoviceNetwork => "/b",
InputChannel.CrossLinkshell1 => "/cwl1",
InputChannel.CrossLinkshell2 => "/cwl2",
InputChannel.CrossLinkshell3 => "/cwl3",
InputChannel.CrossLinkshell4 => "/cwl4",
InputChannel.CrossLinkshell5 => "/cwl5",
InputChannel.CrossLinkshell6 => "/cwl6",
InputChannel.CrossLinkshell7 => "/cwl7",
InputChannel.CrossLinkshell8 => "/cwl8",
InputChannel.Linkshell1 => "/l1",
InputChannel.Linkshell2 => "/l2",
InputChannel.Linkshell3 => "/l3",
InputChannel.Linkshell4 => "/l4",
InputChannel.Linkshell5 => "/l5",
InputChannel.Linkshell6 => "/l6",
InputChannel.Linkshell7 => "/l7",
InputChannel.Linkshell8 => "/l8",
InputChannel.ExtraChatLinkshell1 => "/ecl1",
InputChannel.ExtraChatLinkshell2 => "/ecl2",
InputChannel.ExtraChatLinkshell3 => "/ecl3",
InputChannel.ExtraChatLinkshell4 => "/ecl4",
InputChannel.ExtraChatLinkshell5 => "/ecl5",
InputChannel.ExtraChatLinkshell6 => "/ecl6",
InputChannel.ExtraChatLinkshell7 => "/ecl7",
InputChannel.ExtraChatLinkshell8 => "/ecl8",
_ => "/e",
};
public static IEnumerable<TextCommand>? TextCommands(this InputChannel channel)
{
@@ -145,51 +148,56 @@ internal static class InputChannelExt
if (ids.Length == 0)
return null;
return ids.Where(id => Sheets.TextCommandSheet.HasRow(id)).Select(id => Sheets.TextCommandSheet.GetRow(id));
return ids.Where(id => Sheets.TextCommandSheet.HasRow(id))
.Select(id => Sheets.TextCommandSheet.GetRow(id));
}
internal static bool IsLinkshell(this InputChannel channel) => channel switch
{
InputChannel.Linkshell1 => true,
InputChannel.Linkshell2 => true,
InputChannel.Linkshell3 => true,
InputChannel.Linkshell4 => true,
InputChannel.Linkshell5 => true,
InputChannel.Linkshell6 => true,
InputChannel.Linkshell7 => true,
InputChannel.Linkshell8 => true,
_ => false,
};
internal static bool IsLinkshell(this InputChannel channel) =>
channel switch
{
InputChannel.Linkshell1 => true,
InputChannel.Linkshell2 => true,
InputChannel.Linkshell3 => true,
InputChannel.Linkshell4 => true,
InputChannel.Linkshell5 => true,
InputChannel.Linkshell6 => true,
InputChannel.Linkshell7 => true,
InputChannel.Linkshell8 => true,
_ => false,
};
internal static bool IsCrossLinkshell(this InputChannel channel) => channel switch
{
InputChannel.CrossLinkshell1 => true,
InputChannel.CrossLinkshell2 => true,
InputChannel.CrossLinkshell3 => true,
InputChannel.CrossLinkshell4 => true,
InputChannel.CrossLinkshell5 => true,
InputChannel.CrossLinkshell6 => true,
InputChannel.CrossLinkshell7 => true,
InputChannel.CrossLinkshell8 => true,
_ => false,
};
internal static bool IsCrossLinkshell(this InputChannel channel) =>
channel switch
{
InputChannel.CrossLinkshell1 => true,
InputChannel.CrossLinkshell2 => true,
InputChannel.CrossLinkshell3 => true,
InputChannel.CrossLinkshell4 => true,
InputChannel.CrossLinkshell5 => true,
InputChannel.CrossLinkshell6 => true,
InputChannel.CrossLinkshell7 => true,
InputChannel.CrossLinkshell8 => true,
_ => false,
};
internal static bool IsExtraChatLinkshell(this InputChannel channel) => channel switch
{
InputChannel.ExtraChatLinkshell1 => true,
InputChannel.ExtraChatLinkshell2 => true,
InputChannel.ExtraChatLinkshell3 => true,
InputChannel.ExtraChatLinkshell4 => true,
InputChannel.ExtraChatLinkshell5 => true,
InputChannel.ExtraChatLinkshell6 => true,
InputChannel.ExtraChatLinkshell7 => true,
InputChannel.ExtraChatLinkshell8 => true,
_ => false,
};
internal static bool IsExtraChatLinkshell(this InputChannel channel) =>
channel switch
{
InputChannel.ExtraChatLinkshell1 => true,
InputChannel.ExtraChatLinkshell2 => true,
InputChannel.ExtraChatLinkshell3 => true,
InputChannel.ExtraChatLinkshell4 => true,
InputChannel.ExtraChatLinkshell5 => true,
InputChannel.ExtraChatLinkshell6 => true,
InputChannel.ExtraChatLinkshell7 => true,
InputChannel.ExtraChatLinkshell8 => true,
_ => false,
};
internal static bool IsValid(this InputChannel channel) => channel switch
{
InputChannel.Invalid => false,
_ => true,
};
internal static bool IsValid(this InputChannel channel) =>
channel switch
{
InputChannel.Invalid => false,
_ => true,
};
}
+13 -6
View File
@@ -16,15 +16,22 @@ internal sealed class Commands : IDisposable
{
foreach (var wrapper in Registered.Values)
{
Plugin.CommandManager.AddHandler(wrapper.Name, new CommandInfo(Invoke)
{
HelpMessage = wrapper.Description ?? string.Empty,
ShowInHelp = wrapper.ShowInHelp,
});
Plugin.CommandManager.AddHandler(
wrapper.Name,
new CommandInfo(Invoke)
{
HelpMessage = wrapper.Description ?? string.Empty,
ShowInHelp = wrapper.ShowInHelp,
}
);
}
}
internal CommandWrapper Register(string name, string? description = null, bool? showInHelp = null)
internal CommandWrapper Register(
string name,
string? description = null,
bool? showInHelp = null
)
{
if (Registered.TryGetValue(name, out var wrapper))
{
+215 -147
View File
@@ -1,14 +1,14 @@
using System.Collections;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud;
using Dalamud.Bindings.ImGui;
using Dalamud.Configuration;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat;
@@ -27,7 +27,7 @@ public class ConfigKeyBind
modString += Language.Keybind_Modifier_Shift + " + ";
if (Modifier.HasFlag(ModifierFlag.Alt))
modString += Language.Keybind_Modifier_Alt + " + ";
return modString+Key.GetFancyName();
return modString + Key.GetFancyName();
}
}
@@ -45,10 +45,10 @@ public class Configuration : IPluginConfiguration
// HellionThemeWindowOpacity beim Bump v13 → v14.
public float WindowOpacity = 0.85f;
// v1.1.0 — Felder für künftige UI-Toggles (v1.2.0 / v1.3.0). Werden
// vorab angelegt, damit später keine Migration nötig ist.
public bool ReduceMotion;
// v1.2.1 — Default geflippt von false → true. Card-Rows-Layout aus
// v1.2.0 wurde als zu dicht empfunden; Single-Line `[HH:mm] Sender:
// Text` ist besser lesbar und platzsparender. Bestand-User mit aktiv
@@ -60,8 +60,10 @@ public class Configuration : IPluginConfiguration
// Hellion Chat — Privacy filter (DSGVO Art. 25 Privacy by Default).
// Master-switch defaults to true; set false to restore upstream behavior.
public bool PrivacyFilterEnabled = true;
// Empty set means the migration has not run yet — see Plugin.cs v6→v7.
public HashSet<ChatType> PrivacyPersistChannels = [];
// Failsafe for ChatTypes added by future FFXIV patches we don't know about.
public bool PrivacyPersistUnknownChannels;
@@ -103,15 +105,19 @@ public class Configuration : IPluginConfiguration
// /tell spawns a session-only tab dedicated to that conversation
// partner. See spec: Hellion Chat Auto-Tell-Tabs Spec (Obsidian).
public bool EnableAutoTellTabs = true;
// Hard cap on simultaneously open auto tell tabs. Range enforced by the
// settings slider (150). LRU drop favors greeted tabs first.
public int AutoTellTabsLimit = 15;
// When true the sidebar shows only a thin separator before the temp
// tabs; when false a section header "Active Tells (n)" is rendered.
public bool AutoTellTabsCompactDisplay;
// Number of prior tells to preload from the message store when an
// auto tell tab is spawned. Range 0100; 0 disables preload.
public int AutoTellTabsHistoryPreload = 20;
// Show the greeter "marked-as-greeted" toggle button next to each
// temp tab and dim the tab name when set. Off by default because the
// workflow is specific to club-greeter use cases — most users just
@@ -160,6 +166,7 @@ public class Configuration : IPluginConfiguration
public bool HideWhenUiHidden = true;
public bool HideInLoadingScreens;
public bool HideInBattle;
// v1.2.1 — Default geflippt false → true. Hellion-UI im NG+-Menü
// versteckt zu halten ist konsistent mit den anderen Hide-Defaults
// (Cutscenes, Logged-out, UI-Hidden) — UI-out-of-the-way bei Story-
@@ -179,11 +186,13 @@ public class Configuration : IPluginConfiguration
public bool NativeItemTooltips = true;
public bool PrettierTimestamps = true;
public bool MoreCompactPretty;
// v1.2.1 — Default geflippt false → true. Wiederholte Zeitstempel
// innerhalb derselben Minute lesen sich als Rauschen; ein einziger
// Timestamp pro Minute reicht aus um die Konversation zu verorten.
public bool HideSameTimestamps = true;
public bool ShowNoviceNetwork;
// Hellion Chat — vertical sidebar tab layout reads better than the
// horizontal tab strip in the company of Auto-Tell-Tabs (a club
// greeter typically tracks 515 simultaneous conversations). Bestand
@@ -209,11 +218,13 @@ public class Configuration : IPluginConfiguration
public bool CollapseKeepUniqueLinks;
public bool PlaySounds = true;
public bool KeepInputFocus = true;
// v1.2.1 — Default gesenkt 5000 → 2500. 5000 ist auf Mid-Range-
// Hardware bei langen Sessions spürbar langsamer (Card-Layout
// re-Layout pro Frame), 2500 deckt eine typische Stunde Chat ab
// und bleibt smooth. User die mehr brauchen können bis 10000 hoch.
public int MaxLinesToRender = 2_500; // 1-10000
// Default ON to match a German / European 24h locale. The
// ChatLogWindow.cs format-flip in v0.5.1 honours this strictly via
// CultureInfo.InvariantCulture so the result is consistent across
@@ -246,6 +257,7 @@ public class Configuration : IPluginConfiguration
};
public float TooltipOffset;
// v1.2.1 — Default-Chat-Farben sind das Hellion-Brand-Preset. Der
// First-Run-Wizard bietet keine Theme-/Preset-Wahl an, daher kriegen
// neue User die Hellion-Brand-Farben out-of-the-box (Cyan-Familie für
@@ -257,7 +269,9 @@ public class Configuration : IPluginConfiguration
private static Dictionary<ChatType, uint> BuildDefaultChatColours()
{
var defaults = new Dictionary<ChatType, uint>();
foreach (var (channel, colour) in HellionChat.Resources.ChatColourPresets.All["Hellion"].Colours)
foreach (
var (channel, colour) in HellionChat.Resources.ChatColourPresets.All["Hellion"].Colours
)
defaults[channel] = colour;
return defaults;
}
@@ -284,7 +298,10 @@ public class Configuration : IPluginConfiguration
HideWhenInactive = other.HideWhenInactive;
InactivityHideTimeout = other.InactivityHideTimeout;
InactivityHideActiveDuringBattle = other.InactivityHideActiveDuringBattle;
InactivityHideChannelsV2 = other.InactivityHideChannelsV2.ToDictionary(pair => pair.Key, pair => pair.Value);
InactivityHideChannelsV2 = other.InactivityHideChannelsV2.ToDictionary(
pair => pair.Key,
pair => pair.Value
);
InactivityHideExtraChatAll = other.InactivityHideExtraChatAll;
InactivityHideExtraChatChannels = other.InactivityHideExtraChatChannels.ToHashSet();
ShowHideButton = other.ShowHideButton;
@@ -349,32 +366,37 @@ public class Configuration : IPluginConfiguration
// settings get a fresh empty MessageList; deleted tabs lose their
// history (intended).
var liveTempTabs = Tabs.Where(t => t.IsTempTab).ToList();
var livePersistentSession = Tabs
.Where(t => !t.IsTempTab)
var livePersistentSession = Tabs.Where(t => !t.IsTempTab)
.ToDictionary(t => t.Identifier, t => (t.Messages, t.LastSendUnread));
Tabs = other.Tabs.Where(t => !t.IsTempTab).Select(t =>
{
var clone = t.Clone();
if (livePersistentSession.TryGetValue(clone.Identifier, out var live))
Tabs = other
.Tabs.Where(t => !t.IsTempTab)
.Select(t =>
{
clone.Messages = live.Messages;
clone.LastSendUnread = live.LastSendUnread;
}
return clone;
}).ToList();
var clone = t.Clone();
if (livePersistentSession.TryGetValue(clone.Identifier, out var live))
{
clone.Messages = live.Messages;
clone.LastSendUnread = live.LastSendUnread;
}
return clone;
})
.ToList();
Tabs.AddRange(liveTempTabs);
ChatTabForward = other.ChatTabForward;
ChatTabBackward = other.ChatTabBackward;
PrivacyFilterEnabled = other.PrivacyFilterEnabled;
PrivacyPersistChannels = [..other.PrivacyPersistChannels];
PrivacyPersistChannels = [.. other.PrivacyPersistChannels];
PrivacyPersistUnknownChannels = other.PrivacyPersistUnknownChannels;
RetentionEnabled = other.RetentionEnabled;
RetentionDefaultDays = other.RetentionDefaultDays;
RetentionPerChannelDays = other.RetentionPerChannelDays.ToDictionary(p => p.Key, p => p.Value);
RetentionPerChannelDays = other.RetentionPerChannelDays.ToDictionary(
p => p.Key,
p => p.Value
);
RetentionLastRunAt = other.RetentionLastRunAt;
FirstRunCompleted = other.FirstRunCompleted;
@@ -410,21 +432,23 @@ public enum UnreadMode
public static class UnreadModeExt
{
internal static string Name(this UnreadMode mode) => mode switch
{
UnreadMode.All => Language.UnreadMode_All,
UnreadMode.Unseen => Language.UnreadMode_Unseen,
UnreadMode.None => Language.UnreadMode_None,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
internal static string Name(this UnreadMode mode) =>
mode switch
{
UnreadMode.All => Language.UnreadMode_All,
UnreadMode.Unseen => Language.UnreadMode_Unseen,
UnreadMode.None => Language.UnreadMode_None,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
internal static string? Tooltip(this UnreadMode mode) => mode switch
{
UnreadMode.All => Language.UnreadMode_All_Tooltip,
UnreadMode.Unseen => Language.UnreadMode_Unseen_Tooltip,
UnreadMode.None => Language.UnreadMode_None_Tooltip,
_ => null,
};
internal static string? Tooltip(this UnreadMode mode) =>
mode switch
{
UnreadMode.All => Language.UnreadMode_All_Tooltip,
UnreadMode.Unseen => Language.UnreadMode_Unseen_Tooltip,
UnreadMode.None => Language.UnreadMode_None_Tooltip,
_ => null,
};
}
[Serializable]
@@ -468,29 +492,50 @@ public class Tab
public bool AllSenderMessages;
public TellTarget TellTarget = TellTarget.Empty();
[NonSerialized] public uint Unread;
[NonSerialized] public uint LastSendUnread;
[NonSerialized] public long LastActivity;
[NonSerialized] public MessageList Messages = new();
[NonSerialized]
public uint Unread;
[NonSerialized] public UsedChannel CurrentChannel = new();
[NonSerialized]
public uint LastSendUnread;
[NonSerialized] public Guid Identifier = Guid.NewGuid();
[NonSerialized]
public long LastActivity;
[NonSerialized]
public MessageList Messages = new();
[NonSerialized]
public UsedChannel CurrentChannel = new();
[NonSerialized]
public Guid Identifier = Guid.NewGuid();
// Hellion Chat — Auto-Tell-Tabs greeted flag. Toggled manually from the
// sidebar to mark a tell partner as already greeted in the current
// session. NonSerialized because the temp tab itself is session-only.
[NonSerialized] public bool IsGreeted;
[NonSerialized]
public bool IsGreeted;
// v1.4.2 — TabTintCache uses separate validation keys per cache so a
// TellTarget change picked up by GetTint can't strand GetIcon (or vice
// versa) with a stale entry that looks fresh on the shared key.
[NonSerialized] internal string? _cachedTintTellName;
[NonSerialized] internal uint _cachedTintTellWorld;
[NonSerialized] internal uint _cachedTellTint;
[NonSerialized] internal string? _cachedIconTellName;
[NonSerialized] internal uint _cachedIconTellWorld;
[NonSerialized] internal string? _cachedTellIcon;
[NonSerialized]
internal string? _cachedTintTellName;
[NonSerialized]
internal uint _cachedTintTellWorld;
[NonSerialized]
internal uint _cachedTellTint;
[NonSerialized]
internal string? _cachedIconTellName;
[NonSerialized]
internal uint _cachedIconTellWorld;
[NonSerialized]
internal string? _cachedTellIcon;
public bool Matches(Message message)
{
@@ -517,12 +562,17 @@ public class Tab
return;
Unread += 1;
if (message.Matches(Plugin.Config.InactivityHideChannelsV2, Plugin.Config.InactivityHideExtraChatAll, Plugin.Config.InactivityHideExtraChatChannels))
if (
message.Matches(
Plugin.Config.InactivityHideChannelsV2,
Plugin.Config.InactivityHideExtraChatAll,
Plugin.Config.InactivityHideExtraChatChannels
)
)
LastActivity = Environment.TickCount64;
}
public void Clear()
=> Messages.Clear();
public void Clear() => Messages.Clear();
public Tab Clone()
{
@@ -660,8 +710,14 @@ public class Tab
get
{
LockSlim.Wait(-1);
try { return Messages.Count; }
finally { LockSlim.Release(); }
try
{
return Messages.Count;
}
finally
{
LockSlim.Release();
}
}
}
@@ -691,7 +747,9 @@ public class Tab
return new RLockedMessageList(LockSlim, Messages);
}
public class RLockedMessageList(SemaphoreSlim lockSlim, List<Message> messages) : IReadOnlyList<Message>, IDisposable
public class RLockedMessageList(SemaphoreSlim lockSlim, List<Message> messages)
: IReadOnlyList<Message>,
IDisposable
{
public IEnumerator<Message> GetEnumerator()
{
@@ -750,15 +808,16 @@ public enum PreviewPosition
public static class PreviewPositionExt
{
public static string Name(this PreviewPosition position) => position switch
{
PreviewPosition.None => Language.Options_Preview_None,
PreviewPosition.Inside => Language.Options_Preview_Inside,
PreviewPosition.Top => Language.Options_Preview_Top,
PreviewPosition.Bottom => Language.Options_Preview_Bottom,
PreviewPosition.Tooltip => Language.Options_Preview_Tooltip,
_ => throw new ArgumentOutOfRangeException(nameof(position), position, null),
};
public static string Name(this PreviewPosition position) =>
position switch
{
PreviewPosition.None => Language.Options_Preview_None,
PreviewPosition.Inside => Language.Options_Preview_Inside,
PreviewPosition.Top => Language.Options_Preview_Top,
PreviewPosition.Bottom => Language.Options_Preview_Bottom,
PreviewPosition.Tooltip => Language.Options_Preview_Tooltip,
_ => throw new ArgumentOutOfRangeException(nameof(position), position, null),
};
}
[Serializable]
@@ -771,13 +830,14 @@ public enum CommandHelpSide
public static class CommandHelpSideExt
{
public static string Name(this CommandHelpSide side) => side switch
{
CommandHelpSide.None => Language.CommandHelpSide_None,
CommandHelpSide.Left => Language.CommandHelpSide_Left,
CommandHelpSide.Right => Language.CommandHelpSide_Right,
_ => throw new ArgumentOutOfRangeException(nameof(side), side, null),
};
public static string Name(this CommandHelpSide side) =>
side switch
{
CommandHelpSide.None => Language.CommandHelpSide_None,
CommandHelpSide.Left => Language.CommandHelpSide_Left,
CommandHelpSide.Right => Language.CommandHelpSide_Right,
_ => throw new ArgumentOutOfRangeException(nameof(side), side, null),
};
}
[Serializable]
@@ -789,19 +849,21 @@ public enum KeybindMode
public static class KeybindModeExt
{
public static string Name(this KeybindMode mode) => mode switch
{
KeybindMode.Flexible => Language.KeybindMode_Flexible_Name,
KeybindMode.Strict => Language.KeybindMode_Strict_Name,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
public static string Name(this KeybindMode mode) =>
mode switch
{
KeybindMode.Flexible => Language.KeybindMode_Flexible_Name,
KeybindMode.Strict => Language.KeybindMode_Strict_Name,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
public static string? Tooltip(this KeybindMode mode) => mode switch
{
KeybindMode.Flexible => Language.KeybindMode_Flexible_Tooltip,
KeybindMode.Strict => Language.KeybindMode_Strict_Tooltip,
_ => null,
};
public static string? Tooltip(this KeybindMode mode) =>
mode switch
{
KeybindMode.Flexible => Language.KeybindMode_Flexible_Tooltip,
KeybindMode.Strict => Language.KeybindMode_Strict_Tooltip,
_ => null,
};
}
[Serializable]
@@ -830,49 +892,51 @@ public enum LanguageOverride
public static class LanguageOverrideExt
{
public static string Name(this LanguageOverride mode) => mode switch
{
LanguageOverride.None => Language.LanguageOverride_None,
LanguageOverride.ChineseSimplified => "简体中文",
LanguageOverride.ChineseTraditional => "繁體中文",
LanguageOverride.Dutch => "Nederlands",
LanguageOverride.English => "English",
LanguageOverride.French => "Français",
LanguageOverride.German => "Deutsch",
LanguageOverride.Greek => "Ελληνικά",
// LanguageOverride.Italian => "Italiano",
LanguageOverride.Japanese => "日本語",
// LanguageOverride.Korean => "한국어 (Korean)",
// LanguageOverride.Norwegian => "Norsk",
LanguageOverride.PortugueseBrazil => "Português do Brasil",
LanguageOverride.Romanian => "Română",
LanguageOverride.Russian => "Русский",
LanguageOverride.Spanish => "Español",
LanguageOverride.Swedish => "Svenska",
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
public static string Name(this LanguageOverride mode) =>
mode switch
{
LanguageOverride.None => Language.LanguageOverride_None,
LanguageOverride.ChineseSimplified => "简体中文",
LanguageOverride.ChineseTraditional => "繁體中文",
LanguageOverride.Dutch => "Nederlands",
LanguageOverride.English => "English",
LanguageOverride.French => "Français",
LanguageOverride.German => "Deutsch",
LanguageOverride.Greek => "Ελληνικά",
// LanguageOverride.Italian => "Italiano",
LanguageOverride.Japanese => "日本語",
// LanguageOverride.Korean => "한국어 (Korean)",
// LanguageOverride.Norwegian => "Norsk",
LanguageOverride.PortugueseBrazil => "Português do Brasil",
LanguageOverride.Romanian => "Română",
LanguageOverride.Russian => "Русский",
LanguageOverride.Spanish => "Español",
LanguageOverride.Swedish => "Svenska",
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
public static string Code(this LanguageOverride mode) => mode switch
{
LanguageOverride.None => "",
LanguageOverride.ChineseSimplified => "zh-hans",
LanguageOverride.ChineseTraditional => "zh-hant",
LanguageOverride.Dutch => "nl",
LanguageOverride.English => "en",
LanguageOverride.French => "fr",
LanguageOverride.German => "de",
LanguageOverride.Greek => "el",
// LanguageOverride.Italian => "it",
LanguageOverride.Japanese => "ja",
// LanguageOverride.Korean => "ko",
// LanguageOverride.Norwegian => "no",
LanguageOverride.PortugueseBrazil => "pt-br",
LanguageOverride.Romanian => "ro",
LanguageOverride.Russian => "ru",
LanguageOverride.Spanish => "es",
LanguageOverride.Swedish => "sv",
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
public static string Code(this LanguageOverride mode) =>
mode switch
{
LanguageOverride.None => "",
LanguageOverride.ChineseSimplified => "zh-hans",
LanguageOverride.ChineseTraditional => "zh-hant",
LanguageOverride.Dutch => "nl",
LanguageOverride.English => "en",
LanguageOverride.French => "fr",
LanguageOverride.German => "de",
LanguageOverride.Greek => "el",
// LanguageOverride.Italian => "it",
LanguageOverride.Japanese => "ja",
// LanguageOverride.Korean => "ko",
// LanguageOverride.Norwegian => "no",
LanguageOverride.PortugueseBrazil => "pt-br",
LanguageOverride.Romanian => "ro",
LanguageOverride.Russian => "ru",
LanguageOverride.Spanish => "es",
LanguageOverride.Swedish => "sv",
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
}
[Serializable]
@@ -890,27 +954,31 @@ public enum ExtraGlyphRanges
public static class ExtraGlyphRangesExt
{
public static string Name(this ExtraGlyphRanges ranges) => ranges switch
{
ExtraGlyphRanges.ChineseFull => Language.ExtraGlyphRanges_ChineseFull_Name,
ExtraGlyphRanges.ChineseSimplifiedCommon => Language.ExtraGlyphRanges_ChineseSimplifiedCommon_Name,
ExtraGlyphRanges.Cyrillic => Language.ExtraGlyphRanges_Cyrillic_Name,
ExtraGlyphRanges.Japanese => Language.ExtraGlyphRanges_Japanese_Name,
ExtraGlyphRanges.Korean => Language.ExtraGlyphRanges_Korean_Name,
ExtraGlyphRanges.Thai => Language.ExtraGlyphRanges_Thai_Name,
ExtraGlyphRanges.Vietnamese => Language.ExtraGlyphRanges_Vietnamese_Name,
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
};
public static string Name(this ExtraGlyphRanges ranges) =>
ranges switch
{
ExtraGlyphRanges.ChineseFull => Language.ExtraGlyphRanges_ChineseFull_Name,
ExtraGlyphRanges.ChineseSimplifiedCommon =>
Language.ExtraGlyphRanges_ChineseSimplifiedCommon_Name,
ExtraGlyphRanges.Cyrillic => Language.ExtraGlyphRanges_Cyrillic_Name,
ExtraGlyphRanges.Japanese => Language.ExtraGlyphRanges_Japanese_Name,
ExtraGlyphRanges.Korean => Language.ExtraGlyphRanges_Korean_Name,
ExtraGlyphRanges.Thai => Language.ExtraGlyphRanges_Thai_Name,
ExtraGlyphRanges.Vietnamese => Language.ExtraGlyphRanges_Vietnamese_Name,
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
};
public static unsafe nint Range(this ExtraGlyphRanges ranges) => ranges switch
{
ExtraGlyphRanges.ChineseFull => (nint)ImGui.GetIO().Fonts.GetGlyphRangesChineseFull(),
ExtraGlyphRanges.ChineseSimplifiedCommon => (nint)ImGui.GetIO().Fonts.GetGlyphRangesChineseSimplifiedCommon(),
ExtraGlyphRanges.Cyrillic => (nint)ImGui.GetIO().Fonts.GetGlyphRangesCyrillic(),
ExtraGlyphRanges.Japanese => (nint)ImGui.GetIO().Fonts.GetGlyphRangesJapanese(),
ExtraGlyphRanges.Korean => (nint)ImGui.GetIO().Fonts.GetGlyphRangesKorean(),
ExtraGlyphRanges.Thai => (nint)ImGui.GetIO().Fonts.GetGlyphRangesThai(),
ExtraGlyphRanges.Vietnamese => (nint)ImGui.GetIO().Fonts.GetGlyphRangesVietnamese(),
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
};
public static unsafe nint Range(this ExtraGlyphRanges ranges) =>
ranges switch
{
ExtraGlyphRanges.ChineseFull => (nint)ImGui.GetIO().Fonts.GetGlyphRangesChineseFull(),
ExtraGlyphRanges.ChineseSimplifiedCommon => (nint)
ImGui.GetIO().Fonts.GetGlyphRangesChineseSimplifiedCommon(),
ExtraGlyphRanges.Cyrillic => (nint)ImGui.GetIO().Fonts.GetGlyphRangesCyrillic(),
ExtraGlyphRanges.Japanese => (nint)ImGui.GetIO().Fonts.GetGlyphRangesJapanese(),
ExtraGlyphRanges.Korean => (nint)ImGui.GetIO().Fonts.GetGlyphRangesKorean(),
ExtraGlyphRanges.Thai => (nint)ImGui.GetIO().Fonts.GetGlyphRangesThai(),
ExtraGlyphRanges.Vietnamese => (nint)ImGui.GetIO().Fonts.GetGlyphRangesVietnamese(),
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
};
}
+67 -23
View File
@@ -2,10 +2,10 @@
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Utility;
using Dalamud.Bindings.ImGui;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -15,11 +15,31 @@ public static class EmoteCache
{
private static readonly string[] NotWorking =
[
":tf:", "(ditto)", "c!", "h!", "l!", "M&Mjc", "LUL3D", "p!",
"POLICE2", "r!", "Pussy", "s!", "v!", "w!", "x0r6ztGiggle",
"z!", "xar2EDM", "iron95Pls", "Clap2", "AlienPls3", "Life",
"peepoPogClimbingTreeHard4House", "monkaGIGAftRobertDowneyJr",
"DogLookingSussyAndCold", "DICKS"
":tf:",
"(ditto)",
"c!",
"h!",
"l!",
"M&Mjc",
"LUL3D",
"p!",
"POLICE2",
"r!",
"Pussy",
"s!",
"v!",
"w!",
"x0r6ztGiggle",
"z!",
"xar2EDM",
"iron95Pls",
"Clap2",
"AlienPls3",
"Life",
"peepoPogClimbingTreeHard4House",
"monkaGIGAftRobertDowneyJr",
"DogLookingSussyAndCold",
"DICKS",
];
private static readonly HttpClient Client = new();
@@ -56,7 +76,7 @@ public static class EmoteCache
{
Unloaded,
Loading,
Done
Done,
}
// All of this data is uninitalized while State is not `LoadingState.Done`
@@ -80,11 +100,16 @@ public static class EmoteCache
internal static void TrackLoad(Task loadTask, string emoteCode)
{
PendingLoads.Add(loadTask.ContinueWith(t =>
{
if (t.IsFaulted)
Plugin.Log.Error(t.Exception!, $"EmoteCache load failed for {emoteCode}");
}, TaskScheduler.Default));
PendingLoads.Add(
loadTask.ContinueWith(
t =>
{
if (t.IsFaulted)
Plugin.Log.Error(t.Exception!, $"EmoteCache load failed for {emoteCode}");
},
TaskScheduler.Default
)
);
}
public static async Task LoadData()
@@ -121,7 +146,10 @@ public static class EmoteCache
// load. Skip them defensively so a single bad row no longer
// breaks the cache for everyone else.
foreach (var emote in jsonList)
if (!string.IsNullOrEmpty(emote.Emote.Code) && !NotWorking.Contains(emote.Emote.Code))
if (
!string.IsNullOrEmpty(emote.Emote.Code)
&& !NotWorking.Contains(emote.Emote.Code)
)
Cache.TryAdd(emote.Emote.Code, emote.Emote);
lastId = jsonList.Last().Id;
@@ -225,13 +253,19 @@ public static class EmoteCache
// upstream could still hand us "../foo" and write into the
// pluginConfigs root (or worse). Resolve the candidate path and
// refuse anything that escapes the cache directory.
var dir = Path.GetFullPath(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "EmoteCacheV1"));
var dir = Path.GetFullPath(
Path.Join(Plugin.Interface.ConfigDirectory.FullName, "EmoteCacheV1")
);
Directory.CreateDirectory(dir);
var dirPrefix = dir.EndsWith(Path.DirectorySeparatorChar) ? dir : dir + Path.DirectorySeparatorChar;
var dirPrefix = dir.EndsWith(Path.DirectorySeparatorChar)
? dir
: dir + Path.DirectorySeparatorChar;
var filePath = Path.GetFullPath(Path.Join(dir, $"{emote.Id}.{emote.ImageType}"));
if (!filePath.StartsWith(dirPrefix, StringComparison.Ordinal))
throw new InvalidOperationException($"Emote path escapes cache directory: id={emote.Id}, type={emote.ImageType}");
throw new InvalidOperationException(
$"Emote path escapes cache directory: id={emote.Id}, type={emote.ImageType}"
);
if (File.Exists(filePath))
{
@@ -242,7 +276,12 @@ public static class EmoteCache
var content = await Client.GetAsync(EmotePath.Format(emote.Id), ct);
RawData = await content.Content.ReadAsByteArrayAsync(ct);
await using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
await using var stream = new FileStream(
filePath,
FileMode.Create,
FileAccess.Write,
FileShare.Read
);
await stream.WriteAsync(RawData, ct);
}
@@ -271,12 +310,13 @@ public static class EmoteCache
return;
ct.ThrowIfCancellationRequested();
Texture = await Plugin.TextureProvider.CreateFromImageAsync(image, cancellationToken: ct);
Texture = await Plugin.TextureProvider.CreateFromImageAsync(
image,
cancellationToken: ct
);
IsLoaded = true;
}
catch (OperationCanceledException)
{
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
Failed = true;
@@ -363,7 +403,11 @@ public static class EmoteCache
var buffer = new byte[4 * frame.Width * frame.Height];
frame.CopyPixelDataTo(buffer);
var tex = await Plugin.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(frame.Width, frame.Height), buffer, cancellationToken: ct);
var tex = await Plugin.TextureProvider.CreateFromRawAsync(
RawImageSpecification.Rgba32(frame.Width, frame.Height),
buffer,
cancellationToken: ct
);
frames.Add((tex, delay));
}
@@ -385,4 +429,4 @@ public static class EmoteCache
}
}
}
}
}
+71 -31
View File
@@ -13,21 +13,23 @@ internal enum ExportFormat
internal static class ExportFormatExt
{
internal static string Extension(this ExportFormat fmt) => fmt switch
{
ExportFormat.Markdown => "md",
ExportFormat.Json => "json",
ExportFormat.Csv => "csv",
_ => "txt",
};
internal static string Extension(this ExportFormat fmt) =>
fmt switch
{
ExportFormat.Markdown => "md",
ExportFormat.Json => "json",
ExportFormat.Csv => "csv",
_ => "txt",
};
internal static string Filter(this ExportFormat fmt) => fmt switch
{
ExportFormat.Markdown => ".md",
ExportFormat.Json => ".json",
ExportFormat.Csv => ".csv",
_ => ".txt",
};
internal static string Filter(this ExportFormat fmt) =>
fmt switch
{
ExportFormat.Markdown => ".md",
ExportFormat.Json => ".json",
ExportFormat.Csv => ".csv",
_ => ".txt",
};
}
/// <summary>
@@ -42,13 +44,15 @@ internal static class MessageExporter
IReadOnlyCollection<int>? ChatTypes,
DateTimeOffset? From,
DateTimeOffset? To,
string? SenderSubstring);
string? SenderSubstring
);
internal static int ExportToFile(
string path,
ExportFormat format,
IEnumerable<Message> messages,
FilterDescription filter)
FilterDescription filter
)
{
var matching = filter.SenderSubstring is { Length: > 0 } needle
? messages.Where(m => MatchesSender(m, needle))
@@ -64,10 +68,14 @@ internal static class MessageExporter
};
}
private static bool MatchesSender(Message m, string needle)
=> m.SenderSource.TextValue.Contains(needle, StringComparison.OrdinalIgnoreCase);
private static bool MatchesSender(Message m, string needle) =>
m.SenderSource.TextValue.Contains(needle, StringComparison.OrdinalIgnoreCase);
private static int WriteMarkdown(StreamWriter w, IEnumerable<Message> messages, FilterDescription filter)
private static int WriteMarkdown(
StreamWriter w,
IEnumerable<Message> messages,
FilterDescription filter
)
{
w.WriteLine("# Hellion Chat Export");
w.WriteLine();
@@ -107,7 +115,9 @@ internal static class MessageExporter
private static void WriteFilterSummaryMarkdown(StreamWriter w, FilterDescription filter)
{
if (filter.ChatTypes is { Count: > 0 })
w.WriteLine($"ChatTypes: {string.Join(", ", filter.ChatTypes.Select(t => $"{(ChatType)(ushort)t}({t})"))}");
w.WriteLine(
$"ChatTypes: {string.Join(", ", filter.ChatTypes.Select(t => $"{(ChatType)(ushort)t}({t})"))}"
);
if (filter.From is not null)
w.WriteLine($"From: {filter.From.Value.ToLocalTime():yyyy-MM-dd HH:mm}");
if (filter.To is not null)
@@ -116,7 +126,11 @@ internal static class MessageExporter
w.WriteLine($"Sender contains: \"{filter.SenderSubstring}\"");
}
private static int WriteJson(StreamWriter w, IEnumerable<Message> messages, FilterDescription filter)
private static int WriteJson(
StreamWriter w,
IEnumerable<Message> messages,
FilterDescription filter
)
{
// Manual JSON to avoid pulling in System.Text.Json policy choices.
// Output is a single object with metadata and an array of messages.
@@ -130,9 +144,17 @@ internal static class MessageExporter
else
w.Write("null");
w.Write(",\n \"from\": ");
w.Write(filter.From is null ? "null" : "\"" + filter.From.Value.ToString("O", CultureInfo.InvariantCulture) + "\"");
w.Write(
filter.From is null
? "null"
: "\"" + filter.From.Value.ToString("O", CultureInfo.InvariantCulture) + "\""
);
w.Write(",\n \"to\": ");
w.Write(filter.To is null ? "null" : "\"" + filter.To.Value.ToString("O", CultureInfo.InvariantCulture) + "\"");
w.Write(
filter.To is null
? "null"
: "\"" + filter.To.Value.ToString("O", CultureInfo.InvariantCulture) + "\""
);
w.Write(",\n \"sender_substring\": ");
w.Write(filter.SenderSubstring is null ? "null" : JsonString(filter.SenderSubstring));
w.Write("\n },\n \"messages\": [\n");
@@ -166,7 +188,11 @@ internal static class MessageExporter
return count;
}
private static int WriteCsv(StreamWriter w, IEnumerable<Message> messages, FilterDescription filter)
private static int WriteCsv(
StreamWriter w,
IEnumerable<Message> messages,
FilterDescription filter
)
{
// Header line always written so empty exports are still importable.
w.WriteLine("Date,ChatType,ChatTypeName,Sender,Content,Receiver,ContentId");
@@ -201,13 +227,27 @@ internal static class MessageExporter
{
switch (c)
{
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '\b': sb.Append("\\b"); break;
case '\f': sb.Append("\\f"); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
case '"':
sb.Append("\\\"");
break;
case '\\':
sb.Append("\\\\");
break;
case '\b':
sb.Append("\\b");
break;
case '\f':
sb.Append("\\f");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
if (c < 0x20)
sb.Append($"\\u{(int)c:x4}");
+108 -60
View File
@@ -1,10 +1,10 @@
using Dalamud;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.Utility;
using Dalamud.Bindings.ImGui;
namespace HellionChat;
@@ -23,9 +23,22 @@ public class FontManager
public static readonly HashSet<float> AxisFontSizeList =
[
9.6f, 10f, 12f, 14f, 16f,
18f, 18.4f, 20f, 23f, 34f,
36f, 40f, 45f, 46f, 68f, 90f,
9.6f,
10f,
12f,
14f,
16f,
18f,
18.4f,
20f,
23f,
34f,
36f,
40f,
45f,
46f,
68f,
90f,
];
/// <summary>
@@ -41,8 +54,11 @@ public class FontManager
if (HellionFontBytes is not null)
return HellionFontBytes;
using var stream = typeof(FontManager).Assembly.GetManifestResourceStream("HellionFont.ttf")
?? throw new FileNotFoundException("Hellion font resource not embedded in the assembly");
using var stream =
typeof(FontManager).Assembly.GetManifestResourceStream("HellionFont.ttf")
?? throw new FileNotFoundException(
"Hellion font resource not embedded in the assembly"
);
using var ms = new MemoryStream();
stream.CopyTo(ms);
HellionFontBytes = ms.ToArray();
@@ -66,8 +82,8 @@ public class FontManager
if (chars[i] == 0)
break;
for (var j = (uint) chars[i]; j <= chars[i + 1]; j++)
builder.AddChar((ushort) j);
for (var j = (uint)chars[i]; j <= chars[i + 1]; j++)
builder.AddChar((ushort)j);
}
}
@@ -85,7 +101,7 @@ public class FontManager
// "Enclosed Alphanumerics" (partial) https://www.compart.com/en/unicode/block/U+2460
for (var i = 0x2460; i <= 0x24B5; i++)
builder.AddChar((char) i);
builder.AddChar((char)i);
builder.AddChar('⓪');
return builder.BuildRangesToArray();
@@ -117,66 +133,84 @@ public class FontManager
{
SetUpRanges();
Axis = Plugin.Interface.UiBuilder.FontAtlas.NewGameFontHandle(new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2)));
AxisItalic = Plugin.Interface.UiBuilder.FontAtlas.NewGameFontHandle(new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
{
SkewStrength = SizeInPx(Plugin.Config.FontSizeV2) / 6
});
Axis = Plugin.Interface.UiBuilder.FontAtlas.NewGameFontHandle(
new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
);
AxisItalic = Plugin.Interface.UiBuilder.FontAtlas.NewGameFontHandle(
new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
{
SkewStrength = SizeInPx(Plugin.Config.FontSizeV2) / 6,
}
);
FontAwesome = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
{
e.OnPreBuild(tk => tk.AddFontAwesomeIconFont(new SafeFontConfig { SizePx = GetFontSize() }));
e.OnPreBuild(tk =>
tk.AddFontAwesomeIconFont(new SafeFontConfig { SizePx = GetFontSize() })
);
e.OnPostBuild(tk => tk.FitRatio(tk.Font));
});
RegularFont = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(
e => e.OnPreBuild(
tk =>
RegularFont = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
e.OnPreBuild(tk =>
{
// v1.2.0 — Bei aktiver Hellion-Schrift (Exo 2 ist Variable-Font)
// wird die User-Schriftgröße aus FontSizeV2 als SizePt angewendet.
// Der Bestand-Pfad nutzt weiter GlobalFontV2.SizePt aus dem
// Custom-Font-Stack. Ohne diese Verzweigung war FontSizeV2 bei
// UseHellionFont=true wirkungslos, was 4K-User mit größerer
// Skalierung blockierte (Settings → Erscheinungsbild → Schriftarten).
var basePt = Plugin.Config.UseHellionFont
? Plugin.Config.FontSizeV2
: Plugin.Config.GlobalFontV2.SizePt;
var config = new SafeFontConfig { SizePt = basePt, GlyphRanges = Ranges };
config.MergeFont = Plugin.Config.UseHellionFont
? tk.AddFontFromMemory(GetHellionFontBytes(), config, "Hellion-Exo2")
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
config.GlyphRanges = JpRange;
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
tk.AddGameSymbol(config);
tk.Font = config.MergeFont;
})
);
if (Plugin.Config.ItalicEnabled)
{
ItalicFont = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
e.OnPreBuild(tk =>
{
// v1.2.0 — Bei aktiver Hellion-Schrift (Exo 2 ist Variable-Font)
// wird die User-Schriftgröße aus FontSizeV2 als SizePt angewendet.
// Der Bestand-Pfad nutzt weiter GlobalFontV2.SizePt aus dem
// Custom-Font-Stack. Ohne diese Verzweigung war FontSizeV2 bei
// UseHellionFont=true wirkungslos, was 4K-User mit größerer
// Skalierung blockierte (Settings → Erscheinungsbild → Schriftarten).
var basePt = Plugin.Config.UseHellionFont
? Plugin.Config.FontSizeV2
: Plugin.Config.GlobalFontV2.SizePt;
var config = new SafeFontConfig {SizePt = basePt, GlyphRanges = Ranges};
config.MergeFont = Plugin.Config.UseHellionFont
? tk.AddFontFromMemory(GetHellionFontBytes(), config, "Hellion-Exo2")
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
var config = new SafeFontConfig
{
SizePt = Plugin.Config.ItalicFontV2.SizePt,
GlyphRanges = Ranges,
};
config.MergeFont = AddFontWithFallback(
tk,
Plugin.Config.ItalicFontV2.FontId,
config,
"italic"
);
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
config.GlyphRanges = JpRange;
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
AddFontWithFallback(
tk,
Plugin.Config.JapaneseFontV2.FontId,
config,
"japanese"
);
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
tk.AddGameSymbol(config);
tk.Font = config.MergeFont;
}
));
if (Plugin.Config.ItalicEnabled)
{
ItalicFont = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(
e => e.OnPreBuild(
tk =>
{
var config = new SafeFontConfig {SizePt = Plugin.Config.ItalicFontV2.SizePt, GlyphRanges = Ranges};
config.MergeFont = AddFontWithFallback(tk, Plugin.Config.ItalicFontV2.FontId, config, "italic");
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
config.GlyphRanges = JpRange;
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
tk.AddGameSymbol(config);
tk.Font = config.MergeFont;
}
));
})
);
}
else
{
@@ -191,21 +225,35 @@ public class FontManager
/// pointing at a font the user uninstalled or that never existed on
/// Linux (e.g. "Crimson Text") tears down the entire font atlas build.
/// </summary>
private static ImFontPtr AddFontWithFallback(IFontAtlasBuildToolkitPreBuild tk, IFontId fontId, SafeFontConfig config, string slot)
private static ImFontPtr AddFontWithFallback(
IFontAtlasBuildToolkitPreBuild tk,
IFontId fontId,
SafeFontConfig config,
string slot
)
{
try
{
return fontId.AddToBuildToolkit(tk, config);
}
catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException or IOException)
catch (Exception e)
when (e is FileNotFoundException or DirectoryNotFoundException or IOException)
{
Plugin.Log.Warning(e, $"Configured {slot} font unavailable, falling back to NotoSansCjkRegular");
Plugin.Log.Warning(
e,
$"Configured {slot} font unavailable, falling back to NotoSansCjkRegular"
);
var fallback = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular);
return fallback.AddToBuildToolkit(tk, config);
}
}
public static float SizeInPt(float px) => (float) (px * 3.0 / 4.0);
public static float SizeInPx(float pt) => (float) (pt * 4.0 / 3.0);
public static float GetFontSize() => Plugin.Config.FontsEnabled ? Plugin.Config.GlobalFontV2.SizePx : SizeInPx(Plugin.Config.FontSizeV2);
public static float SizeInPt(float px) => (float)(px * 3.0 / 4.0);
public static float SizeInPx(float pt) => (float)(pt * 4.0 / 3.0);
public static float GetFontSize() =>
Plugin.Config.FontsEnabled
? Plugin.Config.GlobalFontV2.SizePx
: SizeInPx(Plugin.Config.FontSizeV2);
}
+251 -74
View File
@@ -1,8 +1,4 @@
using System.Text;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Game.Config;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
@@ -17,9 +13,12 @@ using FFXIVClientStructs.FFXIV.Client.UI.Info;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
using FFXIVClientStructs.FFXIV.Component.GUI;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
using HellionChat.Util;
using InteropGenerator.Runtime;
using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
namespace HellionChat.GameFunctions;
@@ -28,20 +27,55 @@ internal sealed unsafe class Chat : IDisposable
{
// Functions
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D B9 ?? ?? ?? ?? 33 C0")]
private readonly delegate* unmanaged<RaptureLogModule*, ushort, Utf8String*, Utf8String*, ulong, ulong, ushort, byte, int, byte, void> PrintTellNative = null!;
private readonly delegate* unmanaged<
RaptureLogModule*,
ushort,
Utf8String*,
Utf8String*,
ulong,
ulong,
ushort,
byte,
int,
byte,
void> PrintTellNative = null!;
[Signature("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 ?? 48 8B 8C 24")]
private readonly delegate* unmanaged<NetworkModule*, ulong, ushort, Utf8String*, Utf8String*, ushort, ushort, byte> SendTellNative = null!;
[Signature(
"E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 ?? 48 8B 8C 24"
)]
private readonly delegate* unmanaged<
NetworkModule*,
ulong,
ushort,
Utf8String*,
Utf8String*,
ushort,
ushort,
byte> SendTellNative = null!;
// Client::UI::AddonChatLog.OnRefresh
[Signature("40 53 57 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 4D 8B F8", DetourName = nameof(ChatLogRefreshDetour))]
[Signature(
"40 53 57 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 4D 8B F8",
DetourName = nameof(ChatLogRefreshDetour)
)]
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook = null!;
private delegate byte ChatLogRefreshDelegate(nint log, ushort eventId, AtkValue* value);
// Replace with CS version later
[Signature("48 89 5C 24 ?? 55 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 83 B9", DetourName = nameof(ContextMenuTellInForayDetour))]
[Signature(
"48 89 5C 24 ?? 55 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 83 B9",
DetourName = nameof(ContextMenuTellInForayDetour)
)]
private Hook<ContextMenuTellInForayDelegate>? ContextMenuTellInForayHook = null!;
private delegate void ContextMenuTellInForayDelegate(RaptureShellModule* module, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason);
private delegate void ContextMenuTellInForayDelegate(
RaptureShellModule* module,
Utf8String* playerName,
Utf8String* worldName,
ushort worldId,
ulong accountId,
ulong contentId,
ushort reason
);
private readonly Hook<AgentChatLog.Delegates.ChangeChannelName>? ChangeChannelNameHook;
private readonly Hook<RaptureShellModule.Delegates.ReplyInSelectedChatMode>? ReplyInSelectedChatModeHook;
@@ -58,7 +92,7 @@ internal sealed unsafe class Chat : IDisposable
FullName = 0,
SurnameAbbreviated = 1,
ForenameAbbreviated = 2,
Initials = 3
Initials = 3,
}
private long LastPlayerNameDisplayTypeRefresh;
@@ -72,13 +106,25 @@ internal sealed unsafe class Chat : IDisposable
ChatLogRefreshHook?.Enable();
ContextMenuTellInForayHook?.Enable();
ChangeChannelNameHook = Plugin.GameInteropProvider.HookFromAddress<AgentChatLog.Delegates.ChangeChannelName>(AgentChatLog.MemberFunctionPointers.ChangeChannelName, ChangeChannelNameDetour);
ChangeChannelNameHook =
Plugin.GameInteropProvider.HookFromAddress<AgentChatLog.Delegates.ChangeChannelName>(
AgentChatLog.MemberFunctionPointers.ChangeChannelName,
ChangeChannelNameDetour
);
ChangeChannelNameHook.Enable();
ReplyInSelectedChatModeHook = Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.ReplyInSelectedChatMode>(RaptureShellModule.MemberFunctionPointers.ReplyInSelectedChatMode, ReplyInSelectedChatModeDetour);
ReplyInSelectedChatModeHook =
Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.ReplyInSelectedChatMode>(
RaptureShellModule.MemberFunctionPointers.ReplyInSelectedChatMode,
ReplyInSelectedChatModeDetour
);
ReplyInSelectedChatModeHook.Enable();
SetChatLogTellTargetHook = Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.SetContextTellTarget>(RaptureShellModule.MemberFunctionPointers.SetContextTellTarget, SetContextTellTarget);
SetChatLogTellTargetHook =
Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.SetContextTellTarget>(
RaptureShellModule.MemberFunctionPointers.SetContextTellTarget,
SetContextTellTarget
);
SetChatLogTellTargetHook.Enable();
Plugin.ClientState.Login += Login;
@@ -108,12 +154,13 @@ internal sealed unsafe class Chat : IDisposable
return utf == null ? null : utf->ToString();
}
private static int GetRotateIdx(RotateMode mode) => mode switch
{
RotateMode.Forward => 1,
RotateMode.Reverse => -1,
_ => 0,
};
private static int GetRotateIdx(RotateMode mode) =>
mode switch
{
RotateMode.Forward => 1,
RotateMode.Reverse => -1,
_ => 0,
};
internal static void RotateLinkshellHistory(RotateMode mode)
{
@@ -174,7 +221,7 @@ internal sealed unsafe class Chat : IDisposable
{
string? input = null;
var utf8Bytes = MemoryHelper.ReadRaw((nint)LastTypedCharacter+0x4, 2);
var utf8Bytes = MemoryHelper.ReadRaw((nint)LastTypedCharacter + 0x4, 2);
var chars = Encoding.UTF8.GetString(utf8Bytes).ToCharArray();
if (chars.Length == 0)
return;
@@ -185,7 +232,9 @@ internal sealed unsafe class Chat : IDisposable
try
{
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { Input = input, });
Plugin.ChatLogWindow.Activated(
new ChatActivatedArgs(new ChannelSwitchInfo(null)) { Input = input }
);
}
catch (Exception ex)
{
@@ -197,7 +246,7 @@ internal sealed unsafe class Chat : IDisposable
string? addIfNotPresent = null;
var str = value + 2;
if (str != null && ((int) str->Type & 0xF) == (int) ValueType.String && str->String.HasValue)
if (str != null && ((int)str->Type & 0xF) == (int)ValueType.String && str->String.HasValue)
{
var add = str->String.ToString();
if (add.Length > 0)
@@ -214,7 +263,12 @@ internal sealed unsafe class Chat : IDisposable
return ChatLogRefreshHook!.Original(log, eventId, value);
}
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { AddIfNotPresent = addIfNotPresent, });
Plugin.ChatLogWindow.Activated(
new ChatActivatedArgs(new ChannelSwitchInfo(null))
{
AddIfNotPresent = addIfNotPresent,
}
);
}
catch (Exception ex)
{
@@ -231,9 +285,9 @@ internal sealed unsafe class Chat : IDisposable
if (agent == null)
return ret;
var channel = (uint) RaptureShellModule.Instance()->ChatType;
var channel = (uint)RaptureShellModule.Instance()->ChatType;
if (channel is 17 or 18)
channel = (uint) InputChannel.Tell;
channel = (uint)InputChannel.Tell;
var name = SeString.Parse(agent->ChannelLabel);
if (name.Payloads.Count == 0)
@@ -248,7 +302,7 @@ internal sealed unsafe class Chat : IDisposable
string? playerName = null;
ushort worldId = 0;
if (channel == (uint) InputChannel.Tell)
if (channel == (uint)InputChannel.Tell)
{
playerName = SeString.Parse(agent->TellPlayerName).TextValue;
worldId = agent->TellWorldId;
@@ -257,9 +311,9 @@ internal sealed unsafe class Chat : IDisposable
Plugin.CurrentTab.CurrentChannel = new UsedChannel
{
Channel = (InputChannel) channel,
Channel = (InputChannel)channel,
Name = nameChunks,
TellTarget = playerName != null ? new TellTarget(playerName, worldId, 0, 0) : null
TellTarget = playerName != null ? new TellTarget(playerName, worldId, 0, 0) : null,
};
return ret;
@@ -274,22 +328,40 @@ internal sealed unsafe class Chat : IDisposable
return;
}
SetChannel((InputChannel) replyMode);
SetChannel((InputChannel)replyMode);
ReplyInSelectedChatModeHook!.Original(agent);
}
private bool SetContextTellTarget(RaptureShellModule* a1, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason, bool setChatType)
private bool SetContextTellTarget(
RaptureShellModule* a1,
Utf8String* playerName,
Utf8String* worldName,
ushort worldId,
ulong accountId,
ulong contentId,
ushort reason,
bool setChatType
)
{
if (playerName != null)
{
try
{
var target = new TellTarget(playerName->ToString(), worldId, contentId, (TellReason) reason);
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell, permanent: setChatType))
{
TellReason = (TellReason) reason,
TellTarget = target,
});
var target = new TellTarget(
playerName->ToString(),
worldId,
contentId,
(TellReason)reason
);
Plugin.ChatLogWindow.Activated(
new ChatActivatedArgs(
new ChannelSwitchInfo(InputChannel.Tell, permanent: setChatType)
)
{
TellReason = (TellReason)reason,
TellTarget = target,
}
);
}
catch (Exception ex)
{
@@ -297,10 +369,27 @@ internal sealed unsafe class Chat : IDisposable
}
}
return SetChatLogTellTargetHook!.Original(a1, playerName, worldName, worldId, accountId, contentId, reason, setChatType);
return SetChatLogTellTargetHook!.Original(
a1,
playerName,
worldName,
worldId,
accountId,
contentId,
reason,
setChatType
);
}
private void ContextMenuTellInForayDetour(RaptureShellModule* a1, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason)
private void ContextMenuTellInForayDetour(
RaptureShellModule* a1,
Utf8String* playerName,
Utf8String* worldName,
ushort worldId,
ulong accountId,
ulong contentId,
ushort reason
)
{
if (!Plugin.CurrentTab.CurrentChannel.UseTempChannel)
Plugin.CurrentTab.CurrentChannel.UseTempChannel = true;
@@ -309,13 +398,20 @@ internal sealed unsafe class Chat : IDisposable
{
try
{
var target = new TellTarget(playerName->ToString(), worldId, contentId, (TellReason) reason);
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell))
{
TellReason = (TellReason) reason,
TellTarget = target,
TellSpecial = Sheets.IsInForay(), // Handle Eureka/Bozja special
});
var target = new TellTarget(
playerName->ToString(),
worldId,
contentId,
(TellReason)reason
);
Plugin.ChatLogWindow.Activated(
new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell))
{
TellReason = (TellReason)reason,
TellTarget = target,
TellSpecial = Sheets.IsInForay(), // Handle Eureka/Bozja special
}
);
}
catch (Exception ex)
{
@@ -323,7 +419,15 @@ internal sealed unsafe class Chat : IDisposable
}
}
ContextMenuTellInForayHook!.Original(a1, playerName, worldName, worldId, accountId, contentId, reason);
ContextMenuTellInForayHook!.Original(
a1,
playerName,
worldName,
worldId,
accountId,
contentId,
reason
);
}
/// <summary>
@@ -346,17 +450,22 @@ internal sealed unsafe class Chat : IDisposable
{
if (idx > 7)
return false;
return InfoProxyLinkshell.Instance()->LinkShells[(int) idx].Id != 0;
return InfoProxyLinkshell.Instance()->LinkShells[(int)idx].Id != 0;
}
internal static bool ValidCrossLinkshell(uint idx)
{
if (idx > 7)
return false;
return InfoProxyCrossWorldLinkshell.Instance()->CrossWorldLinkshells[(int) idx].Name.Length > 0;
return InfoProxyCrossWorldLinkshell.Instance()->CrossWorldLinkshells[(int)idx].Name.Length
> 0;
}
private static uint? RotateLinkshell(uint currentIndex, RotateMode rotate, Func<uint, bool> validFn)
private static uint? RotateLinkshell(
uint currentIndex,
RotateMode rotate,
Func<uint, bool> validFn
)
{
if (rotate == RotateMode.None)
return null;
@@ -365,13 +474,13 @@ internal sealed unsafe class Chat : IDisposable
{
RotateMode.Forward => 1,
RotateMode.Reverse => -1,
_ => 1
_ => 1,
};
// Iterate up to 8 times to find a valid linkshell.
for (var i = 0; i < 8; i++)
{
currentIndex = (uint) ((8 + currentIndex + delta) % 8);
currentIndex = (uint)((8 + currentIndex + delta) % 8);
if (validFn(currentIndex))
return currentIndex;
}
@@ -379,27 +488,40 @@ internal sealed unsafe class Chat : IDisposable
return null;
}
internal static InputChannel? ResolveTempInputChannel(InputChannel? currentTempChannel, InputChannel channel, RotateMode rotate)
internal static InputChannel? ResolveTempInputChannel(
InputChannel? currentTempChannel,
InputChannel channel,
RotateMode rotate
)
{
switch (channel)
{
case InputChannel.Linkshell1 or InputChannel.CrossLinkshell1 when rotate != RotateMode.None:
case InputChannel.Linkshell1
or InputChannel.CrossLinkshell1 when rotate != RotateMode.None:
{
var module = UIModule.Instance();
var currentIndex = channel is InputChannel.Linkshell1 ? (uint) module->LinkshellCycle : (uint) module->CrossWorldLinkshellCycle;
var currentIndex =
channel is InputChannel.Linkshell1
? (uint)module->LinkshellCycle
: (uint)module->CrossWorldLinkshellCycle;
if (currentTempChannel != null)
{
switch (channel)
{
case InputChannel.Linkshell1 when currentTempChannel.Value.IsLinkshell():
case InputChannel.CrossLinkshell1 when currentTempChannel.Value.IsCrossLinkshell():
case InputChannel.CrossLinkshell1
when currentTempChannel.Value.IsCrossLinkshell():
currentIndex = currentTempChannel.Value.LinkshellIndex();
break;
}
}
var idx = RotateLinkshell(currentIndex, rotate, channel == InputChannel.Linkshell1 ? ValidLinkshell : ValidCrossLinkshell);
var idx = RotateLinkshell(
currentIndex,
rotate,
channel == InputChannel.Linkshell1 ? ValidLinkshell : ValidCrossLinkshell
);
// RotateLinkshell returns null when no valid linkshell is found within 8 iterations.
// Forward the null so the caller can keep the existing channel instead of crashing on nullable arithmetic.
return idx is null ? null : channel + idx.Value;
@@ -427,11 +549,21 @@ internal sealed unsafe class Chat : IDisposable
if (!ValidAnyLinkshell(channel))
return;
RaptureShellModule.Instance()->ChangeChatChannel(tellTarget != null ? 17 : (int)channel, idx, target, true);
RaptureShellModule
.Instance()
->ChangeChatChannel(tellTarget != null ? 17 : (int)channel, idx, target, true);
target->Dtor(true);
}
internal void SetEurekaTellChannel(string name, string worldName, ushort worldId, ulong accountId, ulong objectId, ushort reason, bool setChatType)
internal void SetEurekaTellChannel(
string name,
string worldName,
ushort worldId,
ulong accountId,
ulong objectId,
ushort reason,
bool setChatType
)
{
// param6 is 0 for contentId and 1 for objectId
// param7 is always 0 ?
@@ -446,7 +578,17 @@ internal sealed unsafe class Chat : IDisposable
var utfName = Utf8String.FromString(name);
var utfWorld = Utf8String.FromString(worldName);
RaptureShellModule.Instance()->SetTellTargetInForay(utfName, utfWorld, worldId, accountId, objectId, reason, setChatType);
RaptureShellModule
.Instance()
->SetTellTargetInForay(
utfName,
utfWorld,
worldId,
accountId,
objectId,
reason,
setChatType
);
utfName->Dtor(true);
utfWorld->Dtor(true);
@@ -475,19 +617,30 @@ internal sealed unsafe class Chat : IDisposable
mes->Dtor(true);
}
internal void SendTell(TellReason reason, ulong contentId, string name, ushort homeWorld, byte[] message, string rawText)
internal void SendTell(
TellReason reason,
ulong contentId,
string name,
ushort homeWorld,
byte[] message,
string rawText
)
{
if (contentId == 0)
{
Plugin.ChatGui.PrintError(Language.Chat_SendTell_Error);
Plugin.Log.Warning("Tried to send a tell with ContentId being 0, sorry this is an internal error.");
Plugin.Log.Warning(
"Tried to send a tell with ContentId being 0, sorry this is an internal error."
);
return;
}
var uName = Utf8String.FromString(name);
var uMessage = Utf8String.FromSequence(message.NullTerminate());
var encoded = Utf8String.FromUtf8String(PronounModule.Instance()->ProcessString(uMessage, true));
var encoded = Utf8String.FromUtf8String(
PronounModule.Instance()->ProcessString(uMessage, true)
);
var decoded = EncodeMessage(rawText);
AutoTranslate.ReplaceWithPayload(ref decoded);
@@ -500,9 +653,28 @@ internal sealed unsafe class Chat : IDisposable
if (reason == TellReason.Direct)
reason = TellReason.Friend;
var ok = SendTellNative(networkModule, contentId, homeWorld, uName, encoded, (ushort) reason, homeWorld);
var ok = SendTellNative(
networkModule,
contentId,
homeWorld,
uName,
encoded,
(ushort)reason,
homeWorld
);
if (ok == 1)
PrintTellNative(logModule, 33, uName, &decodedUtf8String, 0, contentId, homeWorld, 255, 0, 0);
PrintTellNative(
logModule,
33,
uName,
&decodedUtf8String,
0,
contentId,
homeWorld,
255,
0,
0
);
else
Plugin.ChatGui.PrintError(Language.Chat_SendTell_Error);
@@ -511,7 +683,8 @@ internal sealed unsafe class Chat : IDisposable
uMessage->Dtor(true);
}
private static byte[] EncodeMessage(string str) {
private static byte[] EncodeMessage(string str)
{
using var input = new Utf8String(str);
using var output = new Utf8String();
@@ -524,7 +697,7 @@ internal sealed unsafe class Chat : IDisposable
{
var uC = Utf8String.FromString(c.ToString());
uC->SanitizeString((AllowedEntities) 0x27F);
uC->SanitizeString((AllowedEntities)0x27F);
var wasValid = uC->ToString().Length > 0;
uC->Dtor(true);
@@ -537,7 +710,7 @@ internal sealed unsafe class Chat : IDisposable
var ok = Plugin.GameConfig.TryGet(UiConfigOption.LogNameType, out uint type);
if (!ok || !Enum.IsDefined(typeof(PlayerNameDisplayType), type))
return PlayerNameDisplayType.FullName;
return (PlayerNameDisplayType) type;
return (PlayerNameDisplayType)type;
}
internal string AbbreviatePlayerName(string playerName)
@@ -557,10 +730,13 @@ internal sealed unsafe class Chat : IDisposable
return CurrentPlayerNameDisplayType switch
{
PlayerNameDisplayType.SurnameAbbreviated => $"{split.First()} {split.Last().FirstOrDefault('A')}.",
PlayerNameDisplayType.ForenameAbbreviated => $"{split.First().FirstOrDefault('A')}. {split.Last()}",
PlayerNameDisplayType.Initials => $"{split.First().FirstOrDefault('A')}. {split.Last().FirstOrDefault('A')}.",
_ => playerName
PlayerNameDisplayType.SurnameAbbreviated =>
$"{split.First()} {split.Last().FirstOrDefault('A')}.",
PlayerNameDisplayType.ForenameAbbreviated =>
$"{split.First().FirstOrDefault('A')}. {split.Last()}",
PlayerNameDisplayType.Initials =>
$"{split.First().FirstOrDefault('A')}. {split.Last().FirstOrDefault('A')}.",
_ => playerName,
};
}
@@ -571,6 +747,7 @@ internal sealed unsafe class Chat : IDisposable
// second before the cutscene actually starts, because the game sets
// the cutscene conditions before processing the skip.
var raptureAtkUnitManager = RaptureAtkUnitManager.Instance();
return raptureAtkUnitManager == null || raptureAtkUnitManager->UiFlags.HasFlag(UiFlags.Chat);
return raptureAtkUnitManager == null
|| raptureAtkUnitManager->UiFlags.HasFlag(UiFlags.Chat);
}
}
+7 -4
View File
@@ -1,8 +1,8 @@
using System.Text;
using HellionChat.Resources;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using HellionChat.Resources;
namespace HellionChat.GameFunctions;
@@ -27,7 +27,10 @@ public unsafe class ChatBox
// Utf8String->SanitizeString, which only resolves in-process. Returns the
// already-encoded bytes so SendMessage doesn't pay GetBytes twice.
// TEST-MIRROR: ../../../Hellion Build test/GameFunctions/ChatBoxTests.cs
internal static byte[] ValidateMessage(string message, Func<string, string>? sanitiserOverride = null)
internal static byte[] ValidateMessage(
string message,
Func<string, string>? sanitiserOverride = null
)
{
var bytes = Encoding.UTF8.GetBytes(message);
if (bytes.Length == 0)
@@ -47,10 +50,10 @@ public unsafe class ChatBox
{
var uText = Utf8String.FromString(text);
uText->SanitizeString((AllowedEntities) 0x27F);
uText->SanitizeString((AllowedEntities)0x27F);
var sanitised = uText->ToString();
uText->Dtor(true);
return sanitised;
}
}
}
+4 -2
View File
@@ -1,7 +1,7 @@
using HellionChat.Util;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using HellionChat.Util;
namespace HellionChat.GameFunctions;
@@ -10,7 +10,9 @@ internal sealed unsafe class Context
internal static void InviteToNoviceNetwork(string name, ushort world)
{
// can specify content id if we have it, but there's no need
InfoProxyNoviceNetwork.Instance()->InviteToNoviceNetwork(0, 0, world, name.ToTerminatedBytes());
InfoProxyNoviceNetwork
.Instance()
->InviteToNoviceNetwork(0, 0, world, name.ToTerminatedBytes());
}
internal static void TryOn(uint itemId, byte stainId)
+37 -12
View File
@@ -23,9 +23,17 @@ internal unsafe class GameFunctions : IDisposable
internal const string NewGamePlusAddonName = "QuestRedo";
#region Hooks
[Signature("E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B D0 49 8D 4F", DetourName = nameof(ResolveTextCommandPlaceholderDetour))]
[Signature(
"E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B D0 49 8D 4F",
DetourName = nameof(ResolveTextCommandPlaceholderDetour)
)]
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook = null!;
private delegate nint ResolveTextCommandPlaceholderDelegate(nint a1, byte* placeholderText, byte a3, byte a4);
private delegate nint ResolveTextCommandPlaceholderDelegate(
nint a1,
byte* placeholderText,
byte a3,
byte a4
);
#endregion
private Plugin Plugin { get; }
@@ -81,7 +89,8 @@ internal unsafe class GameFunctions : IDisposable
ChatBox.SendMessage($"/{commandName} add {Placeholder}");
}
private static T* GetAddon<T>(string name) where T : unmanaged
private static T* GetAddon<T>(string name)
where T : unmanaged
{
var addon = RaptureAtkModule.Instance()->RaptureAtkUnitManager.GetAddonByName(name);
return addon != null && addon->IsReady ? (T*)addon : null;
@@ -164,14 +173,15 @@ internal unsafe class GameFunctions : IDisposable
{
var addonId = lfg->GetAddonId();
var atkModule = RaptureAtkModule.Instance();
var atkModuleVtbl = (void**) atkModule->AtkModule.VirtualTable;
var vf27 = (delegate* unmanaged<RaptureAtkModule*, ulong, ulong, byte>) atkModuleVtbl[27];
var atkModuleVtbl = (void**)atkModule->AtkModule.VirtualTable;
var vf27 = (delegate* unmanaged<RaptureAtkModule*, ulong, ulong, byte>)
atkModuleVtbl[27];
vf27(atkModule, addonId, 1);
}
else
{
// 6.05: 8443DD
if (*(uint*) ((nint) lfg + 0x2C20) > 0)
if (*(uint*)((nint)lfg + 0x2C20) > 0)
lfg->Hide();
else
lfg->Show();
@@ -197,7 +207,14 @@ internal unsafe class GameFunctions : IDisposable
return;
}
if (!uint.TryParse(splits[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var questId))
if (
!uint.TryParse(
splits[1],
NumberStyles.Any,
CultureInfo.InvariantCulture,
out var questId
)
)
{
Plugin.ChatGui.Print("Unable to parse quest id");
return;
@@ -239,9 +256,10 @@ internal unsafe class GameFunctions : IDisposable
{
var agent = AgentChatLog.Instance();
// case 3
var value = new AtkValue { Type = ValueType.Int, Int = 3, };
var value = new AtkValue { Type = ValueType.Int, Int = 3 };
var result = 0;
var vf0 = *(delegate* unmanaged<AgentChatLog*, int*, AtkValue*, ulong, ulong, int*>*) agent->VirtualTable;
var vf0 = *(delegate* unmanaged<AgentChatLog*, int*, AtkValue*, ulong, ulong, int*>*)
agent->VirtualTable;
vf0(agent, &result, &value, 0, 0);
}
@@ -250,7 +268,12 @@ internal unsafe class GameFunctions : IDisposable
private readonly string Placeholder = $"<{Guid.NewGuid():N}>";
private string? ReplacementName;
private nint ResolveTextCommandPlaceholderDetour(nint a1, byte* placeholderText, byte a3, byte a4)
private nint ResolveTextCommandPlaceholderDetour(
nint a1,
byte* placeholderText,
byte a3,
byte a4
)
{
// The detour is only invoked through the hook, so the hook should
// never be null here, but the nullable field declaration forces us
@@ -258,7 +281,7 @@ internal unsafe class GameFunctions : IDisposable
if (ResolveTextCommandPlaceholderHook is null)
return nint.Zero;
var placeholder = MemoryHelper.ReadStringNullTerminated((nint) placeholderText);
var placeholder = MemoryHelper.ReadStringNullTerminated((nint)placeholderText);
if (ReplacementName == null || placeholder != Placeholder)
return ResolveTextCommandPlaceholderHook.Original(a1, placeholderText, a3, a4);
@@ -268,7 +291,9 @@ internal unsafe class GameFunctions : IDisposable
var byteCount = System.Text.Encoding.UTF8.GetByteCount(ReplacementName);
if (byteCount >= PlaceholderBufferSize)
{
Plugin.Log.Warning($"Replacement name too long for placeholder buffer ({byteCount} bytes >= {PlaceholderBufferSize}); falling back to original.");
Plugin.Log.Warning(
$"Replacement name too long for placeholder buffer ({byteCount} bytes >= {PlaceholderBufferSize}); falling back to original."
);
ReplacementName = null;
return ResolveTextCommandPlaceholderHook.Original(a1, placeholderText, a3, a4);
}
+136 -90
View File
@@ -1,23 +1,25 @@
using System.Numerics;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Config;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Util;
using ModifierFlag = HellionChat.GameFunctions.Types.ModifierFlag;
namespace HellionChat.GameFunctions;
internal enum KeyboardSource {
internal enum KeyboardSource
{
Game,
ImGui
ImGui,
}
internal unsafe class KeybindManager : IDisposable {
internal unsafe class KeybindManager : IDisposable
{
private Plugin Plugin { get; }
internal bool DirectChat;
@@ -26,70 +28,79 @@ internal unsafe class KeybindManager : IDisposable {
private bool VanillaTextInputHasFocus;
private readonly Dictionary<string, Keybind> Keybinds = new();
private static readonly IReadOnlyDictionary<string, ChannelSwitchInfo> KeybindsToIntercept = new Dictionary<string, ChannelSwitchInfo>
{
["CMD_CHAT"] = new(null),
["CMD_COMMAND"] = new(null, text: "/"),
["CMD_REPLY"] = new(InputChannel.Tell, rotate: RotateMode.Forward),
["CMD_REPLY_REV"] = new(InputChannel.Tell, rotate: RotateMode.Reverse),
["CMD_SAY"] = new(InputChannel.Say),
["CMD_YELL"] = new(InputChannel.Yell),
["CMD_SHOUT"] = new(InputChannel.Shout),
["CMD_PARTY"] = new(InputChannel.Party),
["CMD_ALLIANCE"] = new(InputChannel.Alliance),
["CMD_FREECOM"] = new(InputChannel.FreeCompany),
["PVPTEAM_CHAT"] = new(InputChannel.PvpTeam),
["CMD_CWLINKSHELL"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Forward),
["CMD_CWLINKSHELL_REV"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Reverse),
["CMD_CWLINKSHELL_1"] = new(InputChannel.CrossLinkshell1),
["CMD_CWLINKSHELL_2"] = new(InputChannel.CrossLinkshell2),
["CMD_CWLINKSHELL_3"] = new(InputChannel.CrossLinkshell3),
["CMD_CWLINKSHELL_4"] = new(InputChannel.CrossLinkshell4),
["CMD_CWLINKSHELL_5"] = new(InputChannel.CrossLinkshell5),
["CMD_CWLINKSHELL_6"] = new(InputChannel.CrossLinkshell6),
["CMD_CWLINKSHELL_7"] = new(InputChannel.CrossLinkshell7),
["CMD_CWLINKSHELL_8"] = new(InputChannel.CrossLinkshell8),
["CMD_LINKSHELL"] = new(InputChannel.Linkshell1, rotate: RotateMode.Forward),
["CMD_LINKSHELL_REV"] = new(InputChannel.Linkshell1, rotate: RotateMode.Reverse),
["CMD_LINKSHELL_1"] = new(InputChannel.Linkshell1),
["CMD_LINKSHELL_2"] = new(InputChannel.Linkshell2),
["CMD_LINKSHELL_3"] = new(InputChannel.Linkshell3),
["CMD_LINKSHELL_4"] = new(InputChannel.Linkshell4),
["CMD_LINKSHELL_5"] = new(InputChannel.Linkshell5),
["CMD_LINKSHELL_6"] = new(InputChannel.Linkshell6),
["CMD_LINKSHELL_7"] = new(InputChannel.Linkshell7),
["CMD_LINKSHELL_8"] = new(InputChannel.Linkshell8),
["CMD_BEGINNER"] = new(InputChannel.NoviceNetwork),
["CMD_REPLY_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Forward),
["CMD_REPLY_REV_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Reverse),
["CMD_SAY_ALWAYS"] = new(InputChannel.Say, true),
["CMD_YELL_ALWAYS"] = new(InputChannel.Yell, true),
["CMD_PARTY_ALWAYS"] = new(InputChannel.Party, true),
["CMD_ALLIANCE_ALWAYS"] = new(InputChannel.Alliance, true),
["CMD_FREECOM_ALWAYS"] = new(InputChannel.FreeCompany, true),
["PVPTEAM_CHAT_ALWAYS"] = new(InputChannel.PvpTeam, true),
["CMD_CWLINKSHELL_ALWAYS"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Forward),
["CMD_CWLINKSHELL_ALWAYS_REV"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Reverse),
["CMD_CWLINKSHELL_1_ALWAYS"] = new(InputChannel.CrossLinkshell1, true),
["CMD_CWLINKSHELL_2_ALWAYS"] = new(InputChannel.CrossLinkshell2, true),
["CMD_CWLINKSHELL_3_ALWAYS"] = new(InputChannel.CrossLinkshell3, true),
["CMD_CWLINKSHELL_4_ALWAYS"] = new(InputChannel.CrossLinkshell4, true),
["CMD_CWLINKSHELL_5_ALWAYS"] = new(InputChannel.CrossLinkshell5, true),
["CMD_CWLINKSHELL_6_ALWAYS"] = new(InputChannel.CrossLinkshell6, true),
["CMD_CWLINKSHELL_7_ALWAYS"] = new(InputChannel.CrossLinkshell7, true),
["CMD_CWLINKSHELL_8_ALWAYS"] = new(InputChannel.CrossLinkshell8, true),
["CMD_LINKSHELL_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Forward),
["CMD_LINKSHELL_REV_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Reverse),
["CMD_LINKSHELL_1_ALWAYS"] = new(InputChannel.Linkshell1, true),
["CMD_LINKSHELL_2_ALWAYS"] = new(InputChannel.Linkshell2, true),
["CMD_LINKSHELL_3_ALWAYS"] = new(InputChannel.Linkshell3, true),
["CMD_LINKSHELL_4_ALWAYS"] = new(InputChannel.Linkshell4, true),
["CMD_LINKSHELL_5_ALWAYS"] = new(InputChannel.Linkshell5, true),
["CMD_LINKSHELL_6_ALWAYS"] = new(InputChannel.Linkshell6, true),
["CMD_LINKSHELL_7_ALWAYS"] = new(InputChannel.Linkshell7, true),
["CMD_LINKSHELL_8_ALWAYS"] = new(InputChannel.Linkshell8, true),
["CMD_BEGINNER_ALWAYS"] = new(InputChannel.NoviceNetwork, true)
};
private static readonly IReadOnlyDictionary<string, ChannelSwitchInfo> KeybindsToIntercept =
new Dictionary<string, ChannelSwitchInfo>
{
["CMD_CHAT"] = new(null),
["CMD_COMMAND"] = new(null, text: "/"),
["CMD_REPLY"] = new(InputChannel.Tell, rotate: RotateMode.Forward),
["CMD_REPLY_REV"] = new(InputChannel.Tell, rotate: RotateMode.Reverse),
["CMD_SAY"] = new(InputChannel.Say),
["CMD_YELL"] = new(InputChannel.Yell),
["CMD_SHOUT"] = new(InputChannel.Shout),
["CMD_PARTY"] = new(InputChannel.Party),
["CMD_ALLIANCE"] = new(InputChannel.Alliance),
["CMD_FREECOM"] = new(InputChannel.FreeCompany),
["PVPTEAM_CHAT"] = new(InputChannel.PvpTeam),
["CMD_CWLINKSHELL"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Forward),
["CMD_CWLINKSHELL_REV"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Reverse),
["CMD_CWLINKSHELL_1"] = new(InputChannel.CrossLinkshell1),
["CMD_CWLINKSHELL_2"] = new(InputChannel.CrossLinkshell2),
["CMD_CWLINKSHELL_3"] = new(InputChannel.CrossLinkshell3),
["CMD_CWLINKSHELL_4"] = new(InputChannel.CrossLinkshell4),
["CMD_CWLINKSHELL_5"] = new(InputChannel.CrossLinkshell5),
["CMD_CWLINKSHELL_6"] = new(InputChannel.CrossLinkshell6),
["CMD_CWLINKSHELL_7"] = new(InputChannel.CrossLinkshell7),
["CMD_CWLINKSHELL_8"] = new(InputChannel.CrossLinkshell8),
["CMD_LINKSHELL"] = new(InputChannel.Linkshell1, rotate: RotateMode.Forward),
["CMD_LINKSHELL_REV"] = new(InputChannel.Linkshell1, rotate: RotateMode.Reverse),
["CMD_LINKSHELL_1"] = new(InputChannel.Linkshell1),
["CMD_LINKSHELL_2"] = new(InputChannel.Linkshell2),
["CMD_LINKSHELL_3"] = new(InputChannel.Linkshell3),
["CMD_LINKSHELL_4"] = new(InputChannel.Linkshell4),
["CMD_LINKSHELL_5"] = new(InputChannel.Linkshell5),
["CMD_LINKSHELL_6"] = new(InputChannel.Linkshell6),
["CMD_LINKSHELL_7"] = new(InputChannel.Linkshell7),
["CMD_LINKSHELL_8"] = new(InputChannel.Linkshell8),
["CMD_BEGINNER"] = new(InputChannel.NoviceNetwork),
["CMD_REPLY_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Forward),
["CMD_REPLY_REV_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Reverse),
["CMD_SAY_ALWAYS"] = new(InputChannel.Say, true),
["CMD_YELL_ALWAYS"] = new(InputChannel.Yell, true),
["CMD_PARTY_ALWAYS"] = new(InputChannel.Party, true),
["CMD_ALLIANCE_ALWAYS"] = new(InputChannel.Alliance, true),
["CMD_FREECOM_ALWAYS"] = new(InputChannel.FreeCompany, true),
["PVPTEAM_CHAT_ALWAYS"] = new(InputChannel.PvpTeam, true),
["CMD_CWLINKSHELL_ALWAYS"] = new(
InputChannel.CrossLinkshell1,
true,
RotateMode.Forward
),
["CMD_CWLINKSHELL_ALWAYS_REV"] = new(
InputChannel.CrossLinkshell1,
true,
RotateMode.Reverse
),
["CMD_CWLINKSHELL_1_ALWAYS"] = new(InputChannel.CrossLinkshell1, true),
["CMD_CWLINKSHELL_2_ALWAYS"] = new(InputChannel.CrossLinkshell2, true),
["CMD_CWLINKSHELL_3_ALWAYS"] = new(InputChannel.CrossLinkshell3, true),
["CMD_CWLINKSHELL_4_ALWAYS"] = new(InputChannel.CrossLinkshell4, true),
["CMD_CWLINKSHELL_5_ALWAYS"] = new(InputChannel.CrossLinkshell5, true),
["CMD_CWLINKSHELL_6_ALWAYS"] = new(InputChannel.CrossLinkshell6, true),
["CMD_CWLINKSHELL_7_ALWAYS"] = new(InputChannel.CrossLinkshell7, true),
["CMD_CWLINKSHELL_8_ALWAYS"] = new(InputChannel.CrossLinkshell8, true),
["CMD_LINKSHELL_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Forward),
["CMD_LINKSHELL_REV_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Reverse),
["CMD_LINKSHELL_1_ALWAYS"] = new(InputChannel.Linkshell1, true),
["CMD_LINKSHELL_2_ALWAYS"] = new(InputChannel.Linkshell2, true),
["CMD_LINKSHELL_3_ALWAYS"] = new(InputChannel.Linkshell3, true),
["CMD_LINKSHELL_4_ALWAYS"] = new(InputChannel.Linkshell4, true),
["CMD_LINKSHELL_5_ALWAYS"] = new(InputChannel.Linkshell5, true),
["CMD_LINKSHELL_6_ALWAYS"] = new(InputChannel.Linkshell6, true),
["CMD_LINKSHELL_7_ALWAYS"] = new(InputChannel.Linkshell7, true),
["CMD_LINKSHELL_8_ALWAYS"] = new(InputChannel.Linkshell8, true),
["CMD_BEGINNER_ALWAYS"] = new(InputChannel.NoviceNetwork, true),
};
// List of keys that can be used as a part of keybinds while the chat is
// focused WITHOUT modifiers. All other keys can only be used if their
@@ -353,12 +364,22 @@ internal unsafe class KeybindManager : IDisposable {
return key.TryToImGui(out var imguiKey) && ImGui.IsKeyPressed(imguiKey);
}
private static bool ComboPressed(KeyboardSource source, VirtualKey key, ModifierFlag modifier, ModifierFlag? modifierState = null, bool modifiersOnly = false)
private static bool ComboPressed(
KeyboardSource source,
VirtualKey key,
ModifierFlag modifier,
ModifierFlag? modifierState = null,
bool modifiersOnly = false
)
{
// When we're in an input, we don't want to process any keybinds that
// don't have a modifier (or only use shift) and are not explicitly
// whitelisted.
if (modifiersOnly && !ModifierlessChatKeys.Contains(key) && modifier is ModifierFlag.None or ModifierFlag.Shift)
if (
modifiersOnly
&& !ModifierlessChatKeys.Contains(key)
&& modifier is ModifierFlag.None or ModifierFlag.Shift
)
return false;
modifierState ??= GetModifiers(source);
@@ -366,26 +387,43 @@ internal unsafe class KeybindManager : IDisposable {
{
KeybindMode.Strict => modifier == modifierState.Value,
KeybindMode.Flexible => modifierState.Value.HasFlag(modifier),
_ => false
_ => false,
};
return KeyPressed(source, key) && modifierPressed;
}
private static bool ConfigKeybindPressed(KeyboardSource source, ConfigKeyBind? bind, ModifierFlag? modifierState = null, bool modifiersOnly = false)
private static bool ConfigKeybindPressed(
KeyboardSource source,
ConfigKeyBind? bind,
ModifierFlag? modifierState = null,
bool modifiersOnly = false
)
{
return bind != null && ComboPressed(source, bind.Key, bind.Modifier, modifierState: modifierState, modifiersOnly: modifiersOnly);
return bind != null
&& ComboPressed(
source,
bind.Key,
bind.Modifier,
modifierState: modifierState,
modifiersOnly: modifiersOnly
);
}
private void HandleKeybinds(IFramework _ ) => HandleKeybinds(KeyboardSource.Game);
private void HandleKeybinds(IFramework _) => HandleKeybinds(KeyboardSource.Game);
internal void HandleKeybinds(KeyboardSource source, bool ignoreChatOpen = false, bool modifiersOnly = false)
internal void HandleKeybinds(
KeyboardSource source,
bool ignoreChatOpen = false,
bool modifiersOnly = false
)
{
// Refresh current keybinds every 5s
if (LastRefresh + 5 * 1000 < Environment.TickCount64)
{
UpdateKeybinds();
DirectChat = Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option;
DirectChat =
Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option;
LastRefresh = Environment.TickCount64;
}
@@ -433,10 +471,18 @@ internal unsafe class KeybindManager : IDisposable {
void Intercept(VirtualKey vk, ModifierFlag modifier)
{
if (!ComboPressed(source, vk, modifier, modifierState: modifierState, modifiersOnly: modifiersOnly))
if (
!ComboPressed(
source,
vk,
modifier,
modifierState: modifierState,
modifiersOnly: modifiersOnly
)
)
return;
var bits = BitOperations.PopCount((uint) modifier);
var bits = BitOperations.PopCount((uint)modifier);
if (bits < currentBest.Item3)
return;
@@ -457,7 +503,7 @@ internal unsafe class KeybindManager : IDisposable {
try
{
TellReason? reason = info.Channel == InputChannel.Tell ? TellReason.Reply : null;
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(info) { TellReason = reason, });
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(info) { TellReason = reason });
}
catch (Exception ex)
{
@@ -494,11 +540,11 @@ internal unsafe class KeybindManager : IDisposable {
var key2 = outData.KeySettings[1];
return new Keybind
{
Key1 = RemapInvalidVirtualKey((VirtualKey) key1.Key),
Modifier1 = (ModifierFlag) key1.KeyModifier,
Key1 = RemapInvalidVirtualKey((VirtualKey)key1.Key),
Modifier1 = (ModifierFlag)key1.KeyModifier,
Key2 = RemapInvalidVirtualKey((VirtualKey) key2.Key),
Modifier2 = (ModifierFlag) key2.KeyModifier,
Key2 = RemapInvalidVirtualKey((VirtualKey)key2.Key),
Modifier2 = (ModifierFlag)key2.KeyModifier,
};
}
@@ -506,9 +552,9 @@ internal unsafe class KeybindManager : IDisposable {
{
return key switch
{
VirtualKey.F23 => VirtualKey.OEM_2, // /?
(VirtualKey) 140 => VirtualKey.OEM_7, // '"
_ => key
VirtualKey.F23 => VirtualKey.OEM_2, // /?
(VirtualKey)140 => VirtualKey.OEM_7, // '"
_ => key,
};
}
}
}
+8 -5
View File
@@ -1,8 +1,8 @@
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.GameFunctions;
@@ -11,7 +11,8 @@ internal static unsafe class Party
internal static void InviteSameWorld(string name, ushort world, ulong contentId)
{
// this only works if target is on the same world
fixed (byte* namePtr = name.ToTerminatedBytes()) {
fixed (byte* namePtr = name.ToTerminatedBytes())
{
InfoProxyPartyInvite.Instance()->InviteToParty(contentId, namePtr, world);
}
}
@@ -44,14 +45,16 @@ internal static unsafe class Party
internal static void Kick(string name, ulong contentId)
{
fixed (byte* namePtr = name.ToTerminatedBytes()) {
fixed (byte* namePtr = name.ToTerminatedBytes())
{
AgentPartyMember.Instance()->Kick(namePtr, 0, contentId);
}
}
internal static void Promote(string name, ulong contentId)
{
fixed (byte* namePtr = name.ToTerminatedBytes()) {
fixed (byte* namePtr = name.ToTerminatedBytes())
{
AgentPartyMember.Instance()->Promote(namePtr, 0, contentId);
}
}
@@ -2,13 +2,19 @@ using HellionChat.Code;
namespace HellionChat.GameFunctions.Types;
internal class ChannelSwitchInfo {
internal class ChannelSwitchInfo
{
internal InputChannel? Channel { get; }
internal bool Permanent { get; }
internal RotateMode Rotate { get; }
internal string? Text { get; }
internal ChannelSwitchInfo(InputChannel? channel, bool permanent = false, RotateMode rotate = RotateMode.None, string? text = null)
internal ChannelSwitchInfo(
InputChannel? channel,
bool permanent = false,
RotateMode rotate = RotateMode.None,
string? text = null
)
{
Channel = channel;
Permanent = permanent;
@@ -19,14 +19,14 @@ public class TellTarget
Reason = reason;
}
public bool IsSet()
=> !string.IsNullOrEmpty(Name) && World > 0;
public bool IsSet() => !string.IsNullOrEmpty(Name) && World > 0;
public string ToWorldString()
=> Sheets.WorldSheet.TryGetRow(World, out var worldRow) ? worldRow.Name.ToString() : string.Empty;
public string ToWorldString() =>
Sheets.WorldSheet.TryGetRow(World, out var worldRow)
? worldRow.Name.ToString()
: string.Empty;
public string ToTargetString()
=> $"{Name}@{ToWorldString()}";
public string ToTargetString() => $"{Name}@{ToWorldString()}";
public unsafe void FromTarget(IPlayerCharacter target)
{
@@ -39,5 +39,6 @@ public class TellTarget
}
public static TellTarget Empty() => new(string.Empty, 0, 0, TellReason.Direct);
public static TellTarget From(TellTarget t) => new(t.Name, t.World, t.ContentId, t.Reason);
}
-2
View File
@@ -80,7 +80,6 @@
</EmbeddedResource>
</ItemGroup>
<!-- Plugin icon. Copy images/* into the build output so Dalamud
finds the icon next to the DLL, and let the SDK default
DalamudPackager pipeline include the same path in the
@@ -94,5 +93,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+178 -178
View File
@@ -2,220 +2,220 @@ name: Hellion Chat
author: JonKazama-Hellion
punchline: Chat replacement with privacy controls aligned to EU, US and JP rules — based on Chat 2 (EUPL-1.2)
description: |-
Hellion Chat is a privacy-focused chat replacement for FINAL FANTASY XIV
based on the Chat 2 codebase (EUPL-1.2). One feature is intentionally
removed (the optional webinterface) and a stack of privacy controls is
added on top. Tabs, channel filters, RGB colours, emotes, screenshot
mode, IPC integration and the chat replacement window itself work the
same. The webinterface is intentionally not part of Hellion Chat because
it serves a different use case from the smaller default footprint this
plugin is built around.
Hellion Chat is a privacy-focused chat replacement for FINAL FANTASY XIV
based on the Chat 2 codebase (EUPL-1.2). One feature is intentionally
removed (the optional webinterface) and a stack of privacy controls is
added on top. Tabs, channel filters, RGB colours, emotes, screenshot
mode, IPC integration and the chat replacement window itself work the
same. The webinterface is intentionally not part of Hellion Chat because
it serves a different use case from the smaller default footprint this
plugin is built around.
On top of that, Hellion Chat adds privacy and data-handling controls
designed to align with the modern data protection rules that apply
across the EU, the United States and Japan. By default only your own
conversations are stored; messages from strangers, NPCs and system
spam stay out of the database. Retention windows are configurable per
channel, history can be wiped retroactively, and stored data can be
exported on demand.
On top of that, Hellion Chat adds privacy and data-handling controls
designed to align with the modern data protection rules that apply
across the EU, the United States and Japan. By default only your own
conversations are stored; messages from strangers, NPCs and system
spam stay out of the database. Retention windows are configurable per
channel, history can be wiped retroactively, and stored data can be
exported on demand.
Key privacy and data-handling features:
Key privacy and data-handling features:
- Channel whitelist with a Privacy-First default
- Per-channel retention with a daily background sweep
- Retroactive cleanup with a Ctrl+Shift confirm
- Export to Markdown, JSON or CSV
- First-run wizard with three preset profiles (Privacy-First, Casual,
Full History)
- Bilingual UI (English and German) with live language switching
- Independent plugin state — own config file and database directory,
so Hellion Chat does not share state with upstream Chat 2
- Channel whitelist with a Privacy-First default
- Per-channel retention with a daily background sweep
- Retroactive cleanup with a Ctrl+Shift confirm
- Export to Markdown, JSON or CSV
- First-run wizard with three preset profiles (Privacy-First, Casual,
Full History)
- Bilingual UI (English and German) with live language switching
- Independent plugin state — own config file and database directory,
so Hellion Chat does not share state with upstream Chat 2
v1.3.0 First plugin integration cycle. Honorific custom titles
are shown in the chat header above the message log, with auto-detect
and silent fallback when Honorific is not installed.
v1.3.0 First plugin integration cycle. Honorific custom titles
are shown in the chat header above the message log, with auto-detect
and silent fallback when Honorific is not installed.
v1.4.0 — Critical Lifecycle Fixes. Plugin reload and shutdown
are cleaner: SQLite no longer leans on GC pressure to release
its file, worker threads are explicitly background, deferred
config saves no longer get lost mid-disable, and pre-v13 config
backups carry the user's custom theme opacity into the v14 schema
instead of falling back to the default.
v1.4.0 — Critical Lifecycle Fixes. Plugin reload and shutdown
are cleaner: SQLite no longer leans on GC pressure to release
its file, worker threads are explicitly background, deferred
config saves no longer get lost mid-disable, and pre-v13 config
backups carry the user's custom theme opacity into the v14 schema
instead of falling back to the default.
v1.4.1 — Theme Engine Performance plus a tenth built-in.
HellionStyle.PushGlobal reads pre-computed ABGR values from a
per-theme cache instead of converting RGBA per slot per frame
(~13 % render-time recovery in typical scenes). Custom-theme
hot-reload survives transient file locks (editor mid-save
keeps the last-known-good snapshot). Synthwave Sunset joins
as the tenth built-in theme — Hot Magenta + Cyan on midnight
violet, 80s neon-grid vibes.
v1.4.1 — Theme Engine Performance plus a tenth built-in.
HellionStyle.PushGlobal reads pre-computed ABGR values from a
per-theme cache instead of converting RGBA per slot per frame
(~13 % render-time recovery in typical scenes). Custom-theme
hot-reload survives transient file locks (editor mid-save
keeps the last-known-good snapshot). Synthwave Sunset joins
as the tenth built-in theme — Hot Magenta + Cyan on midnight
violet, 80s neon-grid vibes.
v1.4.2 — ChatLog Frame-Hot-Path. Three per-frame allocation
patterns gone from the chat-log render path: card-mode borders
hoist invariants out of the per-message loop, auto-tell tab
tint and icon get a per-tab cache, and the status bar gates
its tab aggregation behind the same one-second cache it uses
for the format strings.
v1.4.2 — ChatLog Frame-Hot-Path. Three per-frame allocation
patterns gone from the chat-log render path: card-mode borders
hoist invariants out of the per-message loop, auto-tell tab
tint and icon get a per-tab cache, and the status bar gates
its tab aggregation behind the same one-second cache it uses
for the format strings.
v1.4.3 — Plugin-Load Async-Init plus Repo-Cutover. Plugin
migrated to Dalamud's IAsyncDalamudPlugin so the heavy work
(migrations, service allocations, window construction, hook
subscription) runs in LoadAsync without blocking Dalamud's
UI. Schema-gate replaces the v9 → v16 migration chain;
configs on schema v16+ load directly. Custom-repo URL moves
to gitea.hellion-forge.cloud, the GitHub repo stays as a
frozen v1.4.2 snapshot.
v1.4.3 — Plugin-Load Async-Init plus Repo-Cutover. Plugin
migrated to Dalamud's IAsyncDalamudPlugin so the heavy work
(migrations, service allocations, window construction, hook
subscription) runs in LoadAsync without blocking Dalamud's
UI. Schema-gate replaces the v9 → v16 migration chain;
configs on schema v16+ load directly. Custom-repo URL moves
to gitea.hellion-forge.cloud, the GitHub repo stays as a
frozen v1.4.2 snapshot.
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
Modding & support: join the Hellion Forge Discord at
https://discord.gg/X9V7Kcv5gR — community for Hellion Chat and
other Hellion Online Media plugins/tools.
Modding & support: join the Hellion Forge Discord at
https://discord.gg/X9V7Kcv5gR — community for Hellion Chat and
other Hellion Online Media plugins/tools.
repo_url: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat
accepts_feedback: true
icon_url: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png
image_urls:
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/chatWindow.png
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/settingsOverview.png
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/themesPicker.png
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/chatWindow.png
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/settingsOverview.png
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/themesPicker.png
tags:
- Social
- UI
- Chat
- Replacement
- Privacy
- Social
- UI
- Chat
- Replacement
- Privacy
changelog: |-
**Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover (2026-05-08)**
**Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover (2026-05-08)**
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin`
API. The constructor now does only the bootstrap-essentials
(config load, language init, conflict detection); migrations,
service allocations, window construction and hook subscription
move to LoadAsync. Dalamud can keep its UI responsive while the
heavy work runs.
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin`
API. The constructor now does only the bootstrap-essentials
(config load, language init, conflict detection); migrations,
service allocations, window construction and hook subscription
move to LoadAsync. Dalamud can keep its UI responsive while the
heavy work runs.
- IAsyncDalamudPlugin two-phase load with per-line CaptureFailure
in DisposeAsync (mirrors LightlessSync's pattern); idempotency
guard protects against reload races
- Schema-gate replaces the v9 → v16 migration chain. Configs
on schema v16+ load directly; older configs trigger an
"install v1.4.2 first" error so the historic migration
path stays intact
- AutoTranslate.PreloadCache moved off the load path. First
use may have a sub-second hitch instead of every-load; the
upstream chose differently, we accept first-use latency
- FontManager.BuildFonts is called sync at the start of
LoadAsync; Dalamud rebuilds the font atlas on its own
pipeline so the custom Hellion-Exo2 font appears with a
brief font-pop after load (matches ChatTwo's behaviour)
- Custom-repo URL moved to gitea.hellion-forge.cloud/
JonKazama-Hellion/HellionChat. GitHub repo stays as a
frozen v1.4.2 snapshot; new releases ship from Gitea.
Existing testers need to update the custom-repo URL once
- Plugin-load time in this release sits at ~3.7 s median
(5 reloads), comparable to v1.4.2. Async migration is
foundational for v1.4.4 Lazy-Init optimisations rather
than an immediate user-perceived win
- IAsyncDalamudPlugin two-phase load with per-line CaptureFailure
in DisposeAsync (mirrors LightlessSync's pattern); idempotency
guard protects against reload races
- Schema-gate replaces the v9 → v16 migration chain. Configs
on schema v16+ load directly; older configs trigger an
"install v1.4.2 first" error so the historic migration
path stays intact
- AutoTranslate.PreloadCache moved off the load path. First
use may have a sub-second hitch instead of every-load; the
upstream chose differently, we accept first-use latency
- FontManager.BuildFonts is called sync at the start of
LoadAsync; Dalamud rebuilds the font atlas on its own
pipeline so the custom Hellion-Exo2 font appears with a
brief font-pop after load (matches ChatTwo's behaviour)
- Custom-repo URL moved to gitea.hellion-forge.cloud/
JonKazama-Hellion/HellionChat. GitHub repo stays as a
frozen v1.4.2 snapshot; new releases ship from Gitea.
Existing testers need to update the custom-repo URL once
- Plugin-load time in this release sits at ~3.7 s median
(5 reloads), comparable to v1.4.2. Async migration is
foundational for v1.4.4 Lazy-Init optimisations rather
than an immediate user-perceived win
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
**Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path**
**Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path**
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame
allocations from the chat-log render path eliminated.
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame
allocations from the chat-log render path eliminated.
- DrawMessages card-mode hoists theme/drawList/winLeft/winRight/
borderColorAbgr out of the per-message loop. About 500
redundant calls per frame at 100 visible messages, multiplied
by every pop-out window
- Auto-tell tab tint and icon use a per-tab cache. Hash
computation and string allocation only happen when the tell
target name or world drifts. AutoTellTabTint stays a pure
hash helper; cache lives in a thin TabTintCache wrapper
- Status bar gates its tab aggregation behind the same
one-second cache it already used for the format strings.
LINQ Sum and Count replaced with a single foreach pass
that runs on roughly 1% of frames
- DrawMessages card-mode hoists theme/drawList/winLeft/winRight/
borderColorAbgr out of the per-message loop. About 500
redundant calls per frame at 100 visible messages, multiplied
by every pop-out window
- Auto-tell tab tint and icon use a per-tab cache. Hash
computation and string allocation only happen when the tell
target name or world drifts. AutoTellTabTint stays a pure
hash helper; cache lives in a thin TabTintCache wrapper
- Status bar gates its tab aggregation behind the same
one-second cache it already used for the format strings.
LINQ Sum and Count replaced with a single foreach pass
that runs on roughly 1% of frames
Realistic frame-time recovery: 2-5% in typical scenes, more
on pop-out-heavy setups because the card-border hoist scales
per window.
Realistic frame-time recovery: 2-5% in typical scenes, more
on pop-out-heavy setups because the card-border hoist scales
per window.
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
**Hellion Chat 1.4.1 — Theme Engine Performance**
**Hellion Chat 1.4.1 — Theme Engine Performance**
Second sub-patch of the v1.4.x Polish Sweep series. Heap
pressure from the theme engine's per-frame render path
removed, plus a tenth built-in theme and hardening for
the custom-theme hot-reload.
Second sub-patch of the v1.4.x Polish Sweep series. Heap
pressure from the theme engine's per-frame render path
removed, plus a tenth built-in theme and hardening for
the custom-theme hot-reload.
- Theme records carry a pre-computed ABGR-packed cache
for every color slot; cache is filled when the theme
is registered and refreshed defensively on every
Switch()
- HellionStyle.PushGlobal reads ABGR values from the
cache instead of calling ColourUtil.RgbaToAbgr per
slot per frame; ~13 % render-time recovery measured
in typical scenes (plan estimate was 26 %, real
~1015 %)
- ThemeRegistry custom-theme reload distinguishes a
recoverable file lock (editor mid-save) from a
permanent IO failure; locked themes keep their
last-known-good snapshot and retry on the next
lookup instead of dropping out of the picker
- New built-in: Synthwave Sunset — Hot Magenta + Cyan
on midnight violet, 80s neon-grid vibes; tenth theme
in the picker
- Author credits refreshed: brand themes are credited
as "Hellion Forge"; Mint Grove and Forge Merchantman
now credited to Carla Beleandis as a community thanks
- Theme records carry a pre-computed ABGR-packed cache
for every color slot; cache is filled when the theme
is registered and refreshed defensively on every
Switch()
- HellionStyle.PushGlobal reads ABGR values from the
cache instead of calling ColourUtil.RgbaToAbgr per
slot per frame; ~13 % render-time recovery measured
in typical scenes (plan estimate was 26 %, real
~1015 %)
- ThemeRegistry custom-theme reload distinguishes a
recoverable file lock (editor mid-save) from a
permanent IO failure; locked themes keep their
last-known-good snapshot and retry on the next
lookup instead of dropping out of the picker
- New built-in: Synthwave Sunset — Hot Magenta + Cyan
on midnight violet, 80s neon-grid vibes; tenth theme
in the picker
- Author credits refreshed: brand themes are credited
as "Hellion Forge"; Mint Grove and Forge Merchantman
now credited to Carla Beleandis as a community thanks
No schema bump, no user-visible behaviour change other
than smoother frames on GC-sensitive setups and one
additional colour option.
No schema bump, no user-visible behaviour change other
than smoother frames on GC-sensitive setups and one
additional colour option.
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
**Hellion Chat 1.4.0 — Critical Lifecycle Fixes**
**Hellion Chat 1.4.0 — Critical Lifecycle Fixes**
First sub-patch of the v1.4.x Polish Sweep series. Seven
known lifecycle and race bugs eliminated before any
performance refactor sits on top.
First sub-patch of the v1.4.x Polish Sweep series. Seven
known lifecycle and race bugs eliminated before any
performance refactor sits on top.
- MessageStore disposal no longer triggers GC.Collect
globally; Pooling=false on the SQLite connection means
there's nothing left to clean up by hand
- PendingMessage and RetentionSweep worker threads are
explicitly marked IsBackground=true so the plugin domain
can unload during XIVLauncher reload without waiting
for them
- EmoteCache image and gif loaders moved from async-void
to async Task with a shared task tracker, draining
on Dispose so an in-flight load can no longer write
to a disposed EmoteImages entry
- DisposeAsync 10s timeout now warns loudly instead of
silently leaving the worker behind
- Plugin.Dispose flushes any pending DeferredSaveFrames
before tearing services down, so settings changes
made in the last few frames before disable are no
longer lost
- The v13→v14 config migration now reads the pre-v13
backup and carries HellionThemeWindowOpacity into the
new WindowOpacity field instead of falling back to
the default 0.85
- MessageStore disposal no longer triggers GC.Collect
globally; Pooling=false on the SQLite connection means
there's nothing left to clean up by hand
- PendingMessage and RetentionSweep worker threads are
explicitly marked IsBackground=true so the plugin domain
can unload during XIVLauncher reload without waiting
for them
- EmoteCache image and gif loaders moved from async-void
to async Task with a shared task tracker, draining
on Dispose so an in-flight load can no longer write
to a disposed EmoteImages entry
- DisposeAsync 10s timeout now warns loudly instead of
silently leaving the worker behind
- Plugin.Dispose flushes any pending DeferredSaveFrames
before tearing services down, so settings changes
made in the last few frames before disable are no
longer lost
- The v13→v14 config migration now reads the pre-v13
backup and carries HellionThemeWindowOpacity into the
new WindowOpacity field instead of falling back to
the default 0.85
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
---
---
Earlier history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
Earlier history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
+33 -21
View File
@@ -39,7 +39,11 @@ internal sealed class HonorificService : IDisposable
public bool IsAvailable { get; private set; }
public (uint Major, uint Minor)? DetectedApiVersion { get; private set; }
public HonorificService(IDalamudPluginInterface pluginInterface, IPluginLog log, IFramework framework)
public HonorificService(
IDalamudPluginInterface pluginInterface,
IPluginLog log,
IFramework framework
)
{
_framework = framework;
_log = log;
@@ -61,16 +65,15 @@ internal sealed class HonorificService : IDisposable
// 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
.GetIpcSubscriber<string>($"{IpcNamespace}.GetLocalCharacterTitle");
_localCharacterTitleChanged = pluginInterface
.GetIpcSubscriber<string, object>($"{IpcNamespace}.LocalCharacterTitleChanged");
_ready = pluginInterface
.GetIpcSubscriber<object>($"{IpcNamespace}.Ready");
_disposing = pluginInterface
.GetIpcSubscriber<object>($"{IpcNamespace}.Disposing");
_apiVersion = pluginInterface.GetIpcSubscriber<(uint, uint)>($"{IpcNamespace}.ApiVersion");
_getLocalCharacterTitle = pluginInterface.GetIpcSubscriber<string>(
$"{IpcNamespace}.GetLocalCharacterTitle"
);
_localCharacterTitleChanged = pluginInterface.GetIpcSubscriber<string, object>(
$"{IpcNamespace}.LocalCharacterTitleChanged"
);
_ready = pluginInterface.GetIpcSubscriber<object>($"{IpcNamespace}.Ready");
_disposing = pluginInterface.GetIpcSubscriber<object>($"{IpcNamespace}.Disposing");
_localCharacterTitleChanged.Subscribe(OnTitleChanged);
_ready.Subscribe(OnReady);
@@ -103,9 +106,11 @@ internal sealed class HonorificService : IDisposable
if (!_versionWarningLogged)
{
_log.Warning(
"Honorific API version mismatch — expected major 3, " +
"found {Major}.{Minor}. Disabling Honorific integration.",
version.Item1, version.Item2);
"Honorific API version mismatch — expected major 3, "
+ "found {Major}.{Minor}. Disabling Honorific integration.",
version.Item1,
version.Item2
);
_versionWarningLogged = true;
}
IsAvailable = false;
@@ -142,7 +147,8 @@ internal sealed class HonorificService : IDisposable
// 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;
if (!IsAvailable)
return;
CurrentTitle = ParseTitleJson(json);
}
@@ -233,13 +239,19 @@ internal sealed class HonorificService : IDisposable
internal static bool ShouldRenderSlot(
bool toggleEnabled,
bool isAvailable,
HonorificTitleData? title)
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;
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;
}
}
+21 -6
View File
@@ -15,8 +15,14 @@ public sealed class ExtraChat : IDisposable
#pragma warning restore CS0649
private ICallGateSubscriber<OverrideInfo, object> OverrideChannelGate { get; }
private ICallGateSubscriber<Dictionary<string, uint>, Dictionary<string, uint>> ChannelCommandColoursGate { get; }
private ICallGateSubscriber<Dictionary<Guid, string>, Dictionary<Guid, string>> ChannelNamesGate { get; }
private ICallGateSubscriber<
Dictionary<string, uint>,
Dictionary<string, uint>
> ChannelCommandColoursGate { get; }
private ICallGateSubscriber<
Dictionary<Guid, string>,
Dictionary<Guid, string>
> ChannelNamesGate { get; }
internal (string, uint)? ChannelOverride { get; set; }
@@ -25,16 +31,25 @@ public sealed class ExtraChat : IDisposable
// Reference assignment is atomic on x64, but the JIT (especially Mono on Wine/Linux) needs
// the volatile barrier to guarantee visibility across threads. See AUDIT-2026-05-05 [SEC-01].
private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
internal IReadOnlyDictionary<string, uint> ChannelCommandColours => ChannelCommandColoursInternal;
internal IReadOnlyDictionary<string, uint> ChannelCommandColours =>
ChannelCommandColoursInternal;
private volatile Dictionary<Guid, string> ChannelNamesInternal = new();
internal IReadOnlyDictionary<Guid, string> ChannelNames => ChannelNamesInternal;
internal ExtraChat()
{
OverrideChannelGate = Plugin.Interface.GetIpcSubscriber<OverrideInfo, object>("ExtraChat.OverrideChannelColour");
ChannelCommandColoursGate = Plugin.Interface.GetIpcSubscriber<Dictionary<string, uint>, Dictionary<string, uint>>("ExtraChat.ChannelCommandColours");
ChannelNamesGate = Plugin.Interface.GetIpcSubscriber<Dictionary<Guid, string>, Dictionary<Guid, string>>("ExtraChat.ChannelNames");
OverrideChannelGate = Plugin.Interface.GetIpcSubscriber<OverrideInfo, object>(
"ExtraChat.OverrideChannelColour"
);
ChannelCommandColoursGate = Plugin.Interface.GetIpcSubscriber<
Dictionary<string, uint>,
Dictionary<string, uint>
>("ExtraChat.ChannelCommandColours");
ChannelNamesGate = Plugin.Interface.GetIpcSubscriber<
Dictionary<Guid, string>,
Dictionary<Guid, string>
>("ExtraChat.ChannelNames");
OverrideChannelGate.Subscribe(OnOverrideChannel);
ChannelCommandColoursGate.Subscribe(OnChannelCommandColours);
+23 -9
View File
@@ -1,9 +1,16 @@
using HellionChat.Code;
using Dalamud.Plugin.Ipc;
using HellionChat.Code;
namespace HellionChat.Ipc;
using ChatInputState = (bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType);
using ChatInputState = (
bool InputVisible,
bool InputFocused,
bool HasText,
bool IsTyping,
int TextLength,
ChatType ChannelType
);
internal sealed class TypingIpc : IDisposable
{
@@ -19,8 +26,12 @@ internal sealed class TypingIpc : IDisposable
{
Plugin = plugin;
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>("HellionChat.GetChatInputState");
StateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>("HellionChat.ChatInputStateChanged");
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
"HellionChat.GetChatInputState"
);
StateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
"HellionChat.ChatInputStateChanged"
);
StateQueryGate.RegisterFunc(GetState);
}
@@ -30,19 +41,22 @@ internal sealed class TypingIpc : IDisposable
var log = Plugin.ChatLogWindow;
var usedChannel = Plugin.CurrentTab.CurrentChannel;
var inputChannel = usedChannel.UseTempChannel ? usedChannel.TempChannel : usedChannel.Channel;
var inputChannel = usedChannel.UseTempChannel
? usedChannel.TempChannel
: usedChannel.Channel;
var channelType = inputChannel.ToChatType();
return (InputVisible: !log.IsHidden,
return (
InputVisible: !log.IsHidden,
log.InputFocused,
HasText: log.Chat.Length > 0,
IsTyping: log is { InputFocused: true, Chat.Length: > 0 },
TextLength: log.Chat.Length,
ChannelType: channelType);
ChannelType: channelType
);
}
private ChatInputState GetState()
=> BuildState();
private ChatInputState GetState() => BuildState();
internal void Update()
{
+26 -3
View File
@@ -9,7 +9,15 @@ internal sealed class IpcManager : IDisposable
private ICallGateProvider<string> RegisterGate { get; }
private ICallGateProvider<string, object?> UnregisterGate { get; }
private ICallGateProvider<object?> AvailableGate { get; }
private ICallGateProvider<string, PlayerPayload?, ulong, Payload?, SeString?, SeString?, object?> InvokeGate { get; }
private ICallGateProvider<
string,
PlayerPayload?,
ulong,
Payload?,
SeString?,
SeString?,
object?
> InvokeGate { get; }
internal List<string> Registered { get; } = [];
@@ -23,12 +31,27 @@ internal sealed class IpcManager : IDisposable
UnregisterGate = Plugin.Interface.GetIpcProvider<string, object?>("HellionChat.Unregister");
UnregisterGate.RegisterAction(Unregister);
InvokeGate = Plugin.Interface.GetIpcProvider<string, PlayerPayload?, ulong, Payload?, SeString?, SeString?, object?>("HellionChat.Invoke");
InvokeGate = Plugin.Interface.GetIpcProvider<
string,
PlayerPayload?,
ulong,
Payload?,
SeString?,
SeString?,
object?
>("HellionChat.Invoke");
AvailableGate.SendMessage();
}
internal void Invoke(string id, PlayerPayload? sender, ulong contentId, Payload? payload, SeString? senderString, SeString? content)
internal void Invoke(
string id,
PlayerPayload? sender,
ulong contentId,
Payload? payload,
SeString? senderString,
SeString? content
)
{
InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
}
+120 -36
View File
@@ -1,12 +1,12 @@
using System.Text;
using HellionChat.Code;
using HellionChat.Util;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using System.Text.RegularExpressions;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using HellionChat.Code;
using HellionChat.Util;
namespace HellionChat;
@@ -33,7 +33,16 @@ public partial class Message
public Dictionary<Guid, float?> Height { get; } = new();
public Dictionary<Guid, bool> IsVisible { get; } = new();
public Message(ulong receiver, ulong contentId, ulong accountId, ChatCode code, List<Chunk> sender, List<Chunk> content, SeString senderSource, SeString contentSource)
public Message(
ulong receiver,
ulong contentId,
ulong accountId,
ChatCode code,
List<Chunk> sender,
List<Chunk> content,
SeString senderSource,
SeString contentSource
)
{
var extraChatChannel = ExtractExtraChatChannel(contentSource);
Receiver = receiver;
@@ -56,7 +65,18 @@ public partial class Message
chunk.Message = this;
}
public Message(Guid id, ulong receiver, ulong contentId, DateTimeOffset date, ChatCode code, List<Chunk> sender, List<Chunk> content, SeString senderSource, SeString contentSource, Guid extraChatChannel)
public Message(
Guid id,
ulong receiver,
ulong contentId,
DateTimeOffset date,
ChatCode code,
List<Chunk> sender,
List<Chunk> content,
SeString senderSource,
SeString contentSource,
Guid extraChatChannel
)
{
Id = id;
Receiver = receiver;
@@ -82,7 +102,11 @@ public partial class Message
return new Message(0, 0, 0, code, [], content, new SeString(), new SeString());
}
public bool Matches(Dictionary<ChatType, (ChatSource Source, ChatSource Target)> channels, bool allExtraChatChannels, HashSet<Guid> extraChatChannels)
public bool Matches(
Dictionary<ChatType, (ChatSource Source, ChatSource Target)> channels,
bool allExtraChatChannels,
HashSet<Guid> extraChatChannels
)
{
if (ExtraChatChannel != Guid.Empty)
return allExtraChatChannels || extraChatChannels.Contains(ExtraChatChannel);
@@ -90,16 +114,21 @@ public partial class Message
var source = (ChatSource)(1 << (int)Code.Source);
var target = (ChatSource)(1 << (int)Code.Target);
return Code.Type.IsGm()
|| channels.TryGetValue(Code.Type, out var sources)
&& (Code.Source is 0 || sources.Source.HasFlag(source) || sources.Target.HasFlag(target));
|| channels.TryGetValue(Code.Type, out var sources)
&& (
Code.Source is 0
|| sources.Source.HasFlag(source)
|| sources.Target.HasFlag(target)
);
}
private int GenerateHash()
{
var hash = SortCodeV2.GetHashCode()
^ ExtraChatChannel.GetHashCode()
^ string.Join("", Sender.Select(c => c.StringValue())).GetHashCode()
^ string.Join("", Content.Select(c => c.StringValue())).GetHashCode();
var hash =
SortCodeV2.GetHashCode()
^ ExtraChatChannel.GetHashCode()
^ string.Join("", Sender.Select(c => c.StringValue())).GetHashCode()
^ string.Join("", Content.Select(c => c.StringValue())).GetHashCode();
if (Plugin.Config.CollapseKeepUniqueLinks)
{
@@ -146,13 +175,15 @@ public partial class Message
}
var nextIsAutoTranslate = false;
var checkForEmotes = (Code.IsPlayerMessage() || extraChatChannel != Guid.Empty) && Plugin.Config.ShowEmotes;
var checkForEmotes =
(Code.IsPlayerMessage() || extraChatChannel != Guid.Empty) && Plugin.Config.ShowEmotes;
foreach (var chunk in oldChunks)
{
// Use as is if it's not a text chunk, it already has a payload, or is auto translate
if (chunk is not TextChunk text || chunk.Link != null || nextIsAutoTranslate)
{
nextIsAutoTranslate = chunk is IconChunk { Icon: BitmapFontIcon.AutoTranslateBegin };
nextIsAutoTranslate =
chunk is IconChunk { Icon: BitmapFontIcon.AutoTranslateBegin };
// No need to call AddChunkWithMessage here since the chunk
// already has the Message field set.
@@ -173,15 +204,23 @@ public partial class Message
var word = wordBuilder.ToString();
wordBuilder.Clear();
var wordUsed = false;
var tokenUsed = false;
if (checkForEmotes && EmoteCache.Exists(word) && !Plugin.Config.BlockedEmotes.Contains(word))
if (
checkForEmotes
&& EmoteCache.Exists(word)
&& !Plugin.Config.BlockedEmotes.Contains(word)
)
{
// Add the previous sentence before adding the emote
AddChunkWithMessage(text.NewWithStyle(chunk, sentenceBuilder.ToString()));
AddChunkWithMessage(new TextChunk(chunk.Source, EmotePayload.ResolveEmote(word), word) { FallbackColour = text.FallbackColour });
AddChunkWithMessage(
new TextChunk(chunk.Source, EmotePayload.ResolveEmote(word), word)
{
FallbackColour = text.FallbackColour,
}
);
wordUsed = true;
sentenceBuilder.Clear();
@@ -190,15 +229,31 @@ public partial class Message
if (token.TokenType == Tokenizer.TokenType.UrlString)
{
// Add the previous sentence before adding the url
AddChunkWithMessage(text.NewWithStyle(chunk.Source, chunk.Link, sentenceBuilder.Append(!wordUsed ? word : "").ToString()));
AddChunkWithMessage(
text.NewWithStyle(
chunk.Source,
chunk.Link,
sentenceBuilder.Append(!wordUsed ? word : "").ToString()
)
);
try
{
AddChunkWithMessage(text.NewWithStyle(chunk.Source, UriPayload.ResolveUri(token.Value), token.Value));
AddChunkWithMessage(
text.NewWithStyle(
chunk.Source,
UriPayload.ResolveUri(token.Value),
token.Value
)
);
}
catch (UriFormatException)
{
AddChunkWithMessage(text.NewWithStyle(chunk.Source, chunk.Link, token.Value));
Plugin.Log.Debug($"Invalid URL accepted by Regex but failed URI parsing: '{token.Value}'");
AddChunkWithMessage(
text.NewWithStyle(chunk.Source, chunk.Link, token.Value)
);
Plugin.Log.Debug(
$"Invalid URL accepted by Regex but failed URI parsing: '{token.Value}'"
);
}
wordUsed = true;
@@ -215,7 +270,12 @@ public partial class Message
}
// End of string reached, we add our leftover
AddChunkWithMessage(text.NewWithStyle(chunk, sentenceBuilder.Append(!wordUsed ? word : "").ToString()));
AddChunkWithMessage(
text.NewWithStyle(
chunk,
sentenceBuilder.Append(!wordUsed ? word : "").ToString()
)
);
}
}
@@ -281,20 +341,30 @@ public partial class Message
< 500_000 => ItemKind.Normal,
< 1_000_000 => ItemKind.Collectible,
< 2_000_000 => ItemKind.Hq,
_ => ItemKind.EventItem
_ => ItemKind.EventItem,
};
var name = kind != ItemKind.EventItem
? Sheets.ItemSheet.GetRow(item.ItemId).Name.ToString()
: Sheets.EventItemSheet.GetRow(item.ItemId).Name.ToString();
var name =
kind != ItemKind.EventItem
? Sheets.ItemSheet.GetRow(item.ItemId).Name.ToString()
: Sheets.EventItemSheet.GetRow(item.ItemId).Name.ToString();
var link = new ItemPayload(item.ItemId, kind, $"{SeIconChar.LinkMarker.ToIconChar()}{name}");
AddChunkWithMessage(text.NewWithStyle(chunk.Source, link, link.DisplayName ?? "Unknown"));
var link = new ItemPayload(
item.ItemId,
kind,
$"{SeIconChar.LinkMarker.ToIconChar()}{name}"
);
AddChunkWithMessage(
text.NewWithStyle(chunk.Source, link, link.DisplayName ?? "Unknown")
);
}
else if (split == "<status>")
{
var statusId = AgentChatLog.Instance()->ContextStatusId;
if (statusId == 0 || !Sheets.StatusSheet.TryGetRow(statusId, out var statusRow))
if (
statusId == 0
|| !Sheets.StatusSheet.TryGetRow(statusId, out var statusRow)
)
{
AddChunkWithMessage(text.NewWithStyle(chunk.Source, chunk.Link, split));
continue;
@@ -305,7 +375,7 @@ public partial class Message
{
1 => $"{SeIconChar.Buff.ToIconString()}{nameValue}",
2 => $"{SeIconChar.Debuff.ToIconString()}{nameValue}",
_ => nameValue
_ => nameValue,
};
var link = new StatusPayload(statusId);
@@ -321,13 +391,27 @@ public partial class Message
}
var mapCoords = agentMap->FlagMapMarkers[0];
var rawX = (int)(MathF.Round(mapCoords.XFloat, 3, MidpointRounding.AwayFromZero) * 1000);
var rawY = (int)(MathF.Round(mapCoords.YFloat, 3, MidpointRounding.AwayFromZero) * 1000);
var rawX = (int)(
MathF.Round(mapCoords.XFloat, 3, MidpointRounding.AwayFromZero) * 1000
);
var rawY = (int)(
MathF.Round(mapCoords.YFloat, 3, MidpointRounding.AwayFromZero) * 1000
);
var link = new MapLinkPayload(mapCoords.TerritoryId, mapCoords.MapId, rawX, rawY);
AddChunkWithMessage(text.NewWithStyle(chunk.Source, link, $"{SeIconChar.LinkMarker.ToIconChar()}{link.PlaceName} {link.CoordinateString}"));
var link = new MapLinkPayload(
mapCoords.TerritoryId,
mapCoords.MapId,
rawX,
rawY
);
AddChunkWithMessage(
text.NewWithStyle(
chunk.Source,
link,
$"{SeIconChar.LinkMarker.ToIconChar()}{link.PlaceName} {link.CoordinateString}"
)
);
}
}
catch (Exception)
{
+78 -27
View File
@@ -1,9 +1,6 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Game.Chat;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
@@ -11,6 +8,9 @@ using Dalamud.Hooking;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Lumina.Text.Expressions;
using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
@@ -68,13 +68,19 @@ internal class MessageManager : IAsyncDisposable
// IsBackground so a stuck worker never blocks plugin unload.
// Cooperative cancel via PendingThreadCancellationToken first, background flag is the safety net.
PendingMessageThread = new Thread(() => ProcessPendingMessages(PendingThreadCancellationToken.Token))
PendingMessageThread = new Thread(() =>
ProcessPendingMessages(PendingThreadCancellationToken.Token)
)
{
IsBackground = true,
};
PendingMessageThread.Start();
ContentIdResolverHook = Plugin.GameInteropProvider.HookFromAddress<RaptureLogModule.Delegates.AddMsgSourceEntry>(RaptureLogModule.MemberFunctionPointers.AddMsgSourceEntry, ContentIdResolver);
ContentIdResolverHook =
Plugin.GameInteropProvider.HookFromAddress<RaptureLogModule.Delegates.AddMsgSourceEntry>(
RaptureLogModule.MemberFunctionPointers.AddMsgSourceEntry,
ContentIdResolver
);
ContentIdResolverHook.Enable();
Plugin.ChatGui.ChatMessageUnhandled += ChatMessage;
@@ -100,9 +106,10 @@ internal class MessageManager : IAsyncDisposable
if (PendingMessageThread.IsAlive)
Plugin.Log.Warning(
"PendingMessageThread did not observe cancellation within 10s. " +
"Worker remains on a background thread; next plugin reload releases it. " +
"If this recurs, file a bug with /xllog after the previous reload.");
"PendingMessageThread did not observe cancellation within 10s. "
+ "Worker remains on a background thread; next plugin reload releases it. "
+ "If this recurs, file a bug with /xllog after the previous reload."
);
// CTS owns an unmanaged WaitHandle; dispose even if the worker is
// alive — it checks IsCancellationRequested via the linked token.
@@ -183,16 +190,20 @@ internal class MessageManager : IAsyncDisposable
// AutoTellTabsService, ein DB-Refilter würde sie nur partial
// wiederherstellen falls Tells in DB liegen, oder leer lassen wenn
// Privacy-Filter sie blockiert hat.
var pendingTabs = Plugin.Config.Tabs.Where(t => !t.IsTempTab).Select(tab => (tab, new List<Message>())).ToList();
var pendingTabs = Plugin
.Config.Tabs.Where(t => !t.IsTempTab)
.Select(tab => (tab, new List<Message>()))
.ToList();
foreach (var message in messages)
foreach (var (_, pendingMessages) in pendingTabs.Where(ptab => ptab.Item1.Matches(message)))
pendingMessages.Add(message);
foreach (var (_, pendingMessages) in pendingTabs.Where(ptab => ptab.Item1.Matches(message)))
pendingMessages.Add(message);
// Apply the messages to the chat log in one go.
foreach (var (tab, pendingMessages) in pendingTabs)
tab.Messages.AddSortPrune(pendingMessages, MessageDisplayLimit);
if (!messages.DidError) return;
if (!messages.DidError)
return;
WrapperUtil.AddNotification(Language.LoadMessages_Error, NotificationType.Error);
@@ -226,6 +237,7 @@ internal class MessageManager : IAsyncDisposable
}
public (SeString? Sender, SeString? Message) LastMessage = (null, null);
private void ChatMessage(IChatMessage message)
{
LastMessage = (message.Sender, message.Message);
@@ -254,11 +266,25 @@ internal class MessageManager : IAsyncDisposable
// message's content ID. If multiple messages are received in the same tick,
// this will be called for each message immediately after ChatMessage is
// called for each message.
private unsafe void ContentIdResolver(RaptureLogModule* agent, ulong contentId, ulong accountId, int messageIndex, ushort worldId, ushort chatType)
private unsafe void ContentIdResolver(
RaptureLogModule* agent,
ulong contentId,
ulong accountId,
int messageIndex,
ushort worldId,
ushort chatType
)
{
try
{
ContentIdResolverHook?.Original(agent, contentId, accountId, messageIndex, worldId, chatType);
ContentIdResolverHook?.Original(
agent,
contentId,
accountId,
messageIndex,
worldId,
chatType
);
if (PendingSync.Last is not { } last)
return;
@@ -273,7 +299,11 @@ internal class MessageManager : IAsyncDisposable
private void ProcessMessage(PendingMessage pendingMessage)
{
var chatCode = new ChatCode(pendingMessage.LogKind, pendingMessage.SourceKind, pendingMessage.TargetKind);
var chatCode = new ChatCode(
pendingMessage.LogKind,
pendingMessage.SourceKind,
pendingMessage.TargetKind
);
NameFormatting? formatting = null;
if (pendingMessage.Sender.Payloads.Count > 0)
@@ -282,13 +312,36 @@ internal class MessageManager : IAsyncDisposable
var senderChunks = new List<Chunk>();
if (formatting is { IsPresent: true })
{
senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.Before) { FallbackColour = chatCode.Type });
senderChunks.AddRange(ChunkUtil.ToChunks(pendingMessage.Sender, ChunkSource.Sender, chatCode.Type));
senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.After) { FallbackColour = chatCode.Type });
senderChunks.Add(
new TextChunk(ChunkSource.None, null, formatting.Before)
{
FallbackColour = chatCode.Type,
}
);
senderChunks.AddRange(
ChunkUtil.ToChunks(pendingMessage.Sender, ChunkSource.Sender, chatCode.Type)
);
senderChunks.Add(
new TextChunk(ChunkSource.None, null, formatting.After)
{
FallbackColour = chatCode.Type,
}
);
}
var contentChunks = ChunkUtil.ToChunks(pendingMessage.Content, ChunkSource.Content, chatCode.Type).ToList();
var message = new Message(CurrentContentId, pendingMessage.ContentId, pendingMessage.AccountId, chatCode, senderChunks, contentChunks, pendingMessage.Sender, pendingMessage.Content);
var contentChunks = ChunkUtil
.ToChunks(pendingMessage.Content, ChunkSource.Content, chatCode.Type)
.ToList();
var message = new Message(
CurrentContentId,
pendingMessage.ContentId,
pendingMessage.AccountId,
chatCode,
senderChunks,
contentChunks,
pendingMessage.Sender,
pendingMessage.Content
);
if (Plugin.Config.DatabaseBattleMessages || !message.Code.IsBattle())
Store.UpsertMessage(message);
@@ -296,7 +349,9 @@ internal class MessageManager : IAsyncDisposable
var currentMatches = Plugin.CurrentTab.Matches(message);
foreach (var tab in Plugin.Config.Tabs)
{
var unread = !(tab.UnreadMode == UnreadMode.Unseen && Plugin.CurrentTab != tab && currentMatches);
var unread = !(
tab.UnreadMode == UnreadMode.Unseen && Plugin.CurrentTab != tab && currentMatches
);
if (tab.Matches(message))
tab.AddMessage(message, unread);
@@ -313,16 +368,12 @@ internal class MessageManager : IAsyncDisposable
internal static NameFormatting Empty()
{
return new NameFormatting { IsPresent = false, };
return new NameFormatting { IsPresent = false };
}
internal static NameFormatting Of(string before, string after)
{
return new NameFormatting
{
Before = before,
After = after,
};
return new NameFormatting { Before = before, After = after };
}
}
+173 -61
View File
@@ -1,15 +1,14 @@
using System.Buffers;
using System.Collections;
using System.Data.Common;
using Dalamud.Game.Text.SeStringHandling;
using HellionChat.Code;
using HellionChat.Ui;
using HellionChat.Util;
using Dalamud.Game.Text.SeStringHandling;
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using Microsoft.Data.Sqlite;
using DalamudUtil = Dalamud.Utility.Util;
using Encoding = System.Text.Encoding;
@@ -36,7 +35,11 @@ internal enum PayloadMessagePackType : byte
public class PayloadMessagePackFormatter : IMessagePackFormatter<Payload?>
{
public void Serialize(ref MessagePackWriter writer, Payload? value, MessagePackSerializerOptions options)
public void Serialize(
ref MessagePackWriter writer,
Payload? value,
MessagePackSerializerOptions options
)
{
if (value == null)
{
@@ -100,14 +103,22 @@ public class PayloadMessagePackFormatter : IMessagePackFormatter<Payload?>
public class SeStringMessagePackFormatter : IMessagePackFormatter<SeString?>
{
public void Serialize(ref MessagePackWriter writer, SeString? value, MessagePackSerializerOptions options)
public void Serialize(
ref MessagePackWriter writer,
SeString? value,
MessagePackSerializerOptions options
)
{
options.Resolver.GetFormatter<List<Payload>>()!.Serialize(ref writer, value?.Payloads ?? [], options);
options
.Resolver.GetFormatter<List<Payload>>()!
.Serialize(ref writer, value?.Payloads ?? [], options);
}
public SeString Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
return new SeString(options.Resolver.GetFormatter<List<Payload>>()!.Deserialize(ref reader, options));
return new SeString(
options.Resolver.GetFormatter<List<Payload>>()!.Deserialize(ref reader, options)
);
}
}
@@ -119,8 +130,13 @@ internal class MessageStore : IDisposable
private SqliteConnection Connection { get; set; }
internal static readonly MessagePackSerializerOptions MsgPackOptions = MessagePackSerializerOptions.Standard
.WithResolver(CompositeResolver.Create([new PayloadMessagePackFormatter(), new SeStringMessagePackFormatter()], [StandardResolver.Instance]));
internal static readonly MessagePackSerializerOptions MsgPackOptions =
MessagePackSerializerOptions.Standard.WithResolver(
CompositeResolver.Create(
[new PayloadMessagePackFormatter(), new SeStringMessagePackFormatter()],
[StandardResolver.Instance]
)
);
internal MessageStore(string dbPath)
{
@@ -193,7 +209,8 @@ internal class MessageStore : IDisposable
private void Migrate0()
{
Plugin.Log.Information("Running migration 0: Creating tables");
Connection.Execute(@"
Connection.Execute(
@"
CREATE TABLE IF NOT EXISTS messages (
Id BLOB PRIMARY KEY NOT NULL, -- Guid
Receiver INTEGER NOT NULL, -- uint64 (first bits are always 0)
@@ -210,7 +227,8 @@ internal class MessageStore : IDisposable
CREATE INDEX IF NOT EXISTS idx_messages_receiver ON messages (Receiver);
CREATE INDEX IF NOT EXISTS idx_messages_date ON messages (Date);
");
"
);
SetMigrationVersion(0);
}
@@ -218,10 +236,12 @@ internal class MessageStore : IDisposable
private void Migrate1()
{
Plugin.Log.Information("Running migration 1: Adding Deleted column");
Connection.Execute(@"
Connection.Execute(
@"
-- Migration 1: Add Deleted column
ALTER TABLE messages ADD COLUMN Deleted BOOLEAN NOT NULL DEFAULT false;
");
"
);
SetMigrationVersion(1);
}
@@ -229,11 +249,13 @@ internal class MessageStore : IDisposable
private void Migrate2()
{
Plugin.Log.Information("Running migration 2: Adding Channel generated column");
Connection.Execute(@"
Connection.Execute(
@"
-- Migration 2: Add Channel generated column
ALTER TABLE messages ADD COLUMN Channel INTEGER GENERATED ALWAYS AS (Code & 0x7f) VIRTUAL;
CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages (Channel);
");
"
);
SetMigrationVersion(2);
}
@@ -263,12 +285,15 @@ internal class MessageStore : IDisposable
// user_version was never bumped, just record the version and exit.
if (ColumnExists("messages", "ChatType") && !ColumnExists("messages", "Code"))
{
Plugin.Log.Information("Migration 3: schema already migrated, only bumping user_version");
Plugin.Log.Information(
"Migration 3: schema already migrated, only bumping user_version"
);
SetMigrationVersion(3);
return;
}
Connection.Execute(@"
Connection.Execute(
@"
-- Migration 3: Fix log kinds to fit the new format
-- Add new ChatType, SourceKind, TargetKind (byte), SortCodeV2
-- Migrate OldChatColumn
@@ -293,7 +318,8 @@ internal class MessageStore : IDisposable
ALTER TABLE messages DROP COLUMN Channel;
ALTER TABLE messages DROP COLUMN Code;
ALTER TABLE messages DROP COLUMN SortCode;
");
"
);
SetMigrationVersion(3);
}
@@ -325,7 +351,8 @@ internal class MessageStore : IDisposable
{
var result = new Dictionary<int, long>();
using var cmd = Connection.CreateCommand();
cmd.CommandText = "SELECT ChatType, COUNT(*) FROM messages WHERE deleted = false GROUP BY ChatType;";
cmd.CommandText =
"SELECT ChatType, COUNT(*) FROM messages WHERE deleted = false GROUP BY ChatType;";
cmd.CommandTimeout = 120;
using var reader = cmd.ExecuteReader();
while (reader.Read())
@@ -343,13 +370,22 @@ internal class MessageStore : IDisposable
/// computed from "now" at call time. Runs VACUUM only if anything was
/// removed. Returns the number of rows deleted.
/// </summary>
internal long DeleteByRetentionPolicy(IReadOnlyDictionary<int, int> chatTypeDaysMap, int defaultDays)
internal long DeleteByRetentionPolicy(
IReadOnlyDictionary<int, int> chatTypeDaysMap,
int defaultDays
)
{
if (defaultDays < 0)
throw new ArgumentOutOfRangeException(nameof(defaultDays), "Negative retention is not allowed.");
throw new ArgumentOutOfRangeException(
nameof(defaultDays),
"Negative retention is not allowed."
);
foreach (var (_, days) in chatTypeDaysMap)
if (days < 0)
throw new ArgumentOutOfRangeException(nameof(chatTypeDaysMap), "Negative retention is not allowed.");
throw new ArgumentOutOfRangeException(
nameof(chatTypeDaysMap),
"Negative retention is not allowed."
);
var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@@ -381,10 +417,13 @@ internal class MessageStore : IDisposable
var defaultCutoff = nowMs - defaultDays * 86400000L;
cmd.Parameters.AddWithValue("$defaultCutoff", defaultCutoff);
var explicitPlaceholders = chatTypeDaysMap.Count > 0
? BindIntList(cmd, "explicit", chatTypeDaysMap.Keys)
: "-1"; // empty list would produce invalid SQL
clauses.Add($"(ChatType NOT IN ({explicitPlaceholders}) AND Date < $defaultCutoff)");
var explicitPlaceholders =
chatTypeDaysMap.Count > 0
? BindIntList(cmd, "explicit", chatTypeDaysMap.Keys)
: "-1"; // empty list would produce invalid SQL
clauses.Add(
$"(ChatType NOT IN ({explicitPlaceholders}) AND Date < $defaultCutoff)"
);
}
if (clauses.Count == 0)
@@ -411,7 +450,9 @@ internal class MessageStore : IDisposable
{
// Defensive: refuse a "delete everything" disguised as a filter.
// Use ClearMessages() if a full wipe is actually intended.
throw new InvalidOperationException("CleanupRetainOnly requires at least one allowed ChatType. Use ClearMessages for a full wipe.");
throw new InvalidOperationException(
"CleanupRetainOnly requires at least one allowed ChatType. Use ClearMessages for a full wipe."
);
}
long deleted;
@@ -428,15 +469,19 @@ internal class MessageStore : IDisposable
internal void PerformMaintenance()
{
Connection.Execute(@"
Connection.Execute(
@"
VACUUM;
REINDEX messages;
ANALYZE;
");
"
);
}
private string LogPath => DbPath + "-wal";
internal long DatabaseSize() => !File.Exists(DbPath) ? 0 : new FileInfo(DbPath).Length;
internal long DatabaseLogSize() => !File.Exists(LogPath) ? 0 : new FileInfo(LogPath).Length;
internal int MessageCount()
@@ -461,7 +506,8 @@ internal class MessageStore : IDisposable
}
using var cmd = Connection.CreateCommand();
cmd.CommandText = @"
cmd.CommandText =
@"
INSERT INTO messages (
Id,
Receiver,
@@ -513,10 +559,22 @@ internal class MessageStore : IDisposable
cmd.Parameters.AddWithValue("$ChatType", message.Code.Type);
cmd.Parameters.AddWithValue("$SourceKind", message.Code.Source);
cmd.Parameters.AddWithValue("$TargetKind", message.Code.Target);
cmd.Parameters.AddWithValue("$Sender", MessagePackSerializer.Serialize(message.Sender, MsgPackOptions));
cmd.Parameters.AddWithValue("$Content", MessagePackSerializer.Serialize(message.Content, MsgPackOptions));
cmd.Parameters.AddWithValue("$SenderSource", MessagePackSerializer.Serialize(message.SenderSource, MsgPackOptions));
cmd.Parameters.AddWithValue("$ContentSource", MessagePackSerializer.Serialize(message.ContentSource, MsgPackOptions));
cmd.Parameters.AddWithValue(
"$Sender",
MessagePackSerializer.Serialize(message.Sender, MsgPackOptions)
);
cmd.Parameters.AddWithValue(
"$Content",
MessagePackSerializer.Serialize(message.Content, MsgPackOptions)
);
cmd.Parameters.AddWithValue(
"$SenderSource",
MessagePackSerializer.Serialize(message.SenderSource, MsgPackOptions)
);
cmd.Parameters.AddWithValue(
"$ContentSource",
MessagePackSerializer.Serialize(message.ContentSource, MsgPackOptions)
);
cmd.Parameters.AddWithValue("$ExtraChatChannel", message.ExtraChatChannel);
cmd.ExecuteNonQuery();
@@ -532,7 +590,8 @@ internal class MessageStore : IDisposable
internal MessageEnumerator StreamForExport(
IReadOnlyCollection<int>? chatTypes,
DateTimeOffset? from,
DateTimeOffset? to)
DateTimeOffset? to
)
{
var cmd = Connection.CreateCommand();
@@ -544,7 +603,8 @@ internal class MessageStore : IDisposable
if (to is not null)
clauses.Add("Date <= $To");
cmd.CommandText = @"
cmd.CommandText =
@"
SELECT
Id,
Receiver,
@@ -559,7 +619,9 @@ internal class MessageStore : IDisposable
ContentSource,
ExtraChatChannel
FROM messages
WHERE " + string.Join(" AND ", clauses) + @"
WHERE "
+ string.Join(" AND ", clauses)
+ @"
ORDER BY Date ASC;";
cmd.CommandTimeout = 600;
@@ -577,7 +639,11 @@ internal class MessageStore : IDisposable
/// <param name="receiver">The receiver content ID to filter by. If null, no filtering is performed.</param>
/// <param name="since">Only show messages since this date. If null, no filtering is performed.</param>
/// <param name="count">The amount to return. Defaults to 10,000.</param>
internal MessageEnumerator GetMostRecentMessages(ulong? receiver = null, DateTimeOffset? since = null, int count = MessageQueryLimit)
internal MessageEnumerator GetMostRecentMessages(
ulong? receiver = null,
DateTimeOffset? since = null,
int count = MessageQueryLimit
)
{
List<string> whereClauses = ["deleted = false"];
if (receiver != null)
@@ -590,7 +656,8 @@ internal class MessageStore : IDisposable
var cmd = Connection.CreateCommand();
// Select last N messages by date DESC, but reverse the order to get
// them in ascending order.
cmd.CommandText = @"
cmd.CommandText =
@"
SELECT *
FROM (
SELECT
@@ -607,7 +674,9 @@ internal class MessageStore : IDisposable
ContentSource,
ExtraChatChannel
FROM messages
" + whereClause + @"
"
+ whereClause
+ @"
ORDER BY Date DESC
LIMIT $Count
)
@@ -645,7 +714,8 @@ internal class MessageStore : IDisposable
string senderName,
uint senderWorld,
int limit,
int sqlScanLimit = 500)
int sqlScanLimit = 500
)
{
if (limit <= 0)
{
@@ -653,7 +723,8 @@ internal class MessageStore : IDisposable
}
using var cmd = Connection.CreateCommand();
cmd.CommandText = @"
cmd.CommandText =
@"
SELECT
Id,
Receiver,
@@ -714,7 +785,12 @@ internal class MessageStore : IDisposable
cmd.ExecuteNonQuery();
}
internal long CountDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
internal long CountDateRange(
DateTime after,
DateTime before,
IEnumerable<byte> channels,
ulong? receiver = null
)
{
using var cmd = Connection.CreateCommand();
@@ -729,7 +805,8 @@ internal class MessageStore : IDisposable
// Select last N messages by date DESC, but reverse the order to get
// them in ascending order.
cmd.CommandText = @"
cmd.CommandText =
@"
SELECT COUNT(*)
FROM messages
" + whereClause;
@@ -737,14 +814,19 @@ internal class MessageStore : IDisposable
if (receiver != null)
cmd.Parameters.AddWithValue("$Receiver", receiver);
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset) after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset) before).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset)after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset)before).ToUnixTimeMilliseconds());
cmd.CommandTimeout = 120; // this could take a while on slow computers
return (long) cmd.ExecuteScalar()!;
return (long)cmd.ExecuteScalar()!;
}
internal MessageEnumerator GetDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
internal MessageEnumerator GetDateRange(
DateTime after,
DateTime before,
IEnumerable<byte> channels,
ulong? receiver = null
)
{
var cmd = Connection.CreateCommand();
@@ -759,7 +841,8 @@ internal class MessageStore : IDisposable
// Select last N messages by date DESC, but reverse the order to get
// them in ascending order.
cmd.CommandText = @"
cmd.CommandText =
@"
SELECT
Id,
Receiver,
@@ -780,13 +863,19 @@ internal class MessageStore : IDisposable
if (receiver != null)
cmd.Parameters.AddWithValue("$Receiver", receiver);
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset) after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset) before).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset)after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset)before).ToUnixTimeMilliseconds());
return new MessageEnumerator(cmd.ExecuteReader());
}
internal MessageEnumerator GetPagedDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null, int page = 0)
internal MessageEnumerator GetPagedDateRange(
DateTime after,
DateTime before,
IEnumerable<byte> channels,
ulong? receiver = null,
int page = 0
)
{
var cmd = Connection.CreateCommand();
@@ -801,7 +890,8 @@ internal class MessageStore : IDisposable
// Select last N messages by date DESC, but reverse the order to get
// them in ascending order.
cmd.CommandText = @"
cmd.CommandText =
@"
SELECT
Id,
Receiver,
@@ -816,7 +906,9 @@ internal class MessageStore : IDisposable
ContentSource,
ExtraChatChannel
FROM messages
" + whereClause + @"
"
+ whereClause
+ @"
ORDER BY Date
LIMIT $Offset, $OffsetCount;
";
@@ -825,8 +917,8 @@ internal class MessageStore : IDisposable
if (receiver != null)
cmd.Parameters.AddWithValue("$Receiver", receiver);
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset) after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset) before).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset)after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset)before).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Offset", DbViewer.RowPerPage * page);
cmd.Parameters.AddWithValue("$OffsetCount", DbViewer.RowPerPage);
@@ -852,7 +944,10 @@ internal class MessageStore : IDisposable
}
}
internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, IDisposable, IAsyncDisposable
internal class MessageEnumerator(DbDataReader reader)
: IEnumerable<Message>,
IDisposable,
IAsyncDisposable
{
private const int MaxErrorLogs = 10;
@@ -876,11 +971,27 @@ internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, ID
(ulong)reader.GetInt64(1),
(ulong)reader.GetInt64(2),
DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64(3)),
new ChatCode((byte)reader.GetInt32(4), (byte)reader.GetInt32(5), (byte)reader.GetInt32(6)),
MessagePackSerializer.Deserialize<List<Chunk>>(reader.GetFieldValue<byte[]>(7), MessageStore.MsgPackOptions),
MessagePackSerializer.Deserialize<List<Chunk>>(reader.GetFieldValue<byte[]>(8), MessageStore.MsgPackOptions),
MessagePackSerializer.Deserialize<SeString>(reader.GetFieldValue<byte[]>(9), MessageStore.MsgPackOptions),
MessagePackSerializer.Deserialize<SeString>(reader.GetFieldValue<byte[]>(10), MessageStore.MsgPackOptions),
new ChatCode(
(byte)reader.GetInt32(4),
(byte)reader.GetInt32(5),
(byte)reader.GetInt32(6)
),
MessagePackSerializer.Deserialize<List<Chunk>>(
reader.GetFieldValue<byte[]>(7),
MessageStore.MsgPackOptions
),
MessagePackSerializer.Deserialize<List<Chunk>>(
reader.GetFieldValue<byte[]>(8),
MessageStore.MsgPackOptions
),
MessagePackSerializer.Deserialize<SeString>(
reader.GetFieldValue<byte[]>(9),
MessageStore.MsgPackOptions
),
MessagePackSerializer.Deserialize<SeString>(
reader.GetFieldValue<byte[]>(10),
MessageStore.MsgPackOptions
),
reader.GetGuid(11)
);
}
@@ -915,6 +1026,7 @@ internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, ID
{
reader.Dispose();
}
public async ValueTask DisposeAsync()
{
await reader.DisposeAsync();
+182 -50
View File
@@ -1,8 +1,5 @@
using System.Numerics;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Ui;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Objects.SubKinds;
@@ -18,11 +15,14 @@ using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Ui;
using HellionChat.Util;
using Lumina.Excel.Sheets;
using Action = System.Action;
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
using ChatTwoPartyFinderPayload = HellionChat.Util.PartyFinderPayload;
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
namespace HellionChat;
@@ -107,7 +107,9 @@ public sealed class PayloadHandler
ImGui.Separator();
var contentId = chunk.Message?.ContentId ?? 0;
var sender = chunk.Message?.Sender.Select(c => c.Link).FirstOrDefault(p => p is PlayerPayload) as PlayerPayload;
var sender =
chunk.Message?.Sender.Select(c => c.Link).FirstOrDefault(p => p is PlayerPayload)
as PlayerPayload;
using var menu = ImRaii.Menu(Language.Context_Integrations);
if (!menu.Success)
@@ -118,7 +120,14 @@ public sealed class PayloadHandler
{
try
{
LogWindow.Plugin.Ipc.Invoke(id, sender, contentId, payload, chunk.Message?.SenderSource, chunk.Message?.ContentSource);
LogWindow.Plugin.Ipc.Invoke(
id,
sender,
contentId,
payload,
chunk.Message?.SenderSource,
chunk.Message?.ContentSource
);
}
catch (Exception ex)
{
@@ -128,7 +137,10 @@ public sealed class PayloadHandler
if (cursor == ImGui.GetCursorPos())
{
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]);
using var pushedColor = ImRaii.PushColor(
ImGuiCol.Text,
ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]
);
ImGui.Text("No integrations available");
}
}
@@ -168,10 +180,16 @@ public sealed class PayloadHandler
if (message.Sender.Count > 0 && ImGui.Selectable(Language.Context_CopyContent))
{
ImGui.SetClipboardText(StringifyMessage(message));
WrapperUtil.AddNotification(Language.Context_CopyContentSuccess, NotificationType.Info);
WrapperUtil.AddNotification(
Language.Context_CopyContentSuccess,
NotificationType.Info
);
}
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
using var pushedColor = ImRaii.PushColor(
ImGuiCol.Text,
ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]
);
ImGui.TextUnformatted(message.Code.Type.Name());
}
@@ -184,7 +202,8 @@ public sealed class PayloadHandler
return string.Empty;
var chunks = withSender ? message.Sender.Concat(message.Content) : message.Content;
return chunks.Where(chunk => chunk is TextChunk)
return chunks
.Where(chunk => chunk is TextChunk)
.Cast<TextChunk>()
.Select(text => text.Content)
.Aggregate(string.Concat);
@@ -255,7 +274,10 @@ public sealed class PayloadHandler
public unsafe void MoveTooltip(AddonEvent type, AddonArgs args)
{
// Only move if the user has the "Next to Cursor" option selected
if (!Plugin.GameConfig.TryGet(UiControlOption.DetailTrackingType, out uint selected) || selected != 0)
if (
!Plugin.GameConfig.TryGet(UiControlOption.DetailTrackingType, out uint selected)
|| selected != 0
)
return;
if (LogWindow.LastViewport != ImGuiHelpers.MainViewport.Handle)
@@ -274,7 +296,10 @@ public sealed class PayloadHandler
var component = atkBase->WindowNode->AtkResNode;
var atkPos = new Vector2(component.ScreenX, component.ScreenY);
var atkSize = new Vector2(component.GetWidth() * component.ScaleX, component.GetHeight() * component.GetScaleY());
var atkSize = new Vector2(
component.GetWidth() * component.ScaleX,
component.GetHeight() * component.GetScaleY()
);
var chatRect = new MathUtil.Rectangle(LogWindow.LastWindowPos, LogWindow.LastWindowSize);
var addonRect = new MathUtil.Rectangle(atkPos, atkSize);
@@ -302,7 +327,7 @@ public sealed class PayloadHandler
if (!chatRect.HasOverlap(addonRect))
{
atkBase->SetPosition((short) addonRect.X, (short) addonRect.Y);
atkBase->SetPosition((short)addonRect.X, (short)addonRect.Y);
return;
}
@@ -320,7 +345,7 @@ public sealed class PayloadHandler
if (!chatRect.HasOverlap(addonRect))
{
atkBase->SetPosition((short) addonRect.X, (short) addonRect.Y);
atkBase->SetPosition((short)addonRect.X, (short)addonRect.Y);
return;
}
@@ -329,7 +354,7 @@ public sealed class PayloadHandler
var y = Math.Clamp(chatRect.SizeY - atkSize.Y, 0, float.MaxValue);
y -= isTop ? 0 : Plugin.Config.TooltipOffset; // offset to prevent cut-off on the bottom
atkBase->SetPosition((short) x, (short) y);
atkBase->SetPosition((short)x, (short)y);
}
private const float MaxInlineIconSize = 32f;
@@ -339,20 +364,25 @@ public sealed class PayloadHandler
if (icon.Size.X <= 0 || icon.Size.Y <= 0)
return;
var width = (float) icon.Size.X;
var height = (float) icon.Size.Y;
var width = (float)icon.Size.X;
var height = (float)icon.Size.Y;
var scale = Math.Min(1f, Math.Min(MaxInlineIconSize / width, MaxInlineIconSize / height));
var size = ImGuiHelpers.ScaledVector2(width * scale, height * scale);
var cursor = ImGui.GetCursorPos();
ImGui.Image(icon.Handle, size);
ImGui.SameLine();
ImGui.SetCursorPos(cursor + new Vector2(size.X + 4, size.Y - ImGui.GetTextLineHeightWithSpacing()));
ImGui.SetCursorPos(
cursor + new Vector2(size.X + 4, size.Y - ImGui.GetTextLineHeightWithSpacing())
);
}
private void HoverStatus(StatusPayload status)
{
if (Plugin.TextureProvider.GetFromGameIcon(status.Status.Value.Icon).GetWrapOrDefault() is { } icon)
if (
Plugin.TextureProvider.GetFromGameIcon(status.Status.Value.Icon).GetWrapOrDefault() is
{ } icon
)
InlineIcon(icon);
var builder = new SeStringBuilder();
@@ -374,7 +404,11 @@ public sealed class PayloadHandler
LogWindow.DrawChunks(name.ToList());
ImGui.Separator();
var desc = ChunkUtil.ToChunks(status.Status.Value.Description.ToDalamudString(), ChunkSource.None, null);
var desc = ChunkUtil.ToChunks(
status.Status.Value.Description.ToDalamudString(),
ChunkSource.None,
null
);
LogWindow.DrawChunks(desc.ToList());
}
@@ -389,14 +423,23 @@ public sealed class PayloadHandler
if (!item.Item.TryGetValue(out Item resolvedItem))
return;
if (Plugin.TextureProvider.GetFromGameIcon(new GameIconLookup(resolvedItem.Icon, item.IsHQ)).GetWrapOrDefault() is { } icon)
if (
Plugin
.TextureProvider.GetFromGameIcon(new GameIconLookup(resolvedItem.Icon, item.IsHQ))
.GetWrapOrDefault() is
{ } icon
)
InlineIcon(icon);
var name = ChunkUtil.ToChunks(resolvedItem.Name.ToDalamudString(), ChunkSource.None, null);
LogWindow.DrawChunks(name.ToList());
ImGui.Separator();
var desc = ChunkUtil.ToChunks(resolvedItem.Description.ToDalamudString(), ChunkSource.None, null);
var desc = ChunkUtil.ToChunks(
resolvedItem.Description.ToDalamudString(),
ChunkSource.None,
null
);
LogWindow.DrawChunks(desc.ToList());
}
@@ -405,7 +448,12 @@ public sealed class PayloadHandler
if (!Sheets.EventItemSheet.TryGetRow(payload.RawItemId, out var itemRow))
return;
if (Plugin.TextureProvider.GetFromGameIcon(new GameIconLookup(itemRow.Icon)).GetWrapOrDefault() is { } icon)
if (
Plugin
.TextureProvider.GetFromGameIcon(new GameIconLookup(itemRow.Icon))
.GetWrapOrDefault() is
{ } icon
)
InlineIcon(icon);
var name = ChunkUtil.ToChunks(itemRow.Name.ToDalamudString(), ChunkSource.None, null);
@@ -415,7 +463,11 @@ public sealed class PayloadHandler
if (!Sheets.EventItemHelpSheet.TryGetRow(payload.RawItemId, out var itemHelpRow))
return;
LogWindow.DrawChunks(ChunkUtil.ToChunks(itemHelpRow.Description.ToDalamudString(), ChunkSource.None, null).ToList());
LogWindow.DrawChunks(
ChunkUtil
.ToChunks(itemHelpRow.Description.ToDalamudString(), ChunkSource.None, null)
.ToList()
);
}
private void HoverUri(UriPayload uri)
@@ -438,7 +490,10 @@ public sealed class PayloadHandler
ClickLinkPayload(chunk, payload, link);
break;
case DalamudPartyFinderPayload pf:
if (pf.LinkType == DalamudPartyFinderPayload.PartyFinderLinkType.PartyFinderNotification)
if (
pf.LinkType
== DalamudPartyFinderPayload.PartyFinderLinkType.PartyFinderNotification
)
GameFunctions.GameFunctions.OpenPartyFinder();
else
GameFunctions.GameFunctions.OpenPartyFinder(pf.ListingId);
@@ -473,7 +528,12 @@ public sealed class PayloadHandler
return;
var payloads = source.Payloads.Skip(start).Take(end - start + 1).ToList();
if (!Plugin.ChatGui.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value))
if (
!Plugin.ChatGui.RegisteredLinkHandlers.TryGetValue(
(link.Plugin, link.CommandId),
out var value
)
)
{
Plugin.Log.Warning("Could not find DalamudLinkHandlers");
return;
@@ -508,7 +568,12 @@ public sealed class PayloadHandler
return;
var hq = payload.Kind == ItemKind.Hq;
if (Plugin.TextureProvider.GetFromGameIcon(new GameIconLookup(itemRow.Icon, hq)).GetWrapOrDefault() is { } icon)
if (
Plugin
.TextureProvider.GetFromGameIcon(new GameIconLookup(itemRow.Icon, hq))
.GetWrapOrDefault() is
{ } icon
)
InlineIcon(icon);
var name = itemRow.Name.ToDalamudString();
@@ -554,10 +619,18 @@ public sealed class PayloadHandler
return;
var item = Sheets.EventItemSheet.GetRow(payload.ItemId);
if (Plugin.TextureProvider.GetFromGameIcon(new GameIconLookup(item.Icon)).GetWrapOrDefault() is { } icon)
if (
Plugin
.TextureProvider.GetFromGameIcon(new GameIconLookup(item.Icon))
.GetWrapOrDefault() is
{ } icon
)
InlineIcon(icon);
LogWindow.DrawChunks(ChunkUtil.ToChunks(item.Name.ToDalamudString(), ChunkSource.None, null).ToList(), false);
LogWindow.DrawChunks(
ChunkUtil.ToChunks(item.Name.ToDalamudString(), ChunkSource.None, null).ToList(),
false
);
ImGui.Separator();
var realItemId = payload.RawItemId;
@@ -585,7 +658,7 @@ public sealed class PayloadHandler
{
name.AddRange([
new IconChunk(ChunkSource.None, null, BitmapFontIcon.CrossWorld),
new TextChunk(ChunkSource.None, null, world.Value.Name.ExtractText())
new TextChunk(ChunkSource.None, null, world.Value.Name.ExtractText()),
]);
}
@@ -606,7 +679,15 @@ public sealed class PayloadHandler
}
else if (validContentId)
{
LogWindow.Plugin.Functions.Chat.SetEurekaTellChannel(player.PlayerName, world.Value.Name.ToString(), (ushort) world.RowId, 0, chunk.Message!.ContentId, 0, false);
LogWindow.Plugin.Functions.Chat.SetEurekaTellChannel(
player.PlayerName,
world.Value.Name.ToString(),
(ushort)world.RowId,
0,
chunk.Message!.ContentId,
0,
false
);
}
LogWindow.Activate = true;
@@ -615,12 +696,18 @@ public sealed class PayloadHandler
if (world.Value.IsPublic)
{
var party = Plugin.PartyList;
var leader = party[(int) party.PartyLeaderIndex]?.ContentId;
var leader = party[(int)party.PartyLeaderIndex]?.ContentId;
var isLeader = party.Length == 0 || Plugin.PlayerState.ContentId == leader;
var member = party.FirstOrDefault(member => member.Name.TextValue == player.PlayerName && member.World.RowId == world.RowId);
var member = party.FirstOrDefault(member =>
member.Name.TextValue == player.PlayerName && member.World.RowId == world.RowId
);
var isInParty = member != null;
var inInstance = GameFunctions.GameFunctions.IsInInstance();
var inPartyInstance = Sheets.TerritorySheet.GetRow(Plugin.ClientState.TerritoryType).TerritoryIntendedUse.RowId is (41 or 47 or 48 or 52 or 53 or 61);
var inPartyInstance =
Sheets
.TerritorySheet.GetRow(Plugin.ClientState.TerritoryType)
.TerritoryIntendedUse.RowId
is (41 or 47 or 48 or 52 or 53 or 61);
if (isLeader)
{
if (!isInParty)
@@ -636,10 +723,20 @@ public sealed class PayloadHandler
if (menu.Success)
{
if (ImGui.Selectable(Language.Context_InviteToParty_SameWorld))
GameFunctions.Party.InviteSameWorld(player.PlayerName, (ushort)world.RowId, chunk.Message?.ContentId ?? 0);
GameFunctions.Party.InviteSameWorld(
player.PlayerName,
(ushort)world.RowId,
chunk.Message?.ContentId ?? 0
);
if (validContentId && ImGui.Selectable(Language.Context_InviteToParty_DifferentWorld))
GameFunctions.Party.InviteOtherWorld(chunk.Message!.ContentId, (ushort)world.RowId);
if (
validContentId
&& ImGui.Selectable(Language.Context_InviteToParty_DifferentWorld)
)
GameFunctions.Party.InviteOtherWorld(
chunk.Message!.ContentId,
(ushort)world.RowId
);
}
}
}
@@ -654,23 +751,41 @@ public sealed class PayloadHandler
}
}
var isFriend = GameFunctions.GameFunctions.GetFriends().Any(friend => friend.NameString == player.PlayerName && friend.HomeWorld == world.RowId);
var isFriend = GameFunctions
.GameFunctions.GetFriends()
.Any(friend =>
friend.NameString == player.PlayerName && friend.HomeWorld == world.RowId
);
if (!isFriend && ImGui.Selectable(Language.Context_SendFriendRequest))
LogWindow.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) world.RowId);
LogWindow.Plugin.Functions.SendFriendRequest(
player.PlayerName,
(ushort)world.RowId
);
using (var menuBlockFunctions = ImRaii.Menu(Language.Context_BlockFunctions))
{
if (menuBlockFunctions.Success)
{
if (ImGui.Selectable(Language.Context_AddToBlacklist))
LogWindow.Plugin.Functions.AddToBlacklist(player.PlayerName, (ushort)world.RowId);
LogWindow.Plugin.Functions.AddToBlacklist(
player.PlayerName,
(ushort)world.RowId
);
if (chunk.Message != null)
{
var message = chunk.Message;
if (message.AccountId != 0 && ImGui.Selectable(Language.Context_AddToMuteList))
LogWindow.Plugin.Functions.AddToMuteList(message.AccountId, message.ContentId, player.PlayerName, (short) world.RowId);
if (
message.AccountId != 0
&& ImGui.Selectable(Language.Context_AddToMuteList)
)
LogWindow.Plugin.Functions.AddToMuteList(
message.AccountId,
message.ContentId,
player.PlayerName,
(short)world.RowId
);
if (ImGui.Selectable(Language.Context_AddToTermsFilter))
LogWindow.Plugin.Functions.AddToTermsList(message.ContentSource);
@@ -678,8 +793,11 @@ public sealed class PayloadHandler
}
}
if (GameFunctions.GameFunctions.IsMentor() && ImGui.Selectable(Language.Context_InviteToNoviceNetwork))
GameFunctions.Context.InviteToNoviceNetwork(player.PlayerName, (ushort) world.RowId);
if (
GameFunctions.GameFunctions.IsMentor()
&& ImGui.Selectable(Language.Context_InviteToNoviceNetwork)
)
GameFunctions.Context.InviteToNoviceNetwork(player.PlayerName, (ushort)world.RowId);
}
var inputChannel = chunk.Message?.Code.Type.ToInputChannel();
@@ -694,7 +812,10 @@ public sealed class PayloadHandler
if (validContentId && ImGui.Selectable(Language.Context_AdventurerPlate))
if (!GameFunctions.GameFunctions.TryOpenAdventurerPlate(chunk.Message!.ContentId))
WrapperUtil.AddNotification(Language.Context_AdventurerPlateError, NotificationType.Warning);
WrapperUtil.AddNotification(
Language.Context_AdventurerPlateError,
NotificationType.Warning
);
}
private IPlayerCharacter? FindCharacterForPayload(PlayerPayload payload)
@@ -728,13 +849,21 @@ public sealed class PayloadHandler
if (ImGui.Selectable(Language.Context_CopyLink))
{
ImGui.SetClipboardText(uri.Uri.ToString());
WrapperUtil.AddNotification(Language.Context_CopyLinkNotification, NotificationType.Info);
WrapperUtil.AddNotification(
Language.Context_CopyLinkNotification,
NotificationType.Info
);
}
}
private void DrawStatusPopup(StatusPayload status)
{
if (Plugin.TextureProvider.GetFromGameIcon(new GameIconLookup(status.Status.Value.Icon)).GetWrapOrDefault() is { } icon)
if (
Plugin
.TextureProvider.GetFromGameIcon(new GameIconLookup(status.Status.Value.Icon))
.GetWrapOrDefault() is
{ } icon
)
InlineIcon(icon);
var builder = new SeStringBuilder();
@@ -752,7 +881,10 @@ public sealed class PayloadHandler
break;
}
LogWindow.DrawChunks(ChunkUtil.ToChunks(builder.BuiltString, ChunkSource.None, null).ToList(), false);
LogWindow.DrawChunks(
ChunkUtil.ToChunks(builder.BuiltString, ChunkSource.None, null).ToList(),
false
);
ImGui.Separator();
if (ImGui.Selectable(Language.Context_Link))
+212 -102
View File
@@ -3,17 +3,17 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.ExceptionServices;
using HellionChat.Ipc;
using HellionChat.Resources;
using HellionChat.Ui;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Windowing;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ImGuiFileDialog;
using HellionChat.Ipc;
using HellionChat.Resources;
using HellionChat.Ui;
using HellionChat.Util;
namespace HellionChat;
@@ -22,27 +22,68 @@ public sealed class Plugin : IAsyncDalamudPlugin
{
public const string PluginName = "Hellion Chat";
[PluginService] public static IPluginLog Log { get; private set; } = null!;
[PluginService] public static IDalamudPluginInterface Interface { get; private set; } = null!;
[PluginService] public static IChatGui ChatGui { get; private set; } = null!;
[PluginService] public static IClientState ClientState { get; private set; } = null!;
[PluginService] public static ICommandManager CommandManager { get; private set; } = null!;
[PluginService] public static ICondition Condition { get; private set; } = null!;
[PluginService] public static IDataManager DataManager { get; private set; } = null!;
[PluginService] public static IFramework Framework { get; private set; } = null!;
[PluginService] public static IGameGui GameGui { get; private set; } = null!;
[PluginService] public static IKeyState KeyState { get; private set; } = null!;
[PluginService] public static IObjectTable ObjectTable { get; private set; } = null!;
[PluginService] public static IPartyList PartyList { get; private set; } = null!;
[PluginService] public static ITargetManager TargetManager { get; private set; } = null!;
[PluginService] public static ITextureProvider TextureProvider { get; private set; } = null!;
[PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!;
[PluginService] public static IGameConfig GameConfig { get; private set; } = null!;
[PluginService] public static INotificationManager Notification { get; private set; } = null!;
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
[PluginService] public static IPlayerState PlayerState { get; private set; } = null!;
[PluginService] public static ISeStringEvaluator Evaluator { get; private set; } = null!;
[PluginService] public static ISelfTestRegistry SelfTestRegistry { get; private set; } = null!;
[PluginService]
public static IPluginLog Log { get; private set; } = null!;
[PluginService]
public static IDalamudPluginInterface Interface { get; private set; } = null!;
[PluginService]
public static IChatGui ChatGui { get; private set; } = null!;
[PluginService]
public static IClientState ClientState { get; private set; } = null!;
[PluginService]
public static ICommandManager CommandManager { get; private set; } = null!;
[PluginService]
public static ICondition Condition { get; private set; } = null!;
[PluginService]
public static IDataManager DataManager { get; private set; } = null!;
[PluginService]
public static IFramework Framework { get; private set; } = null!;
[PluginService]
public static IGameGui GameGui { get; private set; } = null!;
[PluginService]
public static IKeyState KeyState { get; private set; } = null!;
[PluginService]
public static IObjectTable ObjectTable { get; private set; } = null!;
[PluginService]
public static IPartyList PartyList { get; private set; } = null!;
[PluginService]
public static ITargetManager TargetManager { get; private set; } = null!;
[PluginService]
public static ITextureProvider TextureProvider { get; private set; } = null!;
[PluginService]
public static IGameInteropProvider GameInteropProvider { get; private set; } = null!;
[PluginService]
public static IGameConfig GameConfig { get; private set; } = null!;
[PluginService]
public static INotificationManager Notification { get; private set; } = null!;
[PluginService]
public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
[PluginService]
public static IPlayerState PlayerState { get; private set; } = null!;
[PluginService]
public static ISeStringEvaluator Evaluator { get; private set; } = null!;
[PluginService]
public static ISelfTestRegistry SelfTestRegistry { get; private set; } = null!;
public static Configuration Config = null!;
public static FileDialogManager FileDialogManager { get; private set; } = null!;
@@ -136,8 +177,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
if (Config.Version < 16)
{
throw new InvalidOperationException(
$"HellionChat v1.4.3 requires config schema v16, got v{Config.Version}. " +
"Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.3.");
$"HellionChat v1.4.3 requires config schema v16, got v{Config.Version}. "
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.3."
);
}
// Hellion Chat — Auto-Tell-Tabs Defense-in-Depth. SaveConfig
@@ -219,13 +261,15 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Auto-Tell-Tabs subscribes to MessageManager.MessageProcessed for
// live tells and to ClientState.Logout for cleanup; needs the live
// store handed in at construction.
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
AutoTellTabsService = new AutoTellTabsService(
this,
MessageManager,
MessageManager.Store
);
AutoTellTabsService.Initialize();
// SelfTest steps poll Active per frame and need the registry wired.
SelfTestRegistry.RegisterTestSteps([
new SelfTests.ThemeSwitchSelfTestStep(this),
]);
SelfTestRegistry.RegisterTestSteps([new SelfTests.ThemeSwitchSelfTestStep(this)]);
ChatLogWindow = new ChatLogWindow(this);
SettingsWindow = new SettingsWindow(this);
@@ -268,14 +312,14 @@ public sealed class Plugin : IAsyncDalamudPlugin
Interface.UiBuilder.DisableCutsceneUiHide = true;
Interface.UiBuilder.DisableGposeUiHide = true;
#if !DEBUG
#if !DEBUG
// Fire-and-forget on a worker thread. The first auto-translate use of
// a session may have a sub-second hitch if the cache hasn't filled yet,
// but that's preferable to making every user wait ~300 ms during
// plugin load for a cache they may never touch. ChatTwo (upstream)
// does this sync; we trade load-time for first-use latency.
_ = Task.Run(AutoTranslate.PreloadCache, cancellationToken);
#endif
#endif
cancellationToken.ThrowIfCancellationRequested();
@@ -296,8 +340,13 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Mirror the v1.4.0 load-failure recovery: hand off to DisposeAsync
// so partially-built services are torn down. Swallow the cleanup
// exception so the original load failure stays the visible cause.
try { await DisposeAsync().ConfigureAwait(false); }
catch { /* keep original failure */ }
try
{
await DisposeAsync().ConfigureAwait(false);
}
catch
{ /* keep original failure */
}
throw;
}
}
@@ -324,14 +373,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
// v1.4.0 F5.3 — flush a pending DeferredSave before service teardown,
// since FrameworkUpdate just got unsubscribed and won't fire it.
failure = CaptureFailure(failure, () =>
{
if (DeferredSaveFrames >= 0)
failure = CaptureFailure(
failure,
() =>
{
SaveConfig();
DeferredSaveFrames = -1;
if (DeferredSaveFrames >= 0)
{
SaveConfig();
DeferredSaveFrames = -1;
}
}
});
);
// Auto-Tell-Tabs unsubscribes from MessageProcessed before MessageManager
// goes away. Pure-memory cleanup, no framework-thread requirement.
@@ -342,7 +394,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
// framework-block so the worker threads are quiesced first.
if (MessageManager is not null)
{
failure = await CaptureFailureAsync(failure, () => MessageManager.DisposeAsync().AsTask())
failure = await CaptureFailureAsync(
failure,
() => MessageManager.DisposeAsync().AsTask()
)
.ConfigureAwait(false);
}
@@ -354,32 +409,37 @@ public sealed class Plugin : IAsyncDalamudPlugin
// behind it; mirrors Lightless DisposeFrameworkBoundServicesAsync.
try
{
await Framework.RunOnFrameworkThread(() =>
{
// Game-Functions first — other services may still query
// chat-interactable state during their Dispose.
failure = CaptureFailure(failure, () => GameFunctions.GameFunctions.SetChatInteractable(true));
await Framework
.RunOnFrameworkThread(() =>
{
// Game-Functions first — other services may still query
// chat-interactable state during their Dispose.
failure = CaptureFailure(
failure,
() => GameFunctions.GameFunctions.SetChatInteractable(true)
);
// IPC subscribers — dispose before windows so any final
// event firing from the IPC source can't reach a half-torn
// ChatLogWindow.
failure = CaptureFailure(failure, () => HonorificService?.Dispose());
failure = CaptureFailure(failure, () => TypingIpc?.Dispose());
failure = CaptureFailure(failure, () => ExtraChat?.Dispose());
failure = CaptureFailure(failure, () => Ipc?.Dispose());
// IPC subscribers — dispose before windows so any final
// event firing from the IPC source can't reach a half-torn
// ChatLogWindow.
failure = CaptureFailure(failure, () => HonorificService?.Dispose());
failure = CaptureFailure(failure, () => TypingIpc?.Dispose());
failure = CaptureFailure(failure, () => ExtraChat?.Dispose());
failure = CaptureFailure(failure, () => Ipc?.Dispose());
// Windows — RemoveAllWindows first, then per-window Dispose.
// Order matches the pre-v1.4.3 Dispose body byte-for-byte.
// CommandHelpWindow and FirstRunWizard don't implement
// IDisposable; their resources are reclaimed via WindowSystem.
failure = CaptureFailure(failure, () => WindowSystem?.RemoveAllWindows());
failure = CaptureFailure(failure, () => ChatLogWindow?.Dispose());
failure = CaptureFailure(failure, () => DbViewer?.Dispose());
failure = CaptureFailure(failure, () => InputPreview?.Dispose());
failure = CaptureFailure(failure, () => SettingsWindow?.Dispose());
failure = CaptureFailure(failure, () => DebuggerWindow?.Dispose());
failure = CaptureFailure(failure, () => SeStringDebugger?.Dispose());
}).ConfigureAwait(false);
// Windows — RemoveAllWindows first, then per-window Dispose.
// Order matches the pre-v1.4.3 Dispose body byte-for-byte.
// CommandHelpWindow and FirstRunWizard don't implement
// IDisposable; their resources are reclaimed via WindowSystem.
failure = CaptureFailure(failure, () => WindowSystem?.RemoveAllWindows());
failure = CaptureFailure(failure, () => ChatLogWindow?.Dispose());
failure = CaptureFailure(failure, () => DbViewer?.Dispose());
failure = CaptureFailure(failure, () => InputPreview?.Dispose());
failure = CaptureFailure(failure, () => SettingsWindow?.Dispose());
failure = CaptureFailure(failure, () => DebuggerWindow?.Dispose());
failure = CaptureFailure(failure, () => SeStringDebugger?.Dispose());
})
.ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -401,15 +461,30 @@ public sealed class Plugin : IAsyncDalamudPlugin
// skip every cleanup behind it and leave services half-torn.
private static Exception? CaptureFailure(Exception? failure, Action action)
{
try { action(); }
catch (Exception ex) { failure ??= ex; }
try
{
action();
}
catch (Exception ex)
{
failure ??= ex;
}
return failure;
}
private static async ValueTask<Exception?> CaptureFailureAsync(Exception? failure, Func<Task> action)
private static async ValueTask<Exception?> CaptureFailureAsync(
Exception? failure,
Func<Task> action
)
{
try { await action().ConfigureAwait(false); }
catch (Exception ex) { failure ??= ex; }
try
{
await action().ConfigureAwait(false);
}
catch (Exception ex)
{
failure ??= ex;
}
return failure;
}
@@ -434,12 +509,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
if (!File.Exists(ourConfigFile) && File.Exists(legacyConfigFile))
{
File.Move(legacyConfigFile, ourConfigFile);
Log.Information($"HellionChat: migrated config file {legacyConfigFile} → {ourConfigFile}");
Log.Information(
$"HellionChat: migrated config file {legacyConfigFile} → {ourConfigFile}"
);
}
}
catch (IOException e)
{
Log.Warning(e, $"HellionChat: config file move blocked, leaving {legacyConfigFile} in place");
Log.Warning(
e,
$"HellionChat: config file move blocked, leaving {legacyConfigFile} in place"
);
lockedBlocker = true;
}
@@ -469,7 +549,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
catch (IOException e)
{
Log.Warning(e, $"HellionChat: file move blocked for {file}, will retry on next load");
Log.Warning(
e,
$"HellionChat: file move blocked for {file}, will retry on next load"
);
lockedBlocker = true;
}
}
@@ -486,7 +569,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
catch (IOException e)
{
Log.Warning(e, $"HellionChat: subdir move blocked for {dir}, will retry on next load");
Log.Warning(
e,
$"HellionChat: subdir move blocked for {dir}, will retry on next load"
);
lockedBlocker = true;
}
}
@@ -507,15 +593,18 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Surface the most common cause to the user as a notification
// so they don't think Hellion Chat lost their history when in
// fact upstream Chat 2 was still holding the database file.
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
{
Title = "Hellion Chat",
Content = "Could not migrate the Chat 2 database — the file appears to be in use. " +
"Disable Chat 2, fully close the game, then start it again. " +
"See the README troubleshooting section if the issue persists.",
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
InitialDuration = TimeSpan.FromSeconds(30),
});
Notification.AddNotification(
new Dalamud.Interface.ImGuiNotification.Notification
{
Title = "Hellion Chat",
Content =
"Could not migrate the Chat 2 database — the file appears to be in use. "
+ "Disable Chat 2, fully close the game, then start it again. "
+ "See the README troubleshooting section if the issue persists.",
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
InitialDuration = TimeSpan.FromSeconds(30),
}
);
}
}
@@ -575,11 +664,13 @@ public sealed class Plugin : IAsyncDalamudPlugin
// — the .Wait() here would return as soon as the inner Task.Run was
// dispatched, racing the next sweep cycle against the still-running
// filter pass. See AUDIT-2026-05-05 [QUAL-02].
Framework.Run(() =>
{
MessageManager.ClearAllTabs();
MessageManager.FilterAllTabs();
}).Wait();
Framework
.Run(() =>
{
MessageManager.ClearAllTabs();
MessageManager.FilterAllTabs();
})
.Wait();
}
else
{
@@ -595,7 +686,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
lock (RetentionSweepLock)
RetentionSweepRunning = false;
}
}) { IsBackground = true }.Start();
})
{
IsBackground = true,
}.Start();
}
private void Draw()
@@ -603,7 +697,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Theme-Engine ist ab v14 immer aktiv; Klassik ist jetzt ein eigenes
// Theme statt einem deaktivierten Hellion-Theme. Active wird einmal
// pro Frame aus der Registry gelesen.
using IDisposable _style = HellionStyle.PushGlobal(ThemeRegistry.Active, Config.WindowOpacity);
using IDisposable _style = HellionStyle.PushGlobal(
ThemeRegistry.Active,
Config.WindowOpacity
);
ChatLogWindow.BeginFrame();
@@ -617,7 +714,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
// v1.0.2 — global skip while the New Game+ menu (QuestRedo addon) is
// open. Hides every plugin window in one shot (chat log, pop-outs,
// settings, db viewer, etc.), matching the LoadingScreens pattern.
if (Config.HideInNewGamePlusMenu && GameFunctions.GameFunctions.IsAddonInteractable(GameFunctions.GameFunctions.NewGamePlusAddonName))
if (
Config.HideInNewGamePlusMenu
&& GameFunctions.GameFunctions.IsAddonInteractable(
GameFunctions.GameFunctions.NewGamePlusAddonName
)
)
{
ChatLogWindow.FinalizeFrame();
TypingIpc.Update();
@@ -627,7 +729,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
ChatLogWindow.HideStateCheck();
Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden;
ChatLogWindow.DefaultText = ImGui.GetStyle().Colors[(int) ImGuiCol.Text];
ChatLogWindow.DefaultText = ImGui.GetStyle().Colors[(int)ImGuiCol.Text];
using ((Config.FontsEnabled ? FontManager.RegularFont : FontManager.Axis).Push())
WindowSystem.Draw();
@@ -655,9 +757,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
internal void LanguageChanged(string langCode)
{
var info = Config.LanguageOverride is LanguageOverride.None
? new CultureInfo(langCode)
: new CultureInfo(Config.LanguageOverride.Code());
var info =
Config.LanguageOverride is LanguageOverride.None
? new CultureInfo(langCode)
: new CultureInfo(Config.LanguageOverride.Code());
Language.Culture = info;
HellionStrings.Culture = info;
@@ -669,7 +772,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
"ChatLogPanel_0",
"ChatLogPanel_1",
"ChatLogPanel_2",
"ChatLogPanel_3"
"ChatLogPanel_3",
];
private void FrameworkUpdate(IFramework framework)
@@ -687,7 +790,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
public static bool InBattle => Condition[ConditionFlag.InCombat];
public static bool GposeActive => Condition[ConditionFlag.WatchingCutscene];
public static bool CutsceneActive => Condition[ConditionFlag.OccupiedInCutSceneEvent] || Condition[ConditionFlag.WatchingCutscene78];
public static bool CutsceneActive =>
Condition[ConditionFlag.OccupiedInCutSceneEvent]
|| Condition[ConditionFlag.WatchingCutscene78];
// v1.1.0 — wenn der themes/-Ordner leer ist, schreiben wir die embedded
// example-theme.json als Vorlage rein. Bestehende User-Customs werden
@@ -698,7 +803,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
return;
var examplePath = Path.Combine(dir, "example-theme.json");
var resourceStream = typeof(Plugin).Assembly.GetManifestResourceStream("HellionChat.Themes.Builtin.example-theme.json");
var resourceStream = typeof(Plugin).Assembly.GetManifestResourceStream(
"HellionChat.Themes.Builtin.example-theme.json"
);
if (resourceStream is null)
{
Log.Warning("Themes example template not found in assembly resources; skipping seed.");
@@ -713,7 +820,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
catch (IOException ex)
{
Log.Warning(ex, "Failed to seed example-theme.json; user can create custom themes manually.");
Log.Warning(
ex,
"Failed to seed example-theme.json; user can create custom themes manually."
);
}
finally
{
+48 -44
View File
@@ -46,50 +46,53 @@ internal static class PrivacyDefaults
// here fall back to Configuration.RetentionDefaultDays. Reflects the
// design spec: Tells 365, own-conversation channels 90, everything else
// shorter via the global default.
internal static readonly IReadOnlyDictionary<ChatType, int> DefaultRetentionDays = new Dictionary<ChatType, int>
{
[ChatType.TellIncoming] = 365,
[ChatType.TellOutgoing] = 365,
internal static readonly IReadOnlyDictionary<ChatType, int> DefaultRetentionDays =
new Dictionary<ChatType, int>
{
[ChatType.TellIncoming] = 365,
[ChatType.TellOutgoing] = 365,
[ChatType.Party] = 90,
[ChatType.CrossParty] = 90,
[ChatType.Alliance] = 90,
[ChatType.PvpTeam] = 90,
[ChatType.FreeCompany] = 90,
[ChatType.Party] = 90,
[ChatType.CrossParty] = 90,
[ChatType.Alliance] = 90,
[ChatType.PvpTeam] = 90,
[ChatType.FreeCompany] = 90,
[ChatType.Linkshell1] = 90,
[ChatType.Linkshell2] = 90,
[ChatType.Linkshell3] = 90,
[ChatType.Linkshell4] = 90,
[ChatType.Linkshell5] = 90,
[ChatType.Linkshell6] = 90,
[ChatType.Linkshell7] = 90,
[ChatType.Linkshell8] = 90,
[ChatType.Linkshell1] = 90,
[ChatType.Linkshell2] = 90,
[ChatType.Linkshell3] = 90,
[ChatType.Linkshell4] = 90,
[ChatType.Linkshell5] = 90,
[ChatType.Linkshell6] = 90,
[ChatType.Linkshell7] = 90,
[ChatType.Linkshell8] = 90,
[ChatType.CrossLinkshell1] = 90,
[ChatType.CrossLinkshell2] = 90,
[ChatType.CrossLinkshell3] = 90,
[ChatType.CrossLinkshell4] = 90,
[ChatType.CrossLinkshell5] = 90,
[ChatType.CrossLinkshell6] = 90,
[ChatType.CrossLinkshell7] = 90,
[ChatType.CrossLinkshell8] = 90,
[ChatType.CrossLinkshell1] = 90,
[ChatType.CrossLinkshell2] = 90,
[ChatType.CrossLinkshell3] = 90,
[ChatType.CrossLinkshell4] = 90,
[ChatType.CrossLinkshell5] = 90,
[ChatType.CrossLinkshell6] = 90,
[ChatType.CrossLinkshell7] = 90,
[ChatType.CrossLinkshell8] = 90,
[ChatType.ExtraChatLinkshell1] = 90,
[ChatType.ExtraChatLinkshell2] = 90,
[ChatType.ExtraChatLinkshell3] = 90,
[ChatType.ExtraChatLinkshell4] = 90,
[ChatType.ExtraChatLinkshell5] = 90,
[ChatType.ExtraChatLinkshell6] = 90,
[ChatType.ExtraChatLinkshell7] = 90,
[ChatType.ExtraChatLinkshell8] = 90,
};
[ChatType.ExtraChatLinkshell1] = 90,
[ChatType.ExtraChatLinkshell2] = 90,
[ChatType.ExtraChatLinkshell3] = 90,
[ChatType.ExtraChatLinkshell4] = 90,
[ChatType.ExtraChatLinkshell5] = 90,
[ChatType.ExtraChatLinkshell6] = 90,
[ChatType.ExtraChatLinkshell7] = 90,
[ChatType.ExtraChatLinkshell8] = 90,
};
// Casual profile = Privacy-First plus public chat (Say/Shout/Yell, both
// emote types, Novice Network), kept for a short 24-hour window so the
// last RP scene or shout trade is still searchable but third-party data
// doesn't accumulate forever.
internal static readonly IReadOnlySet<ChatType> CasualWhitelist = new HashSet<ChatType>(PrivacyFirstWhitelist)
internal static readonly IReadOnlySet<ChatType> CasualWhitelist = new HashSet<ChatType>(
PrivacyFirstWhitelist
)
{
ChatType.Say,
ChatType.Shout,
@@ -99,13 +102,14 @@ internal static class PrivacyDefaults
ChatType.NoviceNetwork,
};
internal static readonly IReadOnlyDictionary<ChatType, int> CasualRetentionOverrides = new Dictionary<ChatType, int>
{
[ChatType.Say] = 1,
[ChatType.Shout] = 1,
[ChatType.Yell] = 1,
[ChatType.CustomEmote] = 1,
[ChatType.StandardEmote] = 1,
[ChatType.NoviceNetwork] = 1,
};
internal static readonly IReadOnlyDictionary<ChatType, int> CasualRetentionOverrides =
new Dictionary<ChatType, int>
{
[ChatType.Say] = 1,
[ChatType.Shout] = 1,
[ChatType.Yell] = 1,
[ChatType.CustomEmote] = 1,
[ChatType.StandardEmote] = 1,
[ChatType.NoviceNetwork] = 1,
};
}
+51 -43
View File
@@ -13,7 +13,8 @@ public sealed record ChatColourPreset(
string DisplayName,
string LocalizationKey,
bool IsBrandPreset,
IReadOnlyDictionary<ChatType, uint> Colours);
IReadOnlyDictionary<ChatType, uint> Colours
);
public static class ChatColourPresets
{
@@ -27,37 +28,44 @@ public static class ChatColourPresets
DisplayName: "ChatTwo Default",
LocalizationKey: "ChatColourPresets_Default",
IsBrandPreset: false,
Colours: BuildDefault()),
Colours: BuildDefault()
),
["HighContrast"] = new(
DisplayName: "High-Contrast",
LocalizationKey: "ChatColourPresets_HighContrast",
IsBrandPreset: false,
Colours: BuildHighContrast()),
Colours: BuildHighContrast()
),
["Pastell"] = new(
DisplayName: "Pastell",
LocalizationKey: "ChatColourPresets_Pastell",
IsBrandPreset: false,
Colours: BuildPastell()),
Colours: BuildPastell()
),
["DarkModeTuned"] = new(
DisplayName: "Dark-Mode-Tuned",
LocalizationKey: "ChatColourPresets_DarkModeTuned",
IsBrandPreset: false,
Colours: BuildDarkModeTuned()),
Colours: BuildDarkModeTuned()
),
["Hellion"] = new(
DisplayName: "Hellion",
LocalizationKey: "ChatColourPresets_Hellion",
IsBrandPreset: true,
Colours: BuildHellion()),
Colours: BuildHellion()
),
["NightBlue"] = new(
DisplayName: "Night Blue",
LocalizationKey: "ChatColourPresets_NightBlue",
IsBrandPreset: false,
Colours: BuildNightBlue()),
Colours: BuildNightBlue()
),
["IndigoViolet"] = new(
DisplayName: "Indigo Violet",
LocalizationKey: "ChatColourPresets_IndigoViolet",
IsBrandPreset: false,
Colours: BuildIndigoViolet()),
Colours: BuildIndigoViolet()
),
};
}
@@ -187,39 +195,39 @@ public static class ChatColourPresets
return new Dictionary<ChatType, uint>
{
// Standard / Tell — Cyan-Familie (Brand-Primary)
[ChatType.Say] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light #4DD9E8
[ChatType.Say] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light #4DD9E8
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan #00BED2
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark #0097A7
// Laute Channels — Ember/Warning
[ChatType.Yell] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning #F0AD4E
[ChatType.Shout] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember #F97316
[ChatType.Yell] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning #F0AD4E
[ChatType.Shout] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember #F97316
// Gruppen-Channels — Success/Ember-dark/Cyan
[ChatType.Party] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success #5CB85C
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark #E85D04
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(77, 217, 232),// Cyan-light
[ChatType.Party] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success #5CB85C
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark #E85D04
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light
// Linkshells 1-8 — über das ganze Brand-Spektrum verteilt
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(251, 146, 60), // Ember-light #FB923C
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(217, 83, 79), // Danger #D9534F
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(251, 146, 60), // Ember-light #FB923C
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(217, 83, 79), // Danger #D9534F
// CrossWorld-Linkshells 1-8 — dunklere/sattersere Varianten
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(200, 140, 50), // Warning-dark
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(60, 140, 60), // Success-dark
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(0, 110, 130), // Cyan-darker
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(220, 90, 30), // Ember-medium
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(170, 60, 60), // Danger-dark
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(200, 140, 50), // Warning-dark
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(60, 140, 60), // Success-dark
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(0, 110, 130), // Cyan-darker
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(220, 90, 30), // Ember-medium
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(170, 60, 60), // Danger-dark
};
}
@@ -233,19 +241,19 @@ public static class ChatColourPresets
return new Dictionary<ChatType, uint>
{
// Standard / Tell — Royal Blue Akzent-Familie
[ChatType.Say] = ColourUtil.ComponentsToRgba(230, 237, 247), // text-primary
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(106, 176, 255),// akzent-hot
[ChatType.Say] = ColourUtil.ComponentsToRgba(230, 237, 247), // text-primary
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(106, 176, 255), // akzent-hot
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
// Laute Channels — Warning/Danger Status-Töne
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 184, 74), // warning
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 92, 122), // danger
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 184, 74), // warning
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 92, 122), // danger
// Gruppen — Success/Akzent-Variations
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151), // success
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100), // warm-orange-light
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(140, 160, 191),// text-dim
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151), // success
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100), // warm-orange-light
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(140, 160, 191), // text-dim
// Linkshells 1-8 — über Spektrum verteilt
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 184, 74),
@@ -278,8 +286,8 @@ public static class ChatColourPresets
return new Dictionary<ChatType, uint>
{
// Standard / Tell — Royal Violet Akzent-Familie
[ChatType.Say] = ColourUtil.ComponentsToRgba(240, 230, 255), // text-primary (light lavender)
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(176, 124, 255),// akzent-hot
[ChatType.Say] = ColourUtil.ComponentsToRgba(240, 230, 255), // text-primary (light lavender)
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(176, 124, 255), // akzent-hot
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
// Laute Channels — geteilt mit Night Blue (Status-Farben)
@@ -289,8 +297,8 @@ public static class ChatColourPresets
// Gruppen
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151),
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100),
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(168, 144, 208),// text-dim
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(168, 144, 208), // text-dim
// Linkshells 1-8
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 184, 74),
@@ -40,7 +40,9 @@ internal sealed class ThemeSwitchSelfTestStep : ISelfTestStep
if (this.initialSlug is null)
{
this.initialSlug = active.Slug;
ImGui.Text($"Initial theme: \"{this.initialSlug}\". Open Settings -> Theme & Layout and pick a different theme.");
ImGui.Text(
$"Initial theme: \"{this.initialSlug}\". Open Settings -> Theme & Layout and pick a different theme."
);
return SelfTestStepResult.Waiting;
}
@@ -77,9 +79,6 @@ internal sealed class ThemeSwitchSelfTestStep : ISelfTestStep
private static bool HasPopulatedCache(Theme theme)
{
var cache = theme.AbgrCache;
return (cache.Primary
| cache.WindowBg
| cache.TextPrimary
| cache.Border) != 0u;
return (cache.Primary | cache.WindowBg | cache.TextPrimary | cache.Border) != 0u;
}
}
+3 -3
View File
@@ -32,12 +32,12 @@ public static class Sheets
}
public static bool IsInForay() =>
TerritorySheet.TryGetRow(Plugin.ClientState.TerritoryType, out var row) &&
row.TerritoryIntendedUse.RowId is 41 or 61;
TerritorySheet.TryGetRow(Plugin.ClientState.TerritoryType, out var row)
&& row.TerritoryIntendedUse.RowId is 41 or 61;
public static IEnumerable<World> WorldsOnDatacenter(IPlayerCharacter character)
{
var dcRow = character.HomeWorld.Value.DataCenter.RowId;
return WorldSheet.Where(world => world.IsPublic && world.DataCenter.RowId == dcRow);
}
}
}
+43 -41
View File
@@ -6,45 +6,47 @@ internal static class Chat2Classic
{
public const string Slug = "chat2-classic";
public static Theme Build() => new(
Slug: Slug,
Name: "Chat 2 Klassik",
Author: "Upstream (Infi & Anna)",
Description: "Steel-blue accents on neutral dark grey, eckige Kanten. Vertraut für ChatTwo-Veteranen.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#3D6E92"),
Primary: ColourUtil.HexToRgba("#4682B4"),
PrimaryLight: ColourUtil.HexToRgba("#5C9DC8"),
PrimaryGlow: ColourUtil.HexToRgba("#4682B466"),
AccentDark: ColourUtil.HexToRgba("#3D6E92"),
Accent: ColourUtil.HexToRgba("#4682B4"),
AccentLight: ColourUtil.HexToRgba("#5C9DC8"),
Identity: ColourUtil.HexToRgba("#4682B4"),
WindowBg: ColourUtil.HexToRgba("#0F0F0FF2"),
ChildBg: ColourUtil.HexToRgba("#141414"),
FrameBg: ColourUtil.HexToRgba("#1A1A1A"),
Surface: ColourUtil.HexToRgba("#202020"),
SurfaceHover: ColourUtil.HexToRgba("#2C2C2C"),
Border: ColourUtil.HexToRgba("#404040"),
TextPrimary: ColourUtil.HexToRgba("#E6E6E6"),
TextMuted: ColourUtil.HexToRgba("#999999"),
TextDim: ColourUtil.HexToRgba("#666666"),
StatusSuccess: ColourUtil.HexToRgba("#5CB85C"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#F0AD4E"),
StatusInfo: ColourUtil.HexToRgba("#4682B4")
),
Layout: new ThemeLayout(
WindowRounding: 0f, ChildRounding: 0f, PopupRounding: 0f,
FrameRounding: 0f, GrabRounding: 0f, TabRounding: 0f,
ScrollbarRounding: 0f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Chat 2 Klassik",
Author: "Upstream (Infi & Anna)",
Description: "Steel-blue accents on neutral dark grey, eckige Kanten. Vertraut für ChatTwo-Veteranen.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#3D6E92"),
Primary: ColourUtil.HexToRgba("#4682B4"),
PrimaryLight: ColourUtil.HexToRgba("#5C9DC8"),
PrimaryGlow: ColourUtil.HexToRgba("#4682B466"),
AccentDark: ColourUtil.HexToRgba("#3D6E92"),
Accent: ColourUtil.HexToRgba("#4682B4"),
AccentLight: ColourUtil.HexToRgba("#5C9DC8"),
Identity: ColourUtil.HexToRgba("#4682B4"),
WindowBg: ColourUtil.HexToRgba("#0F0F0FF2"),
ChildBg: ColourUtil.HexToRgba("#141414"),
FrameBg: ColourUtil.HexToRgba("#1A1A1A"),
Surface: ColourUtil.HexToRgba("#202020"),
SurfaceHover: ColourUtil.HexToRgba("#2C2C2C"),
Border: ColourUtil.HexToRgba("#404040"),
TextPrimary: ColourUtil.HexToRgba("#E6E6E6"),
TextMuted: ColourUtil.HexToRgba("#999999"),
TextDim: ColourUtil.HexToRgba("#666666"),
StatusSuccess: ColourUtil.HexToRgba("#5CB85C"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#F0AD4E"),
StatusInfo: ColourUtil.HexToRgba("#4682B4")
),
Layout: new ThemeLayout(
WindowRounding: 0f,
ChildRounding: 0f,
PopupRounding: 0f,
FrameRounding: 0f,
GrabRounding: 0f,
TabRounding: 0f,
ScrollbarRounding: 0f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true
);
}
+72 -68
View File
@@ -6,72 +6,76 @@ internal static class EventHorizon
{
public const string Slug = "event-horizon";
public static Theme Build() => new(
Slug: Slug,
Name: "Event Horizon",
Author: "Hellion Forge",
Description: "Cosmic Purple auf Near-Black. Deep-Space-Stimmung.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#7B3FCF"),
Primary: ColourUtil.HexToRgba("#9D5CFF"),
PrimaryLight: ColourUtil.HexToRgba("#B585FF"),
PrimaryGlow: ColourUtil.HexToRgba("#9D5CFF99"),
AccentDark: ColourUtil.HexToRgba("#C9982E"),
Accent: ColourUtil.HexToRgba("#E0AB36"),
AccentLight: ColourUtil.HexToRgba("#F2C25C"),
Identity: ColourUtil.HexToRgba("#9D5CFF"),
WindowBg: ColourUtil.HexToRgba("#040308"),
ChildBg: ColourUtil.HexToRgba("#0A081A"),
FrameBg: ColourUtil.HexToRgba("#140F23"),
Surface: ColourUtil.HexToRgba("#1B1530"),
SurfaceHover: ColourUtil.HexToRgba("#251D40"),
Border: ColourUtil.HexToRgba("#9D5CFF44"),
TextPrimary: ColourUtil.HexToRgba("#E6E0F5"),
TextMuted: ColourUtil.HexToRgba("#9890B5"),
TextDim: ColourUtil.HexToRgba("#5A5570"),
StatusSuccess: ColourUtil.HexToRgba("#26A269"),
StatusDanger: ColourUtil.HexToRgba("#ED333B"),
StatusWarning: ColourUtil.HexToRgba("#E0AB36"),
StatusInfo: ColourUtil.HexToRgba("#9D5CFF")
),
Layout: new ThemeLayout(
WindowRounding: 6f, ChildRounding: 5f, PopupRounding: 5f,
FrameRounding: 4f, GrabRounding: 4f, TabRounding: 4f,
ScrollbarRounding: 4f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Event Horizon — Cosmic-Purple-Drift: helle Pastelle bekommen
// Lavender-Tinte, Akzent-Channels (Tell) ziehen Richtung Magenta-
// Lila. Channel-Identität bleibt klar erkennbar.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#90A0FF"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FFAA80"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#9090E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#90A0FF"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0B070"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F2C25C"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E0B0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#90A0FF"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#B585FF"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#D0A0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E0B870"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E0B870"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#9890B5"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Event Horizon",
Author: "Hellion Forge",
Description: "Cosmic Purple auf Near-Black. Deep-Space-Stimmung.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#7B3FCF"),
Primary: ColourUtil.HexToRgba("#9D5CFF"),
PrimaryLight: ColourUtil.HexToRgba("#B585FF"),
PrimaryGlow: ColourUtil.HexToRgba("#9D5CFF99"),
AccentDark: ColourUtil.HexToRgba("#C9982E"),
Accent: ColourUtil.HexToRgba("#E0AB36"),
AccentLight: ColourUtil.HexToRgba("#F2C25C"),
Identity: ColourUtil.HexToRgba("#9D5CFF"),
WindowBg: ColourUtil.HexToRgba("#040308"),
ChildBg: ColourUtil.HexToRgba("#0A081A"),
FrameBg: ColourUtil.HexToRgba("#140F23"),
Surface: ColourUtil.HexToRgba("#1B1530"),
SurfaceHover: ColourUtil.HexToRgba("#251D40"),
Border: ColourUtil.HexToRgba("#9D5CFF44"),
TextPrimary: ColourUtil.HexToRgba("#E6E0F5"),
TextMuted: ColourUtil.HexToRgba("#9890B5"),
TextDim: ColourUtil.HexToRgba("#5A5570"),
StatusSuccess: ColourUtil.HexToRgba("#26A269"),
StatusDanger: ColourUtil.HexToRgba("#ED333B"),
StatusWarning: ColourUtil.HexToRgba("#E0AB36"),
StatusInfo: ColourUtil.HexToRgba("#9D5CFF")
),
Layout: new ThemeLayout(
WindowRounding: 6f,
ChildRounding: 5f,
PopupRounding: 5f,
FrameRounding: 4f,
GrabRounding: 4f,
TabRounding: 4f,
ScrollbarRounding: 4f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Event Horizon — Cosmic-Purple-Drift: helle Pastelle bekommen
// Lavender-Tinte, Akzent-Channels (Tell) ziehen Richtung Magenta-
// Lila. Channel-Identität bleibt klar erkennbar.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#90A0FF"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FFAA80"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#9090E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#90A0FF"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0B070"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F2C25C"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E0B0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#90A0FF"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#B585FF"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#D0A0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E0B870"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E0B870"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#9890B5"),
}
)
);
}
+71 -67
View File
@@ -6,71 +6,75 @@ internal static class ForgeMerchantman
{
public const string Slug = "forge-merchantman";
public static Theme Build() => new(
Slug: Slug,
Name: "Forge Merchantman",
Author: "Carla Beleandis",
Description: "Patina Bronze auf Workshop-Slate — Hellion Forge im Plugin.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#1F8A82"),
Primary: ColourUtil.HexToRgba("#2DB39E"),
PrimaryLight: ColourUtil.HexToRgba("#4FC9B0"),
PrimaryGlow: ColourUtil.HexToRgba("#2DB39E99"),
AccentDark: ColourUtil.HexToRgba("#B86A20"),
Accent: ColourUtil.HexToRgba("#D9892C"),
AccentLight: ColourUtil.HexToRgba("#E8A04A"),
Identity: ColourUtil.HexToRgba("#1F8A82"),
WindowBg: ColourUtil.HexToRgba("#050B0A"),
ChildBg: ColourUtil.HexToRgba("#0B1413"),
FrameBg: ColourUtil.HexToRgba("#11201D"),
Surface: ColourUtil.HexToRgba("#182925"),
SurfaceHover: ColourUtil.HexToRgba("#213631"),
Border: ColourUtil.HexToRgba("#2DB39E66"),
TextPrimary: ColourUtil.HexToRgba("#D8EFE8"),
TextMuted: ColourUtil.HexToRgba("#8FA39B"),
TextDim: ColourUtil.HexToRgba("#5A6E66"),
StatusSuccess: ColourUtil.HexToRgba("#5CB85C"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#F0AD4E"),
StatusInfo: ColourUtil.HexToRgba("#2DB39E")
),
Layout: new ThemeLayout(
WindowRounding: 4f, ChildRounding: 3f, PopupRounding: 3f,
FrameRounding: 2f, GrabRounding: 2f, TabRounding: 2f,
ScrollbarRounding: 2f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Forge Merchantman — Patina-Tinte in Party/FC, Bernstein-Tinte in
// Yell/Alliance/CustomEmote. Channel-identity bleibt voll erhalten.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#6AC9B0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#E8A04A"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4FB8A0"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#6AC9B0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#E8A04A"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0C060"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E8B0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#6AC9B0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A8A0F0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#8FA39B"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Forge Merchantman",
Author: "Carla Beleandis",
Description: "Patina Bronze auf Workshop-Slate — Hellion Forge im Plugin.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#1F8A82"),
Primary: ColourUtil.HexToRgba("#2DB39E"),
PrimaryLight: ColourUtil.HexToRgba("#4FC9B0"),
PrimaryGlow: ColourUtil.HexToRgba("#2DB39E99"),
AccentDark: ColourUtil.HexToRgba("#B86A20"),
Accent: ColourUtil.HexToRgba("#D9892C"),
AccentLight: ColourUtil.HexToRgba("#E8A04A"),
Identity: ColourUtil.HexToRgba("#1F8A82"),
WindowBg: ColourUtil.HexToRgba("#050B0A"),
ChildBg: ColourUtil.HexToRgba("#0B1413"),
FrameBg: ColourUtil.HexToRgba("#11201D"),
Surface: ColourUtil.HexToRgba("#182925"),
SurfaceHover: ColourUtil.HexToRgba("#213631"),
Border: ColourUtil.HexToRgba("#2DB39E66"),
TextPrimary: ColourUtil.HexToRgba("#D8EFE8"),
TextMuted: ColourUtil.HexToRgba("#8FA39B"),
TextDim: ColourUtil.HexToRgba("#5A6E66"),
StatusSuccess: ColourUtil.HexToRgba("#5CB85C"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#F0AD4E"),
StatusInfo: ColourUtil.HexToRgba("#2DB39E")
),
Layout: new ThemeLayout(
WindowRounding: 4f,
ChildRounding: 3f,
PopupRounding: 3f,
FrameRounding: 2f,
GrabRounding: 2f,
TabRounding: 2f,
ScrollbarRounding: 2f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Forge Merchantman — Patina-Tinte in Party/FC, Bernstein-Tinte in
// Yell/Alliance/CustomEmote. Channel-identity bleibt voll erhalten.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#6AC9B0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#E8A04A"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4FB8A0"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#6AC9B0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#E8A04A"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0C060"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E8B0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#6AC9B0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A8A0F0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#8FA39B"),
}
)
);
}
+71 -67
View File
@@ -6,71 +6,75 @@ internal static class HellionArctic
{
public const string Slug = "hellion-arctic";
public static Theme Build() => new(
Slug: Slug,
Name: "Hellion Arctic",
Author: "Hellion Forge",
Description: "Arctic Cyan + Ember Glow on industrial slate. Plugin default.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#0097A7"),
Primary: ColourUtil.HexToRgba("#00BED2"),
PrimaryLight: ColourUtil.HexToRgba("#4DD9E8"),
PrimaryGlow: ColourUtil.HexToRgba("#00BED299"),
AccentDark: ColourUtil.HexToRgba("#E85D04"),
Accent: ColourUtil.HexToRgba("#F97316"),
AccentLight: ColourUtil.HexToRgba("#FB923C"),
Identity: ColourUtil.HexToRgba("#0097A7"),
WindowBg: ColourUtil.HexToRgba("#070B12"),
ChildBg: ColourUtil.HexToRgba("#0C1220"),
FrameBg: ColourUtil.HexToRgba("#141E30"),
Surface: ColourUtil.HexToRgba("#1A2538"),
SurfaceHover: ColourUtil.HexToRgba("#22303F"),
Border: ColourUtil.HexToRgba("#00BED266"),
TextPrimary: ColourUtil.HexToRgba("#E6F4F1"),
TextMuted: ColourUtil.HexToRgba("#8FA3B5"),
TextDim: ColourUtil.HexToRgba("#566273"),
StatusSuccess: ColourUtil.HexToRgba("#5CB85C"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#F0AD4E"),
StatusInfo: ColourUtil.HexToRgba("#00BED2")
),
Layout: new ThemeLayout(
WindowRounding: 4f, ChildRounding: 3f, PopupRounding: 3f,
FrameRounding: 2f, GrabRounding: 2f, TabRounding: 2f,
ScrollbarRounding: 2f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Hellion Arctic — FFXIV-Standard mit dezenter Cyan-Tinte in den
// blauen Channels (Party/FC). Channel-Identität bleibt klar.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#80C0E8"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FFB870"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4DD9E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#80C0E8"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#FFC080"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFE066"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E8A8"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#80C0E8"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A8A0F0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#C0C0C0"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Hellion Arctic",
Author: "Hellion Forge",
Description: "Arctic Cyan + Ember Glow on industrial slate. Plugin default.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#0097A7"),
Primary: ColourUtil.HexToRgba("#00BED2"),
PrimaryLight: ColourUtil.HexToRgba("#4DD9E8"),
PrimaryGlow: ColourUtil.HexToRgba("#00BED299"),
AccentDark: ColourUtil.HexToRgba("#E85D04"),
Accent: ColourUtil.HexToRgba("#F97316"),
AccentLight: ColourUtil.HexToRgba("#FB923C"),
Identity: ColourUtil.HexToRgba("#0097A7"),
WindowBg: ColourUtil.HexToRgba("#070B12"),
ChildBg: ColourUtil.HexToRgba("#0C1220"),
FrameBg: ColourUtil.HexToRgba("#141E30"),
Surface: ColourUtil.HexToRgba("#1A2538"),
SurfaceHover: ColourUtil.HexToRgba("#22303F"),
Border: ColourUtil.HexToRgba("#00BED266"),
TextPrimary: ColourUtil.HexToRgba("#E6F4F1"),
TextMuted: ColourUtil.HexToRgba("#8FA3B5"),
TextDim: ColourUtil.HexToRgba("#566273"),
StatusSuccess: ColourUtil.HexToRgba("#5CB85C"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#F0AD4E"),
StatusInfo: ColourUtil.HexToRgba("#00BED2")
),
Layout: new ThemeLayout(
WindowRounding: 4f,
ChildRounding: 3f,
PopupRounding: 3f,
FrameRounding: 2f,
GrabRounding: 2f,
TabRounding: 2f,
ScrollbarRounding: 2f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Hellion Arctic — FFXIV-Standard mit dezenter Cyan-Tinte in den
// blauen Channels (Party/FC). Channel-Identität bleibt klar.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#80C0E8"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FFB870"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4DD9E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#80C0E8"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#FFC080"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFE066"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E8A8"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#80C0E8"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A8A0F0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8C880"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#C0C0C0"),
}
)
);
}
+72 -68
View File
@@ -12,72 +12,76 @@ internal static class HellionSpectrum
{
public const string Slug = "hellion-spectrum";
public static Theme Build() => new(
Slug: Slug,
Name: "Hellion Spectrum",
Author: "Hellion Forge",
Description: "Deuteran/Protan-safe channels — Wong palette tones, channel identity preserved.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#005983"),
Primary: ColourUtil.HexToRgba("#0072B2"),
PrimaryLight: ColourUtil.HexToRgba("#3E9BD0"),
PrimaryGlow: ColourUtil.HexToRgba("#0072B299"),
AccentDark: ColourUtil.HexToRgba("#B07F00"),
Accent: ColourUtil.HexToRgba("#E69F00"),
AccentLight: ColourUtil.HexToRgba("#F0B73A"),
Identity: ColourUtil.HexToRgba("#005983"),
WindowBg: ColourUtil.HexToRgba("#0A0F14"),
ChildBg: ColourUtil.HexToRgba("#101620"),
FrameBg: ColourUtil.HexToRgba("#1A222E"),
Surface: ColourUtil.HexToRgba("#22303F"),
SurfaceHover: ColourUtil.HexToRgba("#2D3E51"),
Border: ColourUtil.HexToRgba("#0072B266"),
TextPrimary: ColourUtil.HexToRgba("#F0F4F8"),
TextMuted: ColourUtil.HexToRgba("#9AA8B5"),
TextDim: ColourUtil.HexToRgba("#5E6B78"),
StatusSuccess: ColourUtil.HexToRgba("#009E73"),
StatusDanger: ColourUtil.HexToRgba("#D55E00"),
StatusWarning: ColourUtil.HexToRgba("#F0E442"),
StatusInfo: ColourUtil.HexToRgba("#56B4E9")
),
Layout: new ThemeLayout(
WindowRounding: 6f, ChildRounding: 5f, PopupRounding: 5f,
FrameRounding: 4f, GrabRounding: 4f, TabRounding: 4f,
ScrollbarRounding: 4f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Hellion Spectrum — Wong/Okabe-Ito tones within FFXIV channel
// identity. FC pulled slightly greener than vanilla cyan-teal so
// Party-blue and FC-green stay separable under deuteran sim.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0E442"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#D55E00"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#CC79A7"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#CC79A7"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#56B4E9"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#E69F00"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#009E73"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#94CC4A"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#56B4E9"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#94CC4A"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#E69F00"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0E442"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#66D9A8"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#56B4E9"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#8B7DD0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#E0A0C0"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#DAA0DA"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#C9A56F"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#C9A56F"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#C0C0C0"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Hellion Spectrum",
Author: "Hellion Forge",
Description: "Deuteran/Protan-safe channels — Wong palette tones, channel identity preserved.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#005983"),
Primary: ColourUtil.HexToRgba("#0072B2"),
PrimaryLight: ColourUtil.HexToRgba("#3E9BD0"),
PrimaryGlow: ColourUtil.HexToRgba("#0072B299"),
AccentDark: ColourUtil.HexToRgba("#B07F00"),
Accent: ColourUtil.HexToRgba("#E69F00"),
AccentLight: ColourUtil.HexToRgba("#F0B73A"),
Identity: ColourUtil.HexToRgba("#005983"),
WindowBg: ColourUtil.HexToRgba("#0A0F14"),
ChildBg: ColourUtil.HexToRgba("#101620"),
FrameBg: ColourUtil.HexToRgba("#1A222E"),
Surface: ColourUtil.HexToRgba("#22303F"),
SurfaceHover: ColourUtil.HexToRgba("#2D3E51"),
Border: ColourUtil.HexToRgba("#0072B266"),
TextPrimary: ColourUtil.HexToRgba("#F0F4F8"),
TextMuted: ColourUtil.HexToRgba("#9AA8B5"),
TextDim: ColourUtil.HexToRgba("#5E6B78"),
StatusSuccess: ColourUtil.HexToRgba("#009E73"),
StatusDanger: ColourUtil.HexToRgba("#D55E00"),
StatusWarning: ColourUtil.HexToRgba("#F0E442"),
StatusInfo: ColourUtil.HexToRgba("#56B4E9")
),
Layout: new ThemeLayout(
WindowRounding: 6f,
ChildRounding: 5f,
PopupRounding: 5f,
FrameRounding: 4f,
GrabRounding: 4f,
TabRounding: 4f,
ScrollbarRounding: 4f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Hellion Spectrum — Wong/Okabe-Ito tones within FFXIV channel
// identity. FC pulled slightly greener than vanilla cyan-teal so
// Party-blue and FC-green stay separable under deuteran sim.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0E442"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#D55E00"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#CC79A7"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#CC79A7"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#56B4E9"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#E69F00"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#009E73"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#94CC4A"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#56B4E9"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#94CC4A"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#E69F00"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0E442"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#66D9A8"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#56B4E9"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#8B7DD0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#E0A0C0"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#DAA0DA"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#C9A56F"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#C9A56F"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#C0C0C0"),
}
)
);
}
+72 -68
View File
@@ -6,72 +6,76 @@ internal static class IndigoViolet
{
public const string Slug = "indigo-violet";
public static Theme Build() => new(
Slug: Slug,
Name: "Indigo Violet",
Author: "Julia Moon",
Description: "Royal Violet auf Deep Indigo — Glitter-Galaxy mit Türkis-Mint-Aurora.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#6B3AB0"),
Primary: ColourUtil.HexToRgba("#8B4DDE"),
PrimaryLight: ColourUtil.HexToRgba("#B07CFF"),
PrimaryGlow: ColourUtil.HexToRgba("#8B4DDE99"),
AccentDark: ColourUtil.HexToRgba("#36A89C"),
Accent: ColourUtil.HexToRgba("#4FC9B8"),
AccentLight: ColourUtil.HexToRgba("#7AE0CF"),
Identity: ColourUtil.HexToRgba("#6B3AB0"),
WindowBg: ColourUtil.HexToRgba("#0D061F"),
ChildBg: ColourUtil.HexToRgba("#1A0D3D"),
FrameBg: ColourUtil.HexToRgba("#2A1556"),
Surface: ColourUtil.HexToRgba("#3D1F78"),
SurfaceHover: ColourUtil.HexToRgba("#5B2A9A"),
Border: ColourUtil.HexToRgba("#8B4DDE66"),
TextPrimary: ColourUtil.HexToRgba("#F0E6FF"),
TextMuted: ColourUtil.HexToRgba("#A890D0"),
TextDim: ColourUtil.HexToRgba("#7560A0"),
StatusSuccess: ColourUtil.HexToRgba("#3DDC97"),
StatusDanger: ColourUtil.HexToRgba("#FF5C7A"),
StatusWarning: ColourUtil.HexToRgba("#FFB84A"),
StatusInfo: ColourUtil.HexToRgba("#8B4DDE")
),
Layout: new ThemeLayout(
WindowRounding: 6f, ChildRounding: 5f, PopupRounding: 5f,
FrameRounding: 4f, GrabRounding: 4f, TabRounding: 4f,
ScrollbarRounding: 4f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Indigo Violet — Lavender-Pink-Drift in Tell und LS6/7. Türkis-
// Mint-Aurora-Counter in Party/FC und LS4. Glitter-Gold in Yell.
// Differenzierung zu Event Horizon: dunkler, dichter, Türkis statt Gold.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#6AB8D0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#F0A878"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4FC9B8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#6AB8D0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0BC92"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0D880"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E0C0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#6AB8D0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#B07CFF"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#C098D8"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#A890D0"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Indigo Violet",
Author: "Julia Moon",
Description: "Royal Violet auf Deep Indigo — Glitter-Galaxy mit Türkis-Mint-Aurora.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#6B3AB0"),
Primary: ColourUtil.HexToRgba("#8B4DDE"),
PrimaryLight: ColourUtil.HexToRgba("#B07CFF"),
PrimaryGlow: ColourUtil.HexToRgba("#8B4DDE99"),
AccentDark: ColourUtil.HexToRgba("#36A89C"),
Accent: ColourUtil.HexToRgba("#4FC9B8"),
AccentLight: ColourUtil.HexToRgba("#7AE0CF"),
Identity: ColourUtil.HexToRgba("#6B3AB0"),
WindowBg: ColourUtil.HexToRgba("#0D061F"),
ChildBg: ColourUtil.HexToRgba("#1A0D3D"),
FrameBg: ColourUtil.HexToRgba("#2A1556"),
Surface: ColourUtil.HexToRgba("#3D1F78"),
SurfaceHover: ColourUtil.HexToRgba("#5B2A9A"),
Border: ColourUtil.HexToRgba("#8B4DDE66"),
TextPrimary: ColourUtil.HexToRgba("#F0E6FF"),
TextMuted: ColourUtil.HexToRgba("#A890D0"),
TextDim: ColourUtil.HexToRgba("#7560A0"),
StatusSuccess: ColourUtil.HexToRgba("#3DDC97"),
StatusDanger: ColourUtil.HexToRgba("#FF5C7A"),
StatusWarning: ColourUtil.HexToRgba("#FFB84A"),
StatusInfo: ColourUtil.HexToRgba("#8B4DDE")
),
Layout: new ThemeLayout(
WindowRounding: 6f,
ChildRounding: 5f,
PopupRounding: 5f,
FrameRounding: 4f,
GrabRounding: 4f,
TabRounding: 4f,
ScrollbarRounding: 4f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Indigo Violet — Lavender-Pink-Drift in Tell und LS6/7. Türkis-
// Mint-Aurora-Counter in Party/FC und LS4. Glitter-Gold in Yell.
// Differenzierung zu Event Horizon: dunkler, dichter, Türkis statt Gold.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#6AB8D0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#F0A878"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4FC9B8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#6AB8D0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A0E090"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0BC92"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0D880"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E0C0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#6AB8D0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#B07CFF"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#E090FF"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#C098D8"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#A890D0"),
}
)
);
}
+72 -68
View File
@@ -6,72 +6,76 @@ internal static class MintGrove
{
public const string Slug = "mint-grove";
public static Theme Build() => new(
Slug: Slug,
Name: "Mint Grove",
Author: "Carla Beleandis",
Description: "Mint Green + Honey Amber auf Deep Forest. Naturthemen-tauglich.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#3CB371"),
Primary: ColourUtil.HexToRgba("#5DD39E"),
PrimaryLight: ColourUtil.HexToRgba("#8FE0B8"),
PrimaryGlow: ColourUtil.HexToRgba("#5DD39E99"),
AccentDark: ColourUtil.HexToRgba("#F4C870"),
Accent: ColourUtil.HexToRgba("#F9D580"),
AccentLight: ColourUtil.HexToRgba("#FCDD93"),
Identity: ColourUtil.HexToRgba("#5DD39E"),
WindowBg: ColourUtil.HexToRgba("#0A1410"),
ChildBg: ColourUtil.HexToRgba("#10201A"),
FrameBg: ColourUtil.HexToRgba("#162B22"),
Surface: ColourUtil.HexToRgba("#1E372B"),
SurfaceHover: ColourUtil.HexToRgba("#284335"),
Border: ColourUtil.HexToRgba("#5DD39E55"),
TextPrimary: ColourUtil.HexToRgba("#E8F5EA"),
TextMuted: ColourUtil.HexToRgba("#9BB5A5"),
TextDim: ColourUtil.HexToRgba("#5C6F65"),
StatusSuccess: ColourUtil.HexToRgba("#5DD39E"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#E8B590"),
StatusInfo: ColourUtil.HexToRgba("#5DA9C7")
),
Layout: new ThemeLayout(
WindowRounding: 5f, ChildRounding: 4f, PopupRounding: 4f,
FrameRounding: 3f, GrabRounding: 3f, TabRounding: 3f,
ScrollbarRounding: 3f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Mint Grove — Naturthemen-Tönung: Honey-Amber in Yell-Familie,
// Mint-Drift in NoviceNetwork und Linkshell. Tell-Pink-Identität
// bleibt erhalten für Erkennbarkeit.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#F098C8"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#F098C8"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#80B8D0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#F0B070"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#80C8B0"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#8FE0B8"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#80B8D0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#8FE0B8"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0BC80"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F9D580"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E0A0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#80B8D0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A89DC0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#F098C8"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#D0A8C8"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8C088"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8C088"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#9BB5A5"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Mint Grove",
Author: "Carla Beleandis",
Description: "Mint Green + Honey Amber auf Deep Forest. Naturthemen-tauglich.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#3CB371"),
Primary: ColourUtil.HexToRgba("#5DD39E"),
PrimaryLight: ColourUtil.HexToRgba("#8FE0B8"),
PrimaryGlow: ColourUtil.HexToRgba("#5DD39E99"),
AccentDark: ColourUtil.HexToRgba("#F4C870"),
Accent: ColourUtil.HexToRgba("#F9D580"),
AccentLight: ColourUtil.HexToRgba("#FCDD93"),
Identity: ColourUtil.HexToRgba("#5DD39E"),
WindowBg: ColourUtil.HexToRgba("#0A1410"),
ChildBg: ColourUtil.HexToRgba("#10201A"),
FrameBg: ColourUtil.HexToRgba("#162B22"),
Surface: ColourUtil.HexToRgba("#1E372B"),
SurfaceHover: ColourUtil.HexToRgba("#284335"),
Border: ColourUtil.HexToRgba("#5DD39E55"),
TextPrimary: ColourUtil.HexToRgba("#E8F5EA"),
TextMuted: ColourUtil.HexToRgba("#9BB5A5"),
TextDim: ColourUtil.HexToRgba("#5C6F65"),
StatusSuccess: ColourUtil.HexToRgba("#5DD39E"),
StatusDanger: ColourUtil.HexToRgba("#D9534F"),
StatusWarning: ColourUtil.HexToRgba("#E8B590"),
StatusInfo: ColourUtil.HexToRgba("#5DA9C7")
),
Layout: new ThemeLayout(
WindowRounding: 5f,
ChildRounding: 4f,
PopupRounding: 4f,
FrameRounding: 3f,
GrabRounding: 3f,
TabRounding: 3f,
ScrollbarRounding: 3f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Mint Grove — Naturthemen-Tönung: Honey-Amber in Yell-Familie,
// Mint-Drift in NoviceNetwork und Linkshell. Tell-Pink-Identität
// bleibt erhalten für Erkennbarkeit.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#F098C8"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#F098C8"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#80B8D0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#F0B070"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#80C8B0"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#8FE0B8"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#80B8D0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#8FE0B8"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0BC80"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F9D580"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E0A0"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#80B8D0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A89DC0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#F098C8"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#D0A8C8"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8C088"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8C088"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#9BB5A5"),
}
)
);
}
+71 -67
View File
@@ -6,71 +6,75 @@ internal static class MoonlitBloom
{
public const string Slug = "moonlit-bloom";
public static Theme Build() => new(
Slug: Slug,
Name: "Moonlit Bloom",
Author: "Hellion Forge",
Description: "Bloom Magenta + Soft Sage auf Deep Violet Night.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#C957D0"),
Primary: ColourUtil.HexToRgba("#E374E8"),
PrimaryLight: ColourUtil.HexToRgba("#EF8AF4"),
PrimaryGlow: ColourUtil.HexToRgba("#E374E899"),
AccentDark: ColourUtil.HexToRgba("#7AAC5C"),
Accent: ColourUtil.HexToRgba("#9CCB7C"),
AccentLight: ColourUtil.HexToRgba("#B6E297"),
Identity: ColourUtil.HexToRgba("#E374E8"),
WindowBg: ColourUtil.HexToRgba("#0E0C1F"),
ChildBg: ColourUtil.HexToRgba("#15122B"),
FrameBg: ColourUtil.HexToRgba("#1F1A38"),
Surface: ColourUtil.HexToRgba("#28224A"),
SurfaceHover: ColourUtil.HexToRgba("#332B5B"),
Border: ColourUtil.HexToRgba("#E374E844"),
TextPrimary: ColourUtil.HexToRgba("#ECE6F5"),
TextMuted: ColourUtil.HexToRgba("#9A8BB0"),
TextDim: ColourUtil.HexToRgba("#554B6E"),
StatusSuccess: ColourUtil.HexToRgba("#7AAC5C"),
StatusDanger: ColourUtil.HexToRgba("#E85C6A"),
StatusWarning: ColourUtil.HexToRgba("#E8B590"),
StatusInfo: ColourUtil.HexToRgba("#6278FF")
),
Layout: new ThemeLayout(
WindowRounding: 6f, ChildRounding: 5f, PopupRounding: 5f,
FrameRounding: 4f, GrabRounding: 4f, TabRounding: 4f,
ScrollbarRounding: 4f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Moonlit Bloom — Bloom-Magenta-Tönung. Sage-Drift in NoviceNetwork
// und Linkshell4. Tell-Pink-Identität bleibt sichtbar.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#ECE6F5"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D080"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#EF8AF4"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#EF8AF4"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#A0B0F0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#F0B090"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#A8C8E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#9CCB7C"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#A0B0F0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#9CCB7C"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0BC92"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0D080"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#B6E297"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#A0B0F0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#C098D8"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#EF8AF4"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0E8"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#9A8BB0"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Moonlit Bloom",
Author: "Hellion Forge",
Description: "Bloom Magenta + Soft Sage auf Deep Violet Night.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#C957D0"),
Primary: ColourUtil.HexToRgba("#E374E8"),
PrimaryLight: ColourUtil.HexToRgba("#EF8AF4"),
PrimaryGlow: ColourUtil.HexToRgba("#E374E899"),
AccentDark: ColourUtil.HexToRgba("#7AAC5C"),
Accent: ColourUtil.HexToRgba("#9CCB7C"),
AccentLight: ColourUtil.HexToRgba("#B6E297"),
Identity: ColourUtil.HexToRgba("#E374E8"),
WindowBg: ColourUtil.HexToRgba("#0E0C1F"),
ChildBg: ColourUtil.HexToRgba("#15122B"),
FrameBg: ColourUtil.HexToRgba("#1F1A38"),
Surface: ColourUtil.HexToRgba("#28224A"),
SurfaceHover: ColourUtil.HexToRgba("#332B5B"),
Border: ColourUtil.HexToRgba("#E374E844"),
TextPrimary: ColourUtil.HexToRgba("#ECE6F5"),
TextMuted: ColourUtil.HexToRgba("#9A8BB0"),
TextDim: ColourUtil.HexToRgba("#554B6E"),
StatusSuccess: ColourUtil.HexToRgba("#7AAC5C"),
StatusDanger: ColourUtil.HexToRgba("#E85C6A"),
StatusWarning: ColourUtil.HexToRgba("#E8B590"),
StatusInfo: ColourUtil.HexToRgba("#6278FF")
),
Layout: new ThemeLayout(
WindowRounding: 6f,
ChildRounding: 5f,
PopupRounding: 5f,
FrameRounding: 4f,
GrabRounding: 4f,
TabRounding: 4f,
ScrollbarRounding: 4f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Moonlit Bloom — Bloom-Magenta-Tönung. Sage-Drift in NoviceNetwork
// und Linkshell4. Tell-Pink-Identität bleibt sichtbar.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#ECE6F5"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D080"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#EF8AF4"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#EF8AF4"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#A0B0F0"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#F0B090"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#A8C8E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#9CCB7C"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#A0B0F0"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#9CCB7C"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#F0BC92"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#F0D080"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#B6E297"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#A0B0F0"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#C098D8"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#EF8AF4"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0E8"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8B590"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#9A8BB0"),
}
)
);
}
+71 -67
View File
@@ -6,71 +6,75 @@ internal static class NightBlue
{
public const string Slug = "night-blue";
public static Theme Build() => new(
Slug: Slug,
Name: "Night Blue",
Author: "Julia Moon",
Description: "Royal Blue auf Marineblau — kühles Tech-Dashboard-Mood.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#3576C0"),
Primary: ColourUtil.HexToRgba("#4A90E2"),
PrimaryLight: ColourUtil.HexToRgba("#6AB0FF"),
PrimaryGlow: ColourUtil.HexToRgba("#4A90E299"),
AccentDark: ColourUtil.HexToRgba("#C97A2E"),
Accent: ColourUtil.HexToRgba("#E8A040"),
AccentLight: ColourUtil.HexToRgba("#F4B968"),
Identity: ColourUtil.HexToRgba("#3576C0"),
WindowBg: ColourUtil.HexToRgba("#050B18"),
ChildBg: ColourUtil.HexToRgba("#0A1628"),
FrameBg: ColourUtil.HexToRgba("#122039"),
Surface: ColourUtil.HexToRgba("#1A2D4F"),
SurfaceHover: ColourUtil.HexToRgba("#234070"),
Border: ColourUtil.HexToRgba("#4A90E266"),
TextPrimary: ColourUtil.HexToRgba("#E6EDF7"),
TextMuted: ColourUtil.HexToRgba("#8CA0BF"),
TextDim: ColourUtil.HexToRgba("#5A6F8F"),
StatusSuccess: ColourUtil.HexToRgba("#3DDC97"),
StatusDanger: ColourUtil.HexToRgba("#FF5C7A"),
StatusWarning: ColourUtil.HexToRgba("#FFB84A"),
StatusInfo: ColourUtil.HexToRgba("#4A90E2")
),
Layout: new ThemeLayout(
WindowRounding: 6f, ChildRounding: 5f, PopupRounding: 5f,
FrameRounding: 4f, GrabRounding: 4f, TabRounding: 4f,
ScrollbarRounding: 4f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Night Blue — Royal-Blue-Tinte in Party/FC, Bronze-Gold in Yell/
// Alliance. Channel-identity (Tell-Pink, NN-Lime) bleibt erhalten.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#6AA8E8"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4FA8E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#6AA8E8"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFD060"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E8A8"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#6AA8E8"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A8A0F0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#8CA0BF"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Night Blue",
Author: "Julia Moon",
Description: "Royal Blue auf Marineblau — kühles Tech-Dashboard-Mood.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#3576C0"),
Primary: ColourUtil.HexToRgba("#4A90E2"),
PrimaryLight: ColourUtil.HexToRgba("#6AB0FF"),
PrimaryGlow: ColourUtil.HexToRgba("#4A90E299"),
AccentDark: ColourUtil.HexToRgba("#C97A2E"),
Accent: ColourUtil.HexToRgba("#E8A040"),
AccentLight: ColourUtil.HexToRgba("#F4B968"),
Identity: ColourUtil.HexToRgba("#3576C0"),
WindowBg: ColourUtil.HexToRgba("#050B18"),
ChildBg: ColourUtil.HexToRgba("#0A1628"),
FrameBg: ColourUtil.HexToRgba("#122039"),
Surface: ColourUtil.HexToRgba("#1A2D4F"),
SurfaceHover: ColourUtil.HexToRgba("#234070"),
Border: ColourUtil.HexToRgba("#4A90E266"),
TextPrimary: ColourUtil.HexToRgba("#E6EDF7"),
TextMuted: ColourUtil.HexToRgba("#8CA0BF"),
TextDim: ColourUtil.HexToRgba("#5A6F8F"),
StatusSuccess: ColourUtil.HexToRgba("#3DDC97"),
StatusDanger: ColourUtil.HexToRgba("#FF5C7A"),
StatusWarning: ColourUtil.HexToRgba("#FFB84A"),
StatusInfo: ColourUtil.HexToRgba("#4A90E2")
),
Layout: new ThemeLayout(
WindowRounding: 6f,
ChildRounding: 5f,
PopupRounding: 5f,
FrameRounding: 4f,
GrabRounding: 4f,
TabRounding: 4f,
ScrollbarRounding: 4f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Night Blue — Royal-Blue-Tinte in Party/FC, Bronze-Gold in Yell/
// Alliance. Channel-identity (Tell-Pink, NN-Lime) bleibt erhalten.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#6AA8E8"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#4FA8E8"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#6AA8E8"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#A8E060"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFD060"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#80E8A8"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#6AA8E8"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#A8A0F0"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#FF99CC"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#E8B0F0"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#E8B070"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#8CA0BF"),
}
)
);
}
+71 -67
View File
@@ -6,71 +6,75 @@ internal static class SynthwaveSunset
{
public const string Slug = "synthwave-sunset";
public static Theme Build() => new(
Slug: Slug,
Name: "Synthwave Sunset",
Author: "Hellion Forge",
Description: "Hot Magenta + Cyan on midnight violet. 80s neon-grid vibes for late-night raids.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#C71585"),
Primary: ColourUtil.HexToRgba("#FF2D95"),
PrimaryLight: ColourUtil.HexToRgba("#FF6BB6"),
PrimaryGlow: ColourUtil.HexToRgba("#FF2D9599"),
AccentDark: ColourUtil.HexToRgba("#0098B8"),
Accent: ColourUtil.HexToRgba("#00F0FF"),
AccentLight: ColourUtil.HexToRgba("#5CFFFE"),
Identity: ColourUtil.HexToRgba("#FF2D95"),
WindowBg: ColourUtil.HexToRgba("#13041F"),
ChildBg: ColourUtil.HexToRgba("#1E0A35"),
FrameBg: ColourUtil.HexToRgba("#2A1247"),
Surface: ColourUtil.HexToRgba("#3A1860"),
SurfaceHover: ColourUtil.HexToRgba("#4A2475"),
Border: ColourUtil.HexToRgba("#FF2D9566"),
TextPrimary: ColourUtil.HexToRgba("#F0DFFF"),
TextMuted: ColourUtil.HexToRgba("#A88BC4"),
TextDim: ColourUtil.HexToRgba("#6F4D8E"),
StatusSuccess: ColourUtil.HexToRgba("#39FF14"),
StatusDanger: ColourUtil.HexToRgba("#FF3838"),
StatusWarning: ColourUtil.HexToRgba("#FFD700"),
StatusInfo: ColourUtil.HexToRgba("#00F0FF")
),
Layout: new ThemeLayout(
WindowRounding: 5f, ChildRounding: 4f, PopupRounding: 4f,
FrameRounding: 3f, GrabRounding: 3f, TabRounding: 3f,
ScrollbarRounding: 3f, WindowBorderSize: 1f, FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(new Dictionary<HellionChat.Code.ChatType, uint>
{
// Synthwave Sunset — Magenta dominiert die warmen Channels (Yell/Shout/FC),
// Cyan dominiert die kühlen (Tell/Party). Neon-Akzente für Status-nahe Channels.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#00F0FF"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FF8C00"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#39FF14"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#39FF14"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#FF8C00"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFD700"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#00F0FF"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#FF6BB6"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#A88BC4"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#FF6BB6"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#A88BC4"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#A88BC4"),
})
);
public static Theme Build() =>
new(
Slug: Slug,
Name: "Synthwave Sunset",
Author: "Hellion Forge",
Description: "Hot Magenta + Cyan on midnight violet. 80s neon-grid vibes for late-night raids.",
Colors: new ThemeColors(
PrimaryDark: ColourUtil.HexToRgba("#C71585"),
Primary: ColourUtil.HexToRgba("#FF2D95"),
PrimaryLight: ColourUtil.HexToRgba("#FF6BB6"),
PrimaryGlow: ColourUtil.HexToRgba("#FF2D9599"),
AccentDark: ColourUtil.HexToRgba("#0098B8"),
Accent: ColourUtil.HexToRgba("#00F0FF"),
AccentLight: ColourUtil.HexToRgba("#5CFFFE"),
Identity: ColourUtil.HexToRgba("#FF2D95"),
WindowBg: ColourUtil.HexToRgba("#13041F"),
ChildBg: ColourUtil.HexToRgba("#1E0A35"),
FrameBg: ColourUtil.HexToRgba("#2A1247"),
Surface: ColourUtil.HexToRgba("#3A1860"),
SurfaceHover: ColourUtil.HexToRgba("#4A2475"),
Border: ColourUtil.HexToRgba("#FF2D9566"),
TextPrimary: ColourUtil.HexToRgba("#F0DFFF"),
TextMuted: ColourUtil.HexToRgba("#A88BC4"),
TextDim: ColourUtil.HexToRgba("#6F4D8E"),
StatusSuccess: ColourUtil.HexToRgba("#39FF14"),
StatusDanger: ColourUtil.HexToRgba("#FF3838"),
StatusWarning: ColourUtil.HexToRgba("#FFD700"),
StatusInfo: ColourUtil.HexToRgba("#00F0FF")
),
Layout: new ThemeLayout(
WindowRounding: 5f,
ChildRounding: 4f,
PopupRounding: 4f,
FrameRounding: 3f,
GrabRounding: 3f,
TabRounding: 3f,
ScrollbarRounding: 3f,
WindowBorderSize: 1f,
FrameBorderSize: 1f
),
Typography: new ThemeTypography(),
IsBuiltIn: true,
ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint>
{
// Synthwave Sunset — Magenta dominiert die warmen Channels (Yell/Shout/FC),
// Cyan dominiert die kühlen (Tell/Party). Neon-Akzente für Status-nahe Channels.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"),
[HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#00F0FF"),
[HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FF8C00"),
[HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#39FF14"),
[HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#39FF14"),
[HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#FF8C00"),
[HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFD700"),
[HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#00F0FF"),
[HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#FF6BB6"),
[HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#A88BC4"),
[HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#5CFFFE"),
[HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#FF6BB6"),
[HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#A88BC4"),
[HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#A88BC4"),
}
)
);
}
+39 -39
View File
@@ -1,41 +1,41 @@
{
"schemaVersion": 1,
"slug": "example-custom",
"name": "Example Custom",
"author": "You",
"description": "Starting template — duplicate, rename, edit colors and reload.",
"colors": {
"primaryDark": "#0097A7",
"primary": "#00BED2",
"primaryLight": "#4DD9E8",
"primaryGlow": "#00BED299",
"accentDark": "#E85D04",
"accent": "#F97316",
"accentLight": "#FB923C",
"identity": "#0097A7",
"windowBg": "#070B12",
"childBg": "#0C1220",
"frameBg": "#141E30",
"surface": "#1A2538",
"surfaceHover": "#22303F",
"border": "#00BED266",
"textPrimary": "#E6F4F1",
"textMuted": "#8FA3B5",
"textDim": "#566273",
"statusSuccess": "#5CB85C",
"statusDanger": "#D9534F",
"statusWarning": "#F0AD4E",
"statusInfo": "#00BED2"
},
"layout": {
"windowRounding": 4,
"childRounding": 3,
"popupRounding": 3,
"frameRounding": 2,
"grabRounding": 2,
"tabRounding": 2,
"scrollbarRounding": 2,
"windowBorderSize": 1,
"frameBorderSize": 1
}
"schemaVersion": 1,
"slug": "example-custom",
"name": "Example Custom",
"author": "You",
"description": "Starting template — duplicate, rename, edit colors and reload.",
"colors": {
"primaryDark": "#0097A7",
"primary": "#00BED2",
"primaryLight": "#4DD9E8",
"primaryGlow": "#00BED299",
"accentDark": "#E85D04",
"accent": "#F97316",
"accentLight": "#FB923C",
"identity": "#0097A7",
"windowBg": "#070B12",
"childBg": "#0C1220",
"frameBg": "#141E30",
"surface": "#1A2538",
"surfaceHover": "#22303F",
"border": "#00BED266",
"textPrimary": "#E6F4F1",
"textMuted": "#8FA3B5",
"textDim": "#566273",
"statusSuccess": "#5CB85C",
"statusDanger": "#D9534F",
"statusWarning": "#F0AD4E",
"statusInfo": "#00BED2"
},
"layout": {
"windowRounding": 4,
"childRounding": 3,
"popupRounding": 3,
"frameRounding": 2,
"grabRounding": 2,
"tabRounding": 2,
"scrollbarRounding": 2,
"windowBorderSize": 1,
"frameBorderSize": 1
}
}
+40 -25
View File
@@ -21,38 +21,53 @@ public sealed record Theme(
public void RecomputeAbgrCache()
{
AbgrCache = new ThemeAbgrCache(
PrimaryDark: ColourUtil.RgbaToAbgr(Colors.PrimaryDark),
Primary: ColourUtil.RgbaToAbgr(Colors.Primary),
PrimaryLight: ColourUtil.RgbaToAbgr(Colors.PrimaryLight),
PrimaryGlow: ColourUtil.RgbaToAbgr(Colors.PrimaryGlow),
AccentDark: ColourUtil.RgbaToAbgr(Colors.AccentDark),
Accent: ColourUtil.RgbaToAbgr(Colors.Accent),
AccentLight: ColourUtil.RgbaToAbgr(Colors.AccentLight),
Identity: ColourUtil.RgbaToAbgr(Colors.Identity),
WindowBg: ColourUtil.RgbaToAbgr(Colors.WindowBg),
ChildBg: ColourUtil.RgbaToAbgr(Colors.ChildBg),
FrameBg: ColourUtil.RgbaToAbgr(Colors.FrameBg),
Surface: ColourUtil.RgbaToAbgr(Colors.Surface),
SurfaceHover: ColourUtil.RgbaToAbgr(Colors.SurfaceHover),
Border: ColourUtil.RgbaToAbgr(Colors.Border),
TextPrimary: ColourUtil.RgbaToAbgr(Colors.TextPrimary),
TextMuted: ColourUtil.RgbaToAbgr(Colors.TextMuted),
TextDim: ColourUtil.RgbaToAbgr(Colors.TextDim),
PrimaryDark: ColourUtil.RgbaToAbgr(Colors.PrimaryDark),
Primary: ColourUtil.RgbaToAbgr(Colors.Primary),
PrimaryLight: ColourUtil.RgbaToAbgr(Colors.PrimaryLight),
PrimaryGlow: ColourUtil.RgbaToAbgr(Colors.PrimaryGlow),
AccentDark: ColourUtil.RgbaToAbgr(Colors.AccentDark),
Accent: ColourUtil.RgbaToAbgr(Colors.Accent),
AccentLight: ColourUtil.RgbaToAbgr(Colors.AccentLight),
Identity: ColourUtil.RgbaToAbgr(Colors.Identity),
WindowBg: ColourUtil.RgbaToAbgr(Colors.WindowBg),
ChildBg: ColourUtil.RgbaToAbgr(Colors.ChildBg),
FrameBg: ColourUtil.RgbaToAbgr(Colors.FrameBg),
Surface: ColourUtil.RgbaToAbgr(Colors.Surface),
SurfaceHover: ColourUtil.RgbaToAbgr(Colors.SurfaceHover),
Border: ColourUtil.RgbaToAbgr(Colors.Border),
TextPrimary: ColourUtil.RgbaToAbgr(Colors.TextPrimary),
TextMuted: ColourUtil.RgbaToAbgr(Colors.TextMuted),
TextDim: ColourUtil.RgbaToAbgr(Colors.TextDim),
StatusSuccess: ColourUtil.RgbaToAbgr(Colors.StatusSuccess),
StatusDanger: ColourUtil.RgbaToAbgr(Colors.StatusDanger),
StatusDanger: ColourUtil.RgbaToAbgr(Colors.StatusDanger),
StatusWarning: ColourUtil.RgbaToAbgr(Colors.StatusWarning),
StatusInfo: ColourUtil.RgbaToAbgr(Colors.StatusInfo));
StatusInfo: ColourUtil.RgbaToAbgr(Colors.StatusInfo)
);
}
}
// Mirrors ThemeColors slot-for-slot. The FillsAll21Slots test pins the
// contract — a new slot without its mirror fails the build.
public readonly record struct ThemeAbgrCache(
uint PrimaryDark, uint Primary, uint PrimaryLight, uint PrimaryGlow,
uint AccentDark, uint Accent, uint AccentLight,
uint PrimaryDark,
uint Primary,
uint PrimaryLight,
uint PrimaryGlow,
uint AccentDark,
uint Accent,
uint AccentLight,
uint Identity,
uint WindowBg, uint ChildBg, uint FrameBg,
uint Surface, uint SurfaceHover, uint Border,
uint TextPrimary, uint TextMuted, uint TextDim,
uint StatusSuccess, uint StatusDanger, uint StatusWarning, uint StatusInfo
uint WindowBg,
uint ChildBg,
uint FrameBg,
uint Surface,
uint SurfaceHover,
uint Border,
uint TextPrimary,
uint TextMuted,
uint TextDim,
uint StatusSuccess,
uint StatusDanger,
uint StatusWarning,
uint StatusInfo
);
+1 -3
View File
@@ -6,6 +6,4 @@ namespace HellionChat.Themes;
// User sie per Klick im Themes-Tab auf Configuration.ChatColours anwenden.
// Ein Theme ohne ChatColors (z.B. chat2-classic) lässt die User-Channel-
// Farben unverändert.
public sealed record ThemeChatColors(
IReadOnlyDictionary<ChatType, uint> Channels
);
public sealed record ThemeChatColors(IReadOnlyDictionary<ChatType, uint> Channels);
-5
View File
@@ -6,24 +6,19 @@ public sealed record ThemeColors(
uint Primary,
uint PrimaryLight,
uint PrimaryGlow,
uint AccentDark,
uint Accent,
uint AccentLight,
uint Identity,
uint WindowBg,
uint ChildBg,
uint FrameBg,
uint Surface,
uint SurfaceHover,
uint Border,
uint TextPrimary,
uint TextMuted,
uint TextDim,
uint StatusSuccess,
uint StatusDanger,
uint StatusWarning,
+72 -48
View File
@@ -13,8 +13,14 @@ internal static class ThemeJsonLoader
throw new FormatException("Theme JSON is empty");
JsonDocument doc;
try { doc = JsonDocument.Parse(json); }
catch (JsonException ex) { throw new FormatException("Theme JSON is not valid JSON", ex); }
try
{
doc = JsonDocument.Parse(json);
}
catch (JsonException ex)
{
throw new FormatException("Theme JSON is not valid JSON", ex);
}
using (doc)
{
@@ -22,21 +28,36 @@ internal static class ThemeJsonLoader
var schemaVersion = ReadInt(root, "schemaVersion");
if (schemaVersion != SupportedSchemaVersion)
throw new FormatException($"Unsupported schemaVersion {schemaVersion}; expected {SupportedSchemaVersion}");
throw new FormatException(
$"Unsupported schemaVersion {schemaVersion}; expected {SupportedSchemaVersion}"
);
var slug = ReadString(root, "slug");
var name = ReadString(root, "name");
var author = ReadString(root, "author");
var slug = ReadString(root, "slug");
var name = ReadString(root, "name");
var author = ReadString(root, "author");
var description = ReadString(root, "description");
var colors = ReadColors(root.GetProperty("colors"));
var layout = ReadLayout(root.GetProperty("layout"));
ThemeChatColors? chatColors = null;
if (root.TryGetProperty("chatChannels", out var ch) && ch.ValueKind == JsonValueKind.Object)
if (
root.TryGetProperty("chatChannels", out var ch)
&& ch.ValueKind == JsonValueKind.Object
)
chatColors = ReadChatColors(ch);
return new Theme(slug, name, author, description, colors, layout, new ThemeTypography(), IsBuiltIn: false, ChatColors: chatColors);
return new Theme(
slug,
name,
author,
description,
colors,
layout,
new ThemeTypography(),
IsBuiltIn: false,
ChatColors: chatColors
);
}
}
@@ -49,7 +70,13 @@ internal static class ThemeJsonLoader
// Value ist Hex wie bei den Theme-Colors. Unbekannte Channel-Names
// werden still übersprungen — Forward-Compat falls SE neue Channels
// einführt.
if (!Enum.TryParse<HellionChat.Code.ChatType>(prop.Name, ignoreCase: true, out var channel))
if (
!Enum.TryParse<HellionChat.Code.ChatType>(
prop.Name,
ignoreCase: true,
out var channel
)
)
continue;
if (prop.Value.ValueKind != JsonValueKind.String)
continue;
@@ -71,46 +98,43 @@ internal static class ThemeJsonLoader
return LoadFromString(json);
}
private static ThemeColors ReadColors(JsonElement el) => new(
PrimaryDark: ColourUtil.HexToRgba(ReadString(el, "primaryDark")),
Primary: ColourUtil.HexToRgba(ReadString(el, "primary")),
PrimaryLight: ColourUtil.HexToRgba(ReadString(el, "primaryLight")),
PrimaryGlow: ColourUtil.HexToRgba(ReadString(el, "primaryGlow")),
private static ThemeColors ReadColors(JsonElement el) =>
new(
PrimaryDark: ColourUtil.HexToRgba(ReadString(el, "primaryDark")),
Primary: ColourUtil.HexToRgba(ReadString(el, "primary")),
PrimaryLight: ColourUtil.HexToRgba(ReadString(el, "primaryLight")),
PrimaryGlow: ColourUtil.HexToRgba(ReadString(el, "primaryGlow")),
AccentDark: ColourUtil.HexToRgba(ReadString(el, "accentDark")),
Accent: ColourUtil.HexToRgba(ReadString(el, "accent")),
AccentLight: ColourUtil.HexToRgba(ReadString(el, "accentLight")),
Identity: ColourUtil.HexToRgba(ReadString(el, "identity")),
WindowBg: ColourUtil.HexToRgba(ReadString(el, "windowBg")),
ChildBg: ColourUtil.HexToRgba(ReadString(el, "childBg")),
FrameBg: ColourUtil.HexToRgba(ReadString(el, "frameBg")),
Surface: ColourUtil.HexToRgba(ReadString(el, "surface")),
SurfaceHover: ColourUtil.HexToRgba(ReadString(el, "surfaceHover")),
Border: ColourUtil.HexToRgba(ReadString(el, "border")),
TextPrimary: ColourUtil.HexToRgba(ReadString(el, "textPrimary")),
TextMuted: ColourUtil.HexToRgba(ReadString(el, "textMuted")),
TextDim: ColourUtil.HexToRgba(ReadString(el, "textDim")),
StatusSuccess: ColourUtil.HexToRgba(ReadString(el, "statusSuccess")),
StatusDanger: ColourUtil.HexToRgba(ReadString(el, "statusDanger")),
StatusWarning: ColourUtil.HexToRgba(ReadString(el, "statusWarning")),
StatusInfo: ColourUtil.HexToRgba(ReadString(el, "statusInfo"))
);
AccentDark: ColourUtil.HexToRgba(ReadString(el, "accentDark")),
Accent: ColourUtil.HexToRgba(ReadString(el, "accent")),
AccentLight: ColourUtil.HexToRgba(ReadString(el, "accentLight")),
Identity: ColourUtil.HexToRgba(ReadString(el, "identity")),
WindowBg: ColourUtil.HexToRgba(ReadString(el, "windowBg")),
ChildBg: ColourUtil.HexToRgba(ReadString(el, "childBg")),
FrameBg: ColourUtil.HexToRgba(ReadString(el, "frameBg")),
Surface: ColourUtil.HexToRgba(ReadString(el, "surface")),
SurfaceHover: ColourUtil.HexToRgba(ReadString(el, "surfaceHover")),
Border: ColourUtil.HexToRgba(ReadString(el, "border")),
TextPrimary: ColourUtil.HexToRgba(ReadString(el, "textPrimary")),
TextMuted: ColourUtil.HexToRgba(ReadString(el, "textMuted")),
TextDim: ColourUtil.HexToRgba(ReadString(el, "textDim")),
StatusSuccess: ColourUtil.HexToRgba(ReadString(el, "statusSuccess")),
StatusDanger: ColourUtil.HexToRgba(ReadString(el, "statusDanger")),
StatusWarning: ColourUtil.HexToRgba(ReadString(el, "statusWarning")),
StatusInfo: ColourUtil.HexToRgba(ReadString(el, "statusInfo"))
);
private static ThemeLayout ReadLayout(JsonElement el) => new(
WindowRounding: ReadFloat(el, "windowRounding"),
ChildRounding: ReadFloat(el, "childRounding"),
PopupRounding: ReadFloat(el, "popupRounding"),
FrameRounding: ReadFloat(el, "frameRounding"),
GrabRounding: ReadFloat(el, "grabRounding"),
TabRounding: ReadFloat(el, "tabRounding"),
ScrollbarRounding: ReadFloat(el, "scrollbarRounding"),
WindowBorderSize: ReadFloat(el, "windowBorderSize"),
FrameBorderSize: ReadFloat(el, "frameBorderSize")
);
private static ThemeLayout ReadLayout(JsonElement el) =>
new(
WindowRounding: ReadFloat(el, "windowRounding"),
ChildRounding: ReadFloat(el, "childRounding"),
PopupRounding: ReadFloat(el, "popupRounding"),
FrameRounding: ReadFloat(el, "frameRounding"),
GrabRounding: ReadFloat(el, "grabRounding"),
TabRounding: ReadFloat(el, "tabRounding"),
ScrollbarRounding: ReadFloat(el, "scrollbarRounding"),
WindowBorderSize: ReadFloat(el, "windowBorderSize"),
FrameBorderSize: ReadFloat(el, "frameBorderSize")
);
private static string ReadString(JsonElement el, string name)
{
+27 -27
View File
@@ -17,39 +17,39 @@ internal static class ThemeJsonWriter
writer.WriteString("description", theme.Description);
writer.WriteStartObject("colors");
WriteColor(writer, "primaryDark", theme.Colors.PrimaryDark);
WriteColor(writer, "primary", theme.Colors.Primary);
WriteColor(writer, "primaryLight", theme.Colors.PrimaryLight);
WriteColor(writer, "primaryGlow", theme.Colors.PrimaryGlow);
WriteColor(writer, "accentDark", theme.Colors.AccentDark);
WriteColor(writer, "accent", theme.Colors.Accent);
WriteColor(writer, "accentLight", theme.Colors.AccentLight);
WriteColor(writer, "identity", theme.Colors.Identity);
WriteColor(writer, "windowBg", theme.Colors.WindowBg);
WriteColor(writer, "childBg", theme.Colors.ChildBg);
WriteColor(writer, "frameBg", theme.Colors.FrameBg);
WriteColor(writer, "surface", theme.Colors.Surface);
WriteColor(writer, "surfaceHover", theme.Colors.SurfaceHover);
WriteColor(writer, "border", theme.Colors.Border);
WriteColor(writer, "textPrimary", theme.Colors.TextPrimary);
WriteColor(writer, "textMuted", theme.Colors.TextMuted);
WriteColor(writer, "textDim", theme.Colors.TextDim);
WriteColor(writer, "primaryDark", theme.Colors.PrimaryDark);
WriteColor(writer, "primary", theme.Colors.Primary);
WriteColor(writer, "primaryLight", theme.Colors.PrimaryLight);
WriteColor(writer, "primaryGlow", theme.Colors.PrimaryGlow);
WriteColor(writer, "accentDark", theme.Colors.AccentDark);
WriteColor(writer, "accent", theme.Colors.Accent);
WriteColor(writer, "accentLight", theme.Colors.AccentLight);
WriteColor(writer, "identity", theme.Colors.Identity);
WriteColor(writer, "windowBg", theme.Colors.WindowBg);
WriteColor(writer, "childBg", theme.Colors.ChildBg);
WriteColor(writer, "frameBg", theme.Colors.FrameBg);
WriteColor(writer, "surface", theme.Colors.Surface);
WriteColor(writer, "surfaceHover", theme.Colors.SurfaceHover);
WriteColor(writer, "border", theme.Colors.Border);
WriteColor(writer, "textPrimary", theme.Colors.TextPrimary);
WriteColor(writer, "textMuted", theme.Colors.TextMuted);
WriteColor(writer, "textDim", theme.Colors.TextDim);
WriteColor(writer, "statusSuccess", theme.Colors.StatusSuccess);
WriteColor(writer, "statusDanger", theme.Colors.StatusDanger);
WriteColor(writer, "statusDanger", theme.Colors.StatusDanger);
WriteColor(writer, "statusWarning", theme.Colors.StatusWarning);
WriteColor(writer, "statusInfo", theme.Colors.StatusInfo);
WriteColor(writer, "statusInfo", theme.Colors.StatusInfo);
writer.WriteEndObject();
writer.WriteStartObject("layout");
writer.WriteNumber("windowRounding", theme.Layout.WindowRounding);
writer.WriteNumber("childRounding", theme.Layout.ChildRounding);
writer.WriteNumber("popupRounding", theme.Layout.PopupRounding);
writer.WriteNumber("frameRounding", theme.Layout.FrameRounding);
writer.WriteNumber("grabRounding", theme.Layout.GrabRounding);
writer.WriteNumber("tabRounding", theme.Layout.TabRounding);
writer.WriteNumber("windowRounding", theme.Layout.WindowRounding);
writer.WriteNumber("childRounding", theme.Layout.ChildRounding);
writer.WriteNumber("popupRounding", theme.Layout.PopupRounding);
writer.WriteNumber("frameRounding", theme.Layout.FrameRounding);
writer.WriteNumber("grabRounding", theme.Layout.GrabRounding);
writer.WriteNumber("tabRounding", theme.Layout.TabRounding);
writer.WriteNumber("scrollbarRounding", theme.Layout.ScrollbarRounding);
writer.WriteNumber("windowBorderSize", theme.Layout.WindowBorderSize);
writer.WriteNumber("frameBorderSize", theme.Layout.FrameBorderSize);
writer.WriteNumber("windowBorderSize", theme.Layout.WindowBorderSize);
writer.WriteNumber("frameBorderSize", theme.Layout.FrameBorderSize);
writer.WriteEndObject();
if (theme.ChatColors is { Channels.Count: > 0 } cc)
+24 -16
View File
@@ -7,7 +7,9 @@ public sealed class ThemeRegistry
public const string DefaultSlug = HellionArctic.Slug;
private readonly Dictionary<string, Theme> _builtIns;
private readonly Dictionary<string, (Theme Theme, DateTime Stamp)> _customCache = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, (Theme Theme, DateTime Stamp)> _customCache = new(
StringComparer.OrdinalIgnoreCase
);
private readonly string? _customThemesDir;
private Theme _active;
@@ -15,16 +17,16 @@ public sealed class ThemeRegistry
{
_builtIns = new Dictionary<string, Theme>(StringComparer.OrdinalIgnoreCase)
{
{ HellionArctic.Slug, HellionArctic.Build() },
{ HellionSpectrum.Slug, HellionSpectrum.Build() },
{ Chat2Classic.Slug, Chat2Classic.Build() },
{ EventHorizon.Slug, EventHorizon.Build() },
{ MoonlitBloom.Slug, MoonlitBloom.Build() },
{ NightBlue.Slug, NightBlue.Build() },
{ IndigoViolet.Slug, IndigoViolet.Build() },
{ ForgeMerchantman.Slug, ForgeMerchantman.Build() },
{ MintGrove.Slug, MintGrove.Build() },
{ SynthwaveSunset.Slug, SynthwaveSunset.Build() },
{ HellionArctic.Slug, HellionArctic.Build() },
{ HellionSpectrum.Slug, HellionSpectrum.Build() },
{ Chat2Classic.Slug, Chat2Classic.Build() },
{ EventHorizon.Slug, EventHorizon.Build() },
{ MoonlitBloom.Slug, MoonlitBloom.Build() },
{ NightBlue.Slug, NightBlue.Build() },
{ IndigoViolet.Slug, IndigoViolet.Build() },
{ ForgeMerchantman.Slug, ForgeMerchantman.Build() },
{ MintGrove.Slug, MintGrove.Build() },
{ SynthwaveSunset.Slug, SynthwaveSunset.Build() },
};
// Centralised so the ten .Build() factories stay free of cache plumbing.
@@ -39,10 +41,12 @@ public sealed class ThemeRegistry
public Theme Get(string slug)
{
if (_builtIns.TryGetValue(slug, out var b)) return b;
if (_builtIns.TryGetValue(slug, out var b))
return b;
var custom = LoadCustomBySlug(slug);
if (custom != null) return custom;
if (custom != null)
return custom;
return _builtIns[DefaultSlug];
}
@@ -75,8 +79,10 @@ public sealed class ThemeRegistry
// neu eingelesen.
private Theme? LoadCustomBySlug(string slug)
{
if (_customThemesDir is null) return null;
if (!Directory.Exists(_customThemesDir)) return null;
if (_customThemesDir is null)
return null;
if (!Directory.Exists(_customThemesDir))
return null;
foreach (var theme in RefreshCustomCache())
if (string.Equals(theme.Slug, slug, StringComparison.OrdinalIgnoreCase))
@@ -111,7 +117,9 @@ public sealed class ThemeRegistry
{
// Editor mid-save: keep the cached snapshot, leave the stamp
// alone so the next refresh retries automatically.
Plugin.Log.Debug($"Custom theme {Path.GetFileName(path)} is locked, keeping last known good");
Plugin.Log.Debug(
$"Custom theme {Path.GetFileName(path)} is locked, keeping last known good"
);
if (cached.Theme is not null)
theme = cached.Theme;
}
+49 -38
View File
@@ -1,10 +1,10 @@
using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat._Helpers;
using HellionChat.Code;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
namespace HellionChat.Ui;
@@ -36,9 +36,7 @@ public sealed class ChatInputBar
public bool IsFocused { get; private set; }
// Stub. v0.6.0 belässt den Hauptfenster-Input wie er ist.
public void Render()
{
}
public void Render() { }
// Compact rendering for pop-out windows.
//
@@ -79,12 +77,20 @@ public sealed class ChatInputBar
// window does it: via IsItemDeactivated + Enter, NOT EnterReturnsTrue
// (matching v0.5.x ChatLogWindow.cs behavior).
const ImGuiInputTextFlags flags = ImGuiInputTextFlags.CallbackHistory;
ImGui.InputText($"##chat-compact-input-{tab.Identifier}", ref _state.Buffer, 500, flags, CompactCallback);
ImGui.InputText(
$"##chat-compact-input-{tab.Identifier}",
ref _state.Buffer,
500,
flags,
CompactCallback
);
IsFocused = ImGui.IsItemActive();
if (ImGui.IsItemDeactivated()
&& (ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter)))
if (
ImGui.IsItemDeactivated()
&& (ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter))
)
{
SubmitCompact(tab);
}
@@ -118,7 +124,8 @@ public sealed class ChatInputBar
_state.Buffer,
() => InputHistoryService.Count,
InputHistoryService.Push,
InputHistoryService.GetByCursor);
InputHistoryService.GetByCursor
);
_state.HistoryCursor = cursor;
if (replacement is null)
@@ -156,7 +163,10 @@ public sealed class ChatInputBar
// Single-letter glyph derived from the channel — quick visual cue
// until we have a proper icon font available in the compact bar.
var label = ChannelGlyph(inputType);
if (ImGui.Button($"{label}##chan-compact", new Vector2(buttonSize, buttonSize)) && tab.Channel is null)
if (
ImGui.Button($"{label}##chan-compact", new Vector2(buttonSize, buttonSize))
&& tab.Channel is null
)
ImGui.OpenPopup(popupId);
}
@@ -181,34 +191,35 @@ public sealed class ChatInputBar
}
}
private static string ChannelGlyph(ChatType type) => type switch
{
ChatType.Say => "S",
ChatType.Yell => "Y",
ChatType.Shout => "!",
ChatType.TellIncoming or ChatType.TellOutgoing => "T",
ChatType.Party or ChatType.CrossParty => "P",
ChatType.Alliance => "A",
ChatType.FreeCompany => "F",
ChatType.NoviceNetwork => "N",
ChatType.Linkshell1 => "1",
ChatType.Linkshell2 => "2",
ChatType.Linkshell3 => "3",
ChatType.Linkshell4 => "4",
ChatType.Linkshell5 => "5",
ChatType.Linkshell6 => "6",
ChatType.Linkshell7 => "7",
ChatType.Linkshell8 => "8",
ChatType.CrossLinkshell1 => "",
ChatType.CrossLinkshell2 => "",
ChatType.CrossLinkshell3 => "",
ChatType.CrossLinkshell4 => "",
ChatType.CrossLinkshell5 => "",
ChatType.CrossLinkshell6 => "",
ChatType.CrossLinkshell7 => "",
ChatType.CrossLinkshell8 => "",
_ => "?",
};
private static string ChannelGlyph(ChatType type) =>
type switch
{
ChatType.Say => "S",
ChatType.Yell => "Y",
ChatType.Shout => "!",
ChatType.TellIncoming or ChatType.TellOutgoing => "T",
ChatType.Party or ChatType.CrossParty => "P",
ChatType.Alliance => "A",
ChatType.FreeCompany => "F",
ChatType.NoviceNetwork => "N",
ChatType.Linkshell1 => "1",
ChatType.Linkshell2 => "2",
ChatType.Linkshell3 => "3",
ChatType.Linkshell4 => "4",
ChatType.Linkshell5 => "5",
ChatType.Linkshell6 => "6",
ChatType.Linkshell7 => "7",
ChatType.Linkshell8 => "8",
ChatType.CrossLinkshell1 => "",
ChatType.CrossLinkshell2 => "",
ChatType.CrossLinkshell3 => "",
ChatType.CrossLinkshell4 => "",
ChatType.CrossLinkshell5 => "",
ChatType.CrossLinkshell6 => "",
ChatType.CrossLinkshell7 => "",
ChatType.CrossLinkshell8 => "",
_ => "?",
};
// Forwards a tab-cycle keybind delta to the host so all windows
// navigate the same active-tab pointer (single source of truth).
File diff suppressed because it is too large Load Diff
+21 -9
View File
@@ -1,23 +1,30 @@
using System.Numerics;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using Dalamud.Bindings.ImGui;
using HellionChat.Util;
using Lumina.Text.ReadOnly;
namespace HellionChat.Ui;
public class CommandHelpWindow : Window {
public class CommandHelpWindow : Window
{
private ChatLogWindow LogWindow { get; }
private ReadOnlySeString? CommandDescription { get; set; }
internal CommandHelpWindow(ChatLogWindow logWindow) : base("command help##chat2-commandhelp")
internal CommandHelpWindow(ChatLogWindow logWindow)
: base("command help##chat2-commandhelp")
{
LogWindow = logWindow;
Flags = ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize;
Flags =
ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.AlwaysAutoResize;
RespectCloseHotkey = false;
DisableWindowSounds = true;
@@ -31,7 +38,8 @@ public class CommandHelpWindow : Window {
var width = 350;
var scaledWidth = width * ImGuiHelpers.GlobalScale;
var pos = LogWindow.LastWindowPos;
switch (Plugin.Config.CommandHelpSide) {
switch (Plugin.Config.CommandHelpSide)
{
case CommandHelpSide.Right:
pos.X += LogWindow.LastWindowSize.X;
break;
@@ -51,7 +59,7 @@ public class CommandHelpWindow : Window {
// coordinate space as Position above; otherwise the help window
// ends up the wrong width at non-100% DPI.
MinimumSize = new Vector2(scaledWidth, 0),
MaximumSize = LogWindow.LastWindowSize with { X = scaledWidth }
MaximumSize = LogWindow.LastWindowSize with { X = scaledWidth },
};
IsOpen = true;
@@ -62,6 +70,10 @@ public class CommandHelpWindow : Window {
if (CommandDescription == null)
return;
LogWindow.DrawChunks(ChunkUtil.ToChunks(CommandDescription.Value.ToDalamudString(), ChunkSource.None, null).ToList());
LogWindow.DrawChunks(
ChunkUtil
.ToChunks(CommandDescription.Value.ToDalamudString(), ChunkSource.None, null)
.ToList()
);
}
}
+192 -51
View File
@@ -2,18 +2,18 @@
using System.Globalization;
using System.Numerics;
using System.Text;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiNotification;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Lumina.Data.Files;
using Lumina.Text.ReadOnly;
using MoreLinq;
@@ -47,8 +47,8 @@ public class DbViewer : Window
private readonly string DateTimeFormat;
private long Count;
private Message[] Messages = []; // Messages are only touched while processing is false
private ConcurrentStack<Message> Filtered = []; // Is used every frame, so ConcurrentStack for safety
private Message[] Messages = []; // Messages are only touched while processing is false
private ConcurrentStack<Message> Filtered = []; // Is used every frame, so ConcurrentStack for safety
private bool IsExporting;
private string InputPath = string.Empty;
@@ -56,7 +56,8 @@ public class DbViewer : Window
private bool NeedsScrollReset;
public DbViewer(Plugin plugin) : base("DBViewer###chat2-dbviewer")
public DbViewer(Plugin plugin)
: base("DBViewer###chat2-dbviewer")
{
Plugin = plugin;
SelectedChannels = TabsUtil.MostlyPlayer;
@@ -64,24 +65,42 @@ public class DbViewer : Window
DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
DateTimeFormat = "ddd, dd MMM yyy HH:mm:ss";
LastProcessed = (AfterDate, BeforeDate, CurrentPage, OnlyCurrentCharacter, SelectedChannels.Count);
LastProcessed = (
AfterDate,
BeforeDate,
CurrentPage,
OnlyCurrentCharacter,
SelectedChannels.Count
);
DateReset();
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(475, 600),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
};
RespectCloseHotkey = false;
DisableWindowSounds = true;
Plugin.Commands.Register("/hellionView", "Get access to your message history, with simple filter options.", true).Execute += Toggle;
Plugin
.Commands.Register(
"/hellionView",
"Get access to your message history, with simple filter options.",
true
)
.Execute += Toggle;
}
public void Dispose()
{
Plugin.Commands.Register("/hellionView", "Get access to your message history, with simple filter options.", true).Execute -= Toggle;
Plugin
.Commands.Register(
"/hellionView",
"Get access to your message history, with simple filter options.",
true
)
.Execute -= Toggle;
}
private void Toggle(string _, string __) => Toggle();
@@ -102,8 +121,21 @@ public class DbViewer : Window
ImGui.SameLine();
var spacing = 3.0f * ImGuiHelpers.GlobalScale;
DateWidget.DatePickerWithInput("##FromDate", 1, ref MinDateString, ref AfterDate, DateFormat);
DateWidget.DatePickerWithInput("##ToDate", 2, ref MaxDateString, ref BeforeDate, DateFormat, true);
DateWidget.DatePickerWithInput(
"##FromDate",
1,
ref MinDateString,
ref AfterDate,
DateFormat
);
DateWidget.DatePickerWithInput(
"##ToDate",
2,
ref MaxDateString,
ref BeforeDate,
DateFormat,
true
);
ImGui.SameLine(0, spacing);
@@ -118,7 +150,11 @@ public class DbViewer : Window
ChannelSelection();
var skipText = Language.DbViewer_CharacterOption;
var textLength = ImGui.GetTextLineHeight() + ImGui.CalcTextSize(skipText).X + ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetStyle().FramePadding.X * 2;
var textLength =
ImGui.GetTextLineHeight()
+ ImGui.CalcTextSize(skipText).X
+ ImGui.GetStyle().ItemInnerSpacing.X
+ ImGui.GetStyle().FramePadding.X * 2;
ImGui.SameLine(ImGui.GetContentRegionMax().X - textLength);
ImGui.Checkbox(skipText, ref OnlyCurrentCharacter);
@@ -139,7 +175,16 @@ public class DbViewer : Window
using (var innerPopup = ImRaii.Popup("InputPathDialog"))
{
if (innerPopup.Success)
Plugin.FileDialogManager.OpenFolderDialog(Language.Folder_Selection_Header, (b, s) => { if (b) InputPath = s; }, null, true);
Plugin.FileDialogManager.OpenFolderDialog(
Language.Folder_Selection_Header,
(b, s) =>
{
if (b)
InputPath = s;
},
null,
true
);
}
ImGui.SameLine(0, spacing);
@@ -157,7 +202,8 @@ public class DbViewer : Window
UserDismissable = false,
InitialDuration = TimeSpan.FromSeconds(10000),
Progress = 0.0f,
});
}
);
CreateTxtBackup();
}
}
@@ -176,12 +222,34 @@ public class DbViewer : Window
var loadingIndicator = IsProcessing && ProcessingStart < Environment.TickCount64;
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(string.Format(Language.DbViewer_Page, CurrentPage, totalPages, Count, loadingIndicator ? Language.DbViewer_LoadingIndicator : ""));
ImGuiUtil.DrawArrows(ref CurrentPage, 1, totalPages, spacing, tooltipLeft: Language.Page_ArrowLeft_Tooltip, tooltipRight: Language.Page_ArrowRight_Tooltip);
ImGui.TextUnformatted(
string.Format(
Language.DbViewer_Page,
CurrentPage,
totalPages,
Count,
loadingIndicator ? Language.DbViewer_LoadingIndicator : ""
)
);
ImGuiUtil.DrawArrows(
ref CurrentPage,
1,
totalPages,
spacing,
tooltipLeft: Language.Page_ArrowLeft_Tooltip,
tooltipRight: Language.Page_ArrowRight_Tooltip
);
ImGui.SameLine(ImGui.GetContentRegionMax().X - width);
ImGui.SetNextItemWidth(width);
if (ImGui.InputTextWithHint("##searchbar", Language.DbViewer_SearcHint, ref SimpleSearchTerm, 30))
if (
ImGui.InputTextWithHint(
"##searchbar",
Language.DbViewer_SearcHint,
ref SimpleSearchTerm,
30
)
)
Filtered = Filter(Messages);
// Third row
@@ -189,7 +257,17 @@ public class DbViewer : Window
if (DateWidget.Validate(MinimalDate, ref AfterDate, ref BeforeDate))
DateRefresh();
if (!IsProcessing && LastProcessed != (AfterDate, BeforeDate, CurrentPage, OnlyCurrentCharacter, SelectedChannels.Count))
if (
!IsProcessing
&& LastProcessed
!= (
AfterDate,
BeforeDate,
CurrentPage,
OnlyCurrentCharacter,
SelectedChannels.Count
)
)
{
// Page hasn't changed, so we reset it back to 1
if (LastProcessed.Page == CurrentPage)
@@ -198,19 +276,37 @@ public class DbViewer : Window
AdjustDates();
IsProcessing = true;
ProcessingStart = Environment.TickCount64 + 1_000; // + 1 second
LastProcessed = (AfterDate, BeforeDate, CurrentPage, OnlyCurrentCharacter, SelectedChannels.Count);
LastProcessed = (
AfterDate,
BeforeDate,
CurrentPage,
OnlyCurrentCharacter,
SelectedChannels.Count
);
Task.Run(() =>
{
try
{
ulong? character = OnlyCurrentCharacter ? Plugin.PlayerState.ContentId : null;
var channels = SelectedChannels.Select(pair => (byte) pair.Key).ToArray();
var channels = SelectedChannels.Select(pair => (byte)pair.Key).ToArray();
// We only want to fetch count if this is the first page
if (CurrentPage == 1)
Count = Plugin.MessageManager.Store.CountDateRange(AfterDate, BeforeDate, channels, character);
Count = Plugin.MessageManager.Store.CountDateRange(
AfterDate,
BeforeDate,
channels,
character
);
using var rangeMessageEnumerator = Plugin.MessageManager.Store.GetPagedDateRange(AfterDate, BeforeDate, channels, character, CurrentPage - 1);
using var rangeMessageEnumerator =
Plugin.MessageManager.Store.GetPagedDateRange(
AfterDate,
BeforeDate,
channels,
character,
CurrentPage - 1
);
Messages = rangeMessageEnumerator.ToArray();
Filtered = Filter(Messages);
@@ -231,7 +327,11 @@ public class DbViewer : Window
if (Filtered.IsEmpty)
{
ImGui.TextUnformatted(SimpleSearchTerm == "" ? Language.DbViewer_Status_NothingFound : Language.DbViewer_Status_NoSearchResult);
ImGui.TextUnformatted(
SimpleSearchTerm == ""
? Language.DbViewer_Status_NothingFound
: Language.DbViewer_Status_NoSearchResult
);
return;
}
@@ -245,13 +345,24 @@ public class DbViewer : Window
ImGui.SetScrollY(0.0f);
}
using var table = ImRaii.Table("##messageHistory", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.Resizable);
using var table = ImRaii.Table(
"##messageHistory",
4,
ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.Resizable
);
if (!table.Success)
return;
var columnWidth = ImGui.CalcTextSize(Language.DbViewer_TableField_Type);
ImGui.TableSetupColumn(Language.DbViewer_TableField_Date, ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize);
ImGui.TableSetupColumn(Language.DbViewer_TableField_Type, ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize, columnWidth.X);
ImGui.TableSetupColumn(
Language.DbViewer_TableField_Date,
ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize
);
ImGui.TableSetupColumn(
Language.DbViewer_TableField_Type,
ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize,
columnWidth.X
);
ImGui.TableSetupColumn(Language.DbViewer_TableField_Sender);
ImGui.TableSetupColumn(Language.DbViewer_TableField_Content);
@@ -349,10 +460,18 @@ public class DbViewer : Window
return new ConcurrentStack<Message>(messages.Reverse().OrderByDescending(m => m.Date));
return new ConcurrentStack<Message>(
messages.Reverse().Where(m =>
ChunkUtil.ToRawString(m.Sender).Contains(SimpleSearchTerm, StringComparison.InvariantCultureIgnoreCase) ||
ChunkUtil.ToRawString(m.Content).Contains(SimpleSearchTerm, StringComparison.InvariantCultureIgnoreCase)
).OrderByDescending(m => m.Date));
messages
.Reverse()
.Where(m =>
ChunkUtil
.ToRawString(m.Sender)
.Contains(SimpleSearchTerm, StringComparison.InvariantCultureIgnoreCase)
|| ChunkUtil
.ToRawString(m.Content)
.Contains(SimpleSearchTerm, StringComparison.InvariantCultureIgnoreCase)
)
.OrderByDescending(m => m.Date)
);
}
private void DateRefresh()
@@ -386,7 +505,12 @@ public class DbViewer : Window
ulong? character = OnlyCurrentCharacter ? Plugin.PlayerState.ContentId : null;
var channels = SelectedChannels.Select(pair => (byte)pair.Key).ToArray();
var rangeMessageEnumerator = Plugin.MessageManager.Store.GetDateRange(AfterDate, BeforeDate, channels, character);
var rangeMessageEnumerator = Plugin.MessageManager.Store.GetDateRange(
AfterDate,
BeforeDate,
channels,
character
);
var messageHistory = rangeMessageEnumerator.ToArray();
await rangeMessageEnumerator.DisposeAsync();
@@ -397,28 +521,46 @@ public class DbViewer : Window
var totalCount = filteredHistory.Count;
var sb = new StringBuilder();
await using var stream = new StreamWriter(Path.Join(InputPath, $"Chat2_{DateTime.Now:yyyy_dd_M__HH_mm_ss}.txt"));
await using var stream = new StreamWriter(
Path.Join(InputPath, $"Chat2_{DateTime.Now:yyyy_dd_M__HH_mm_ss}.txt")
);
var batch = 0;
foreach (var messages in filteredHistory.Batch(5000))
{
await Plugin.Framework.RunOnTick(() =>
{
foreach (var message in messages)
await Plugin.Framework.RunOnTick(
() =>
{
if (!Sheets.LogKindSheet.TryGetRow((uint)message.Code.Type, out var logKind))
logKind = Sheets.LogKindSheet.GetRow(10); // default to say
foreach (var message in messages)
{
if (
!Sheets.LogKindSheet.TryGetRow(
(uint)message.Code.Type,
out var logKind
)
)
logKind = Sheets.LogKindSheet.GetRow(10); // default to say
var rossSender = new ReadOnlySeString(message.SenderSource.Encode());
var rossMessage = new ReadOnlySeString(message.ContentSource.Encode());
var rossSender = new ReadOnlySeString(
message.SenderSource.Encode()
);
var rossMessage = new ReadOnlySeString(
message.ContentSource.Encode()
);
var timestamp = message.Date.ToLocalTime().ToString(DateTimeFormat);
var text = Plugin.Evaluator.Evaluate(logKind.Format, [rossSender, rossMessage]).ToString();
sb.AppendLine($"[{timestamp}][{message.Code.Type.Name()}] {text}");
var timestamp = message.Date.ToLocalTime().ToString(DateTimeFormat);
var text = Plugin
.Evaluator.Evaluate(logKind.Format, [rossSender, rossMessage])
.ToString();
sb.AppendLine(
$"[{timestamp}][{message.Code.Type.Name()}] {text}"
);
batch++;
}
}, delayTicks: 5);
batch++;
}
},
delayTicks: 5
);
Notification.Progress = (float)batch / totalCount;
Notification.Content = $"Exported {batch} of {totalCount} messages";
@@ -447,5 +589,4 @@ public class DbViewer : Window
}
});
}
}
+27 -12
View File
@@ -1,10 +1,10 @@
using System.Numerics;
using HellionChat.Code;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using Lumina.Text.ReadOnly;
namespace HellionChat.Ui;
@@ -14,7 +14,8 @@ public class DebuggerWindow : Window, IDisposable
private readonly Plugin Plugin;
private readonly ChatLogWindow ChatLogWindow;
public DebuggerWindow(Plugin plugin) : base("Debugger###chat2-debugger")
public DebuggerWindow(Plugin plugin)
: base("Debugger###chat2-debugger")
{
Plugin = plugin;
ChatLogWindow = plugin.ChatLogWindow;
@@ -22,7 +23,7 @@ public class DebuggerWindow : Window, IDisposable
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(475, 600),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
};
RespectCloseHotkey = false;
@@ -40,7 +41,7 @@ public class DebuggerWindow : Window, IDisposable
public override unsafe void Draw()
{
var agent = (nint) AgentItemDetail.Instance();
var agent = (nint)AgentItemDetail.Instance();
ImGui.TextUnformatted($"Current Cursor Pos: {ChatLogWindow.CursorPos}");
if (ImGui.Selectable($"Agent Address: {agent:X}"))
ImGui.SetClipboardText(agent.ToString("X"));
@@ -50,23 +51,37 @@ public class DebuggerWindow : Window, IDisposable
ImGui.TextUnformatted($"Handle Tooltips: {ChatLogWindow.PayloadHandler.HandleTooltips}");
ImGui.TextUnformatted($"Hovered Item: {ChatLogWindow.PayloadHandler.HoveredItem}");
ImGui.TextUnformatted($"Hover Counter: {ChatLogWindow.PayloadHandler.HoverCounter}");
ImGui.TextUnformatted($"Last Hover Counter: {ChatLogWindow.PayloadHandler.LastHoverCounter}");
ImGui.TextUnformatted(
$"Last Hover Counter: {ChatLogWindow.PayloadHandler.LastHoverCounter}"
);
ImGuiHelpers.ScaledDummy(5.0f);
ImGui.TextColored(ImGuiColors.DalamudOrange, "Current Tab");
ImGui.TextUnformatted($"Name: {Plugin.CurrentTab.Name}");
ImGui.TextUnformatted($"Channel: {Plugin.CurrentTab.CurrentChannel.Channel.ToChatType().Name()}");
ImGui.TextUnformatted($"Tell Target: {Plugin.CurrentTab.CurrentChannel.TellTarget?.ToTargetString() ?? "Null"}");
ImGui.TextUnformatted(
$"Channel: {Plugin.CurrentTab.CurrentChannel.Channel.ToChatType().Name()}"
);
ImGui.TextUnformatted(
$"Tell Target: {Plugin.CurrentTab.CurrentChannel.TellTarget?.ToTargetString() ?? "Null"}"
);
ImGui.TextUnformatted($"Use Temp? {Plugin.CurrentTab.CurrentChannel.UseTempChannel}");
ImGui.TextUnformatted($"Temp Channel: {Plugin.CurrentTab.CurrentChannel.TempChannel.ToChatType().Name()}");
ImGui.TextUnformatted($"Temp Tell Target: {Plugin.CurrentTab.CurrentChannel.TempTellTarget?.ToTargetString() ?? "Null"}");
ImGui.TextUnformatted(
$"Temp Channel: {Plugin.CurrentTab.CurrentChannel.TempChannel.ToChatType().Name()}"
);
ImGui.TextUnformatted(
$"Temp Tell Target: {Plugin.CurrentTab.CurrentChannel.TempTellTarget?.ToTargetString() ?? "Null"}"
);
ImGui.TextUnformatted($"Name Set? {Plugin.CurrentTab.CurrentChannel.Name.Count > 0}");
ImGui.TextUnformatted($"Name {string.Join(" ", Plugin.CurrentTab.CurrentChannel.Name.Select(c => c.StringValue()))}");
ImGui.TextUnformatted(
$"Name {string.Join(" ", Plugin.CurrentTab.CurrentChannel.Name.Select(c => c.StringValue()))}"
);
ImGuiHelpers.ScaledDummy(5.0f);
ImGui.TextColored(ImGuiColors.DalamudOrange, "Vanilla Chat");
ImGui.TextUnformatted($"Channel: {new ReadOnlySeString(AgentChatLog.Instance()->ChannelLabel).ExtractText()}");
ImGui.TextUnformatted(
$"Channel: {new ReadOnlySeString(AgentChatLog.Instance()->ChannelLabel).ExtractText()}"
);
}
}
+39 -15
View File
@@ -1,11 +1,11 @@
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using HellionChat.Code;
using HellionChat.Privacy;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
namespace HellionChat.Ui;
@@ -13,7 +13,8 @@ public sealed class FirstRunWizard : Window
{
private readonly Plugin Plugin;
internal FirstRunWizard(Plugin plugin) : base($"{HellionStrings.Wizard_Title}###hellion-firstrun")
internal FirstRunWizard(Plugin plugin)
: base($"{HellionStrings.Wizard_Title}###hellion-firstrun")
{
Plugin = plugin;
@@ -50,33 +51,54 @@ public sealed class FirstRunWizard : Window
var cardWidth = (avail.X - ImGui.GetStyle().ItemSpacing.X * 2) / 3f;
var cardHeight = avail.Y - ImGui.GetTextLineHeightWithSpacing();
DrawCard("privacy-first", cardWidth, cardHeight,
DrawCard(
"privacy-first",
cardWidth,
cardHeight,
HellionStrings.Wizard_Profile_PrivacyFirst_Heading,
HellionStrings.Wizard_Profile_PrivacyFirst_Description,
null,
HellionStrings.Wizard_Profile_PrivacyFirst_Apply,
ApplyPrivacyFirst);
ApplyPrivacyFirst
);
ImGui.SameLine();
DrawCard("casual", cardWidth, cardHeight,
DrawCard(
"casual",
cardWidth,
cardHeight,
HellionStrings.Wizard_Profile_Casual_Heading,
HellionStrings.Wizard_Profile_Casual_Description,
null,
HellionStrings.Wizard_Profile_Casual_Apply,
ApplyCasual);
ApplyCasual
);
ImGui.SameLine();
DrawCard("full-history", cardWidth, cardHeight,
DrawCard(
"full-history",
cardWidth,
cardHeight,
HellionStrings.Wizard_Profile_FullHistory_Heading,
HellionStrings.Wizard_Profile_FullHistory_Description,
HellionStrings.Wizard_Profile_FullHistory_GdprWarning,
HellionStrings.Wizard_Profile_FullHistory_Apply,
ApplyFullHistory);
ApplyFullHistory
);
}
private void DrawCard(string id, float width, float height, string heading, string description, string? warning, string buttonLabel, Action onApply)
private void DrawCard(
string id,
float width,
float height,
string heading,
string description,
string? warning,
string buttonLabel,
Action onApply
)
{
using var child = ImRaii.Child($"##wizard-card-{id}", new Vector2(width, height), true);
if (!child.Success)
@@ -113,19 +135,21 @@ public sealed class FirstRunWizard : Window
private void ApplyPrivacyFirst()
{
Plugin.Config.PrivacyFilterEnabled = true;
Plugin.Config.PrivacyPersistChannels = [..PrivacyDefaults.PrivacyFirstWhitelist];
Plugin.Config.PrivacyPersistChannels = [.. PrivacyDefaults.PrivacyFirstWhitelist];
Plugin.Config.PrivacyPersistUnknownChannels = false;
Plugin.Config.RetentionEnabled = true;
Plugin.Config.RetentionDefaultDays = 30;
Plugin.Config.RetentionPerChannelDays =
PrivacyDefaults.DefaultRetentionDays.ToDictionary(p => p.Key, p => p.Value);
Plugin.Config.RetentionPerChannelDays = PrivacyDefaults.DefaultRetentionDays.ToDictionary(
p => p.Key,
p => p.Value
);
}
private void ApplyCasual()
{
Plugin.Config.PrivacyFilterEnabled = true;
Plugin.Config.PrivacyPersistChannels = [..PrivacyDefaults.CasualWhitelist];
Plugin.Config.PrivacyPersistChannels = [.. PrivacyDefaults.CasualWhitelist];
Plugin.Config.PrivacyPersistUnknownChannels = false;
Plugin.Config.RetentionEnabled = true;
+55 -55
View File
@@ -1,7 +1,7 @@
using HellionChat.Themes;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Themes;
using HellionChat.Util;
namespace HellionChat.Ui;
@@ -21,18 +21,18 @@ internal static class HellionStyle
{
var a = theme.AbgrCache;
var stack = new StackHandle();
stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
return stack;
}
@@ -67,71 +67,71 @@ internal static class HellionStyle
var childBgWithAlpha = (c.ChildBg & 0xFFFFFF00u) | childBgAlpha;
// Layout
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding);
stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding);
stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding);
stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding);
stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding);
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding);
stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding);
stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding);
stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding);
stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding);
stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, l.ScrollbarRounding);
stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize);
stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize);
stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize);
stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize);
// Surfaces — WindowBg/ChildBg use the per-push opacity-modulated value,
// so they go through the RGBA path; everything else reads from cache.
stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha);
stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha);
stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u);
// Frames
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
// Title bars
stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg);
// Buttons
stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
// Headers / selectables
stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
// Tabs
stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark);
// Scrollbar
stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface);
stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent);
// Resize grip
stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent);
// Check mark + slider grab
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
// Separator
stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);
stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);
return stack;
}
@@ -140,14 +140,14 @@ internal static class HellionStyle
{
private readonly List<IDisposable> _items = new(64);
internal void PushColor(ImGuiCol slot, uint rgba)
=> _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));
internal void PushColor(ImGuiCol slot, uint rgba) =>
_items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));
internal void PushColorAbgr(ImGuiCol slot, uint abgr)
=> _items.Add(ImRaii.PushColor(slot, abgr));
internal void PushColorAbgr(ImGuiCol slot, uint abgr) =>
_items.Add(ImRaii.PushColor(slot, abgr));
internal void PushStyleVar(ImGuiStyleVar var, float value)
=> _items.Add(ImRaii.PushStyle(var, value));
internal void PushStyleVar(ImGuiStyleVar var, float value) =>
_items.Add(ImRaii.PushStyle(var, value));
public void Dispose()
{
+48 -14
View File
@@ -1,16 +1,16 @@
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui;
@@ -30,12 +30,18 @@ public partial class InputPreview : Window
internal int SelectedCursorPos = -1;
internal InputPreview(ChatLogWindow logWindow) : base("##chat2-inputpreview")
internal InputPreview(ChatLogWindow logWindow)
: base("##chat2-inputpreview")
{
LogWindow = logWindow;
Flags = ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoScrollbar;
Flags =
ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoScrollbar;
RespectCloseHotkey = false;
DisableWindowSounds = true;
@@ -49,7 +55,10 @@ public partial class InputPreview : Window
Plugin.Framework.Update -= UpdateConditionCheck;
}
private bool ValidDraw => !string.IsNullOrEmpty(LogWindow.Chat) && LogWindow.Chat.Length >= Plugin.Config.PreviewMinimum;
private bool ValidDraw =>
!string.IsNullOrEmpty(LogWindow.Chat)
&& LogWindow.Chat.Length >= Plugin.Config.PreviewMinimum;
private void UpdateConditionCheck(IFramework framework)
{
Drawing = ValidDraw;
@@ -70,7 +79,9 @@ public partial class InputPreview : Window
var bytes = Encoding.UTF8.GetBytes(LogWindow.Chat.Trim());
AutoTranslate.ReplaceWithPayload(ref bytes);
var chunks = ChunkUtil.ToChunks(SeString.Parse(bytes), ChunkSource.Content, ChatType.Say).ToList();
var chunks = ChunkUtil
.ToChunks(SeString.Parse(bytes), ChunkSource.Content, ChatType.Say)
.ToList();
PreviewMessage = Message.FakeMessage(chunks, new ChatCode(XivChatType.Say, 0, 0));
PreviewMessage.DecodeTextParam();
}
@@ -79,7 +90,9 @@ public partial class InputPreview : Window
internal bool IsDrawable => ValidDraw && HasEvaluation;
private static bool IsWindowMode => Plugin.Config.PreviewPosition is PreviewPosition.Top or PreviewPosition.Bottom;
private static bool IsWindowMode =>
Plugin.Config.PreviewPosition is PreviewPosition.Top or PreviewPosition.Bottom;
public override bool DrawConditions()
{
return IsWindowMode && IsDrawable;
@@ -96,7 +109,11 @@ public partial class InputPreview : Window
{
PreviewPosition.Top => pos.Y - PreviewHeight,
PreviewPosition.Bottom => pos.Y + size.Y,
_ => throw new ArgumentOutOfRangeException(nameof(Plugin.Config.PreviewPosition), Plugin.Config.PreviewPosition, null)
_ => throw new ArgumentOutOfRangeException(
nameof(Plugin.Config.PreviewPosition),
Plugin.Config.PreviewPosition,
null
),
};
Position = pos with { Y = y };
@@ -141,7 +158,12 @@ public partial class InputPreview : Window
}
}
private void DrawChunksPreview(IReadOnlyList<Chunk> chunks, PayloadHandler? handler = null, float lineWidth = 0f, int unique = 0)
private void DrawChunksPreview(
IReadOnlyList<Chunk> chunks,
PayloadHandler? handler = null,
float lineWidth = 0f,
int unique = 0
)
{
CursorPosition = 0;
@@ -168,7 +190,12 @@ public partial class InputPreview : Window
}
}
private void DrawChunkPreview(Chunk chunk, PayloadHandler? handler = null, float lineWidth = 0f, int unique = 0)
private void DrawChunkPreview(
Chunk chunk,
PayloadHandler? handler = null,
float lineWidth = 0f,
int unique = 0
)
{
if (chunk is IconChunk icon)
{
@@ -248,7 +275,14 @@ public partial class InputPreview : Window
var letterSize = ImGui.CalcTextSize(letter.ToString());
CursorPosition++;
if (ImGui.Selectable($"{letter}##{CursorPosition + unique}", false, ImGuiSelectableFlags.None, letterSize))
if (
ImGui.Selectable(
$"{letter}##{CursorPosition + unique}",
false,
ImGuiSelectableFlags.None,
letterSize
)
)
{
SelectedCursorPos = CursorPosition;
LogWindow.FocusedPreview = true;
+34 -10
View File
@@ -1,8 +1,8 @@
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
namespace HellionChat.Ui;
@@ -27,7 +27,8 @@ internal class Popout : Window
// matching pop-out window when an LRU temp tab gets evicted.
internal Guid TabIdentifier => Tab.Identifier;
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout")
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx)
: base($"{tab.Name}##popout")
{
ChatLogWindow = chatLogWindow;
Tab = tab;
@@ -59,7 +60,11 @@ internal class Popout : Window
if (Tab.IndependentHide ? HideStateCheck() : ChatLogWindow.IsHidden)
return false;
if (!Plugin.Config.HideWhenInactive || (!Plugin.Config.InactivityHideActiveDuringBattle && Plugin.InBattle) || !Tab.UnhideOnActivity)
if (
!Plugin.Config.HideWhenInactive
|| (!Plugin.Config.InactivityHideActiveDuringBattle && Plugin.InBattle)
|| !Tab.UnhideOnActivity
)
{
LastActivityTime = FrameTime;
return true;
@@ -169,7 +174,13 @@ internal class Popout : Window
var dismiss = false;
var openSettings = false;
using (var child = ImRaii.Child("##v060-pop-out-hint", new System.Numerics.Vector2(0f, 64f), true))
using (
var child = ImRaii.Child(
"##v060-pop-out-hint",
new System.Numerics.Vector2(0f, 64f),
true
)
)
{
if (child)
{
@@ -222,7 +233,7 @@ internal class Popout : Window
Cutscene,
CutsceneOverride,
User,
Battle
Battle,
}
private HideState CurrentHideState = HideState.None;
@@ -244,7 +255,11 @@ internal class Popout : Window
}
// if the chat has no hide state and in a cutscene, set the hide state to cutscene
if (Tab.HideDuringCutscenes && CurrentHideState == HideState.None && (Plugin.CutsceneActive || Plugin.GposeActive))
if (
Tab.HideDuringCutscenes
&& CurrentHideState == HideState.None
&& (Plugin.CutsceneActive || Plugin.GposeActive)
)
{
if (ChatLogWindow.Plugin.Functions.Chat.CheckHideFlags())
{
@@ -254,9 +269,15 @@ internal class Popout : Window
}
// if the chat is hidden because of a cutscene and no longer in a cutscene, set the hide state to none
if (CurrentHideState is HideState.Cutscene or HideState.CutsceneOverride && !Plugin.CutsceneActive && !Plugin.GposeActive)
if (
CurrentHideState is HideState.Cutscene or HideState.CutsceneOverride
&& !Plugin.CutsceneActive
&& !Plugin.GposeActive
)
{
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: {CurrentHideState} → None (cutscene/gpose ended)");
Plugin.Log.Verbose(
$"Popout HideState [{Tab.Name}]: {CurrentHideState} → None (cutscene/gpose ended)"
);
CurrentHideState = HideState.None;
}
@@ -264,7 +285,9 @@ internal class Popout : Window
if (CurrentHideState == HideState.Cutscene && ChatLogWindow.Activate)
{
CurrentHideState = HideState.CutsceneOverride;
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Cutscene → CutsceneOverride (user activate)");
Plugin.Log.Verbose(
$"Popout HideState [{Tab.Name}]: Cutscene → CutsceneOverride (user activate)"
);
}
// if the user hid the chat and is now activating chat, reset the hide state
@@ -274,6 +297,7 @@ internal class Popout : Window
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: User → None (activate)");
}
return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Tab.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn);
return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle
|| (Tab.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn);
}
}
+174 -107
View File
@@ -1,13 +1,13 @@
using System.Globalization;
using System.Numerics;
using System.Text;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
using Dalamud.Utility;
using HellionChat.Util;
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
namespace HellionChat.Ui;
@@ -16,29 +16,30 @@ public class SeStringDebugger : Window
{
private readonly Plugin Plugin;
public SeStringDebugger(Plugin plugin) : base("SeString Debugger###chat2-sestringdebugger")
public SeStringDebugger(Plugin plugin)
: base("SeString Debugger###chat2-sestringdebugger")
{
Plugin = plugin;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(475, 600),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
};
RespectCloseHotkey = false;
DisableWindowSounds = true;
#if DEBUG
#if DEBUG
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute += Toggle;
#endif
#endif
}
public void Dispose()
{
#if DEBUG
#if DEBUG
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute -= Toggle;
#endif
#endif
}
private void Toggle(string _, string __) => Toggle();
@@ -75,99 +76,143 @@ public class SeStringDebugger : Window
{
case UIForegroundPayload color:
{
RenderMetadataDictionary("Link ForegroundColor", new Dictionary<string, string?>
{
{ "Enabled?", color.IsEnabled.ToString() },
{ "ColorKey", color.IsEnabled ? color.ColorKey.ToString() : "Color Ended" },
});
RenderMetadataDictionary(
"Link ForegroundColor",
new Dictionary<string, string?>
{
{ "Enabled?", color.IsEnabled.ToString() },
{
"ColorKey",
color.IsEnabled ? color.ColorKey.ToString() : "Color Ended"
},
}
);
break;
}
case MapLinkPayload map:
{
RenderMetadataDictionary("Link MapLinkPayload", new Dictionary<string, string?>
{
{ "Map.RowId", map.Map.RowId.ToString() },
{ "Map.PlaceName", map.Map.Value.PlaceName.Value.Name.ToString() },
{ "Map.PlaceNameRegion", map.Map.Value.PlaceNameRegion.Value.Name.ToString() },
{ "Map.PlaceNameSub", map.Map.Value.PlaceNameSub.Value.Name.ToString() },
{ "TerritoryType.RowId", map.TerritoryType.RowId.ToString() },
{ "RawX", map.RawX.ToString() },
{ "RawY", map.RawY.ToString() },
{ "XCoord", map.XCoord.ToString(CultureInfo.InvariantCulture) },
{ "YCoord", map.YCoord.ToString(CultureInfo.InvariantCulture) },
{ "CoordinateString", map.CoordinateString },
{ "DataString", map.DataString },
});
RenderMetadataDictionary(
"Link MapLinkPayload",
new Dictionary<string, string?>
{
{ "Map.RowId", map.Map.RowId.ToString() },
{ "Map.PlaceName", map.Map.Value.PlaceName.Value.Name.ToString() },
{
"Map.PlaceNameRegion",
map.Map.Value.PlaceNameRegion.Value.Name.ToString()
},
{
"Map.PlaceNameSub",
map.Map.Value.PlaceNameSub.Value.Name.ToString()
},
{ "TerritoryType.RowId", map.TerritoryType.RowId.ToString() },
{ "RawX", map.RawX.ToString() },
{ "RawY", map.RawY.ToString() },
{ "XCoord", map.XCoord.ToString(CultureInfo.InvariantCulture) },
{ "YCoord", map.YCoord.ToString(CultureInfo.InvariantCulture) },
{ "CoordinateString", map.CoordinateString },
{ "DataString", map.DataString },
}
);
break;
}
case QuestPayload quest:
{
RenderMetadataDictionary("Link QuestPayload", new Dictionary<string, string?>
{
{ "Quest.RowId", quest.Quest.RowId.ToString() },
{ "Quest.Name", quest.Quest.Value.Name.ToString() },
});
RenderMetadataDictionary(
"Link QuestPayload",
new Dictionary<string, string?>
{
{ "Quest.RowId", quest.Quest.RowId.ToString() },
{ "Quest.Name", quest.Quest.Value.Name.ToString() },
}
);
break;
}
case DalamudLinkPayload link:
{
RenderMetadataDictionary("Link DalamudLinkPayload", new Dictionary<string, string?>
{
{ "CommandId", link.CommandId.ToString() },
{ "Plugin", link.Plugin },
});
RenderMetadataDictionary(
"Link DalamudLinkPayload",
new Dictionary<string, string?>
{
{ "CommandId", link.CommandId.ToString() },
{ "Plugin", link.Plugin },
}
);
break;
}
case DalamudPartyFinderPayload pf:
{
RenderMetadataDictionary("Link PartyFinderPayload", new Dictionary<string, string?>
{
{ "ListingId", pf.ListingId.ToString() },
{ "LinkType", EnumName(pf.LinkType) },
});
RenderMetadataDictionary(
"Link PartyFinderPayload",
new Dictionary<string, string?>
{
{ "ListingId", pf.ListingId.ToString() },
{ "LinkType", EnumName(pf.LinkType) },
}
);
break;
}
case PlayerPayload player:
{
RenderMetadataDictionary("Link PlayerPayload", new Dictionary<string, string?>
{
{ "Displayed", player.DisplayedName },
{ "Player Name", player.PlayerName },
{ "World Name", player.World.Value.Name.ExtractText() },
{ "Data", string.Join(" ", player.Encode().Select(b => b.ToString("X2"))) },
});
RenderMetadataDictionary(
"Link PlayerPayload",
new Dictionary<string, string?>
{
{ "Displayed", player.DisplayedName },
{ "Player Name", player.PlayerName },
{ "World Name", player.World.Value.Name.ExtractText() },
{
"Data",
string.Join(" ", player.Encode().Select(b => b.ToString("X2")))
},
}
);
break;
}
case ItemPayload item:
{
RenderMetadataDictionary("Link ItemPayload", new Dictionary<string, string?>
{
{ "ItemId", item.ItemId.ToString() },
{ "RawItemId", item.RawItemId.ToString() },
{ "Kind", EnumName(item.Kind) },
{ "IsHQ", item.IsHQ.ToString() },
{ "Item.Name", item.Kind == ItemKind.EventItem ? Sheets.EventItemSheet.GetRow(item.ItemId).Name.ExtractText() : Sheets.ItemSheet.GetRow(item.ItemId).Name.ExtractText() },
});
RenderMetadataDictionary(
"Link ItemPayload",
new Dictionary<string, string?>
{
{ "ItemId", item.ItemId.ToString() },
{ "RawItemId", item.RawItemId.ToString() },
{ "Kind", EnumName(item.Kind) },
{ "IsHQ", item.IsHQ.ToString() },
{
"Item.Name",
item.Kind == ItemKind.EventItem
? Sheets.EventItemSheet.GetRow(item.ItemId).Name.ExtractText()
: Sheets.ItemSheet.GetRow(item.ItemId).Name.ExtractText()
},
}
);
break;
}
case AutoTranslatePayload at:
{
RenderMetadataDictionary("Link AutoTranslatePayload", new Dictionary<string, string?>
{
{ "Text", at.Text },
{ "Key/Group", $"{at.Key}/{at.Group}" },
{ "Data", string.Join(" ", at.Encode().Select(b => b.ToString("X2"))) },
});
RenderMetadataDictionary(
"Link AutoTranslatePayload",
new Dictionary<string, string?>
{
{ "Text", at.Text },
{ "Key/Group", $"{at.Key}/{at.Group}" },
{ "Data", string.Join(" ", at.Encode().Select(b => b.ToString("X2"))) },
}
);
break;
}
case IconPayload icon:
{
var found = IconUtil.GfdFileView.TryGetEntry((uint) icon.Icon, out _);
RenderMetadataDictionary("Link IconPayload", new Dictionary<string, string?>
{
{ "Found", found.ToString() },
{ "Icon ID", ((uint) icon.Icon).ToString() },
});
var found = IconUtil.GfdFileView.TryGetEntry((uint)icon.Icon, out _);
RenderMetadataDictionary(
"Link IconPayload",
new Dictionary<string, string?>
{
{ "Found", found.ToString() },
{ "Icon ID", ((uint)icon.Icon).ToString() },
}
);
break;
}
case RawPayload raw:
@@ -175,48 +220,60 @@ public class SeStringDebugger : Window
var colorPayload = ColorPayload.From(raw.Data);
if (colorPayload != null)
{
RenderMetadataDictionary("Link ColorPayload", new Dictionary<string, string?>
{
{ "Unshifted", colorPayload.UnshiftedColor.ToString("X8") },
{ "Color", colorPayload.Color.ToString("X8") },
{ "Enabled?", colorPayload.Enabled.ToString() },
});
RenderMetadataDictionary(
"Link ColorPayload",
new Dictionary<string, string?>
{
{ "Unshifted", colorPayload.UnshiftedColor.ToString("X8") },
{ "Color", colorPayload.Color.ToString("X8") },
{ "Enabled?", colorPayload.Enabled.ToString() },
}
);
}
else
{
RenderMetadataDictionary("Link RawPayload", new Dictionary<string, string?>
{
{ "Data", string.Join(" ", raw.Data.Select(b => b.ToString("X2"))) },
{ "Type", EnumName(raw.Type) },
});
RenderMetadataDictionary(
"Link RawPayload",
new Dictionary<string, string?>
{
{
"Data",
string.Join(" ", raw.Data.Select(b => b.ToString("X2")))
},
{ "Type", EnumName(raw.Type) },
}
);
}
break;
}
case StatusPayload status:
{
RenderMetadataDictionary("Link StatusPayload", new Dictionary<string, string?>
{
{ "Status.RowId", status.Status.RowId.ToString() },
{ "Status.Name", status.Status.Value.Name.ExtractText() },
{ "Status.Icon", status.Status.Value.Icon.ToString() }
});
RenderMetadataDictionary(
"Link StatusPayload",
new Dictionary<string, string?>
{
{ "Status.RowId", status.Status.RowId.ToString() },
{ "Status.Name", status.Status.Value.Name.ExtractText() },
{ "Status.Icon", status.Status.Value.Icon.ToString() },
}
);
break;
}
case Util.PartyFinderPayload pf:
{
RenderMetadataDictionary("Link PartyFinderPayload", new Dictionary<string, string?>
{
{ "Id", pf.Id.ToString() }
});
RenderMetadataDictionary(
"Link PartyFinderPayload",
new Dictionary<string, string?> { { "Id", pf.Id.ToString() } }
);
break;
}
case AchievementPayload achievement:
{
RenderMetadataDictionary("Link AchievementPayload", new Dictionary<string, string?>
{
{ "Id", achievement.Id.ToString() }
});
RenderMetadataDictionary(
"Link AchievementPayload",
new Dictionary<string, string?> { { "Id", achievement.Id.ToString() } }
);
break;
}
default:
@@ -224,35 +281,45 @@ public class SeStringDebugger : Window
if (payloadData.Length == 0)
{
RenderMetadataDictionary("Empty Payload", new Dictionary<string, string?>
{
{ "Type", payload.GetType().Name },
});
RenderMetadataDictionary(
"Empty Payload",
new Dictionary<string, string?> { { "Type", payload.GetType().Name } }
);
break;
}
var initialByte = payloadData[0];
if (initialByte != 0x02)
{
RenderMetadataDictionary("Text Payload", new Dictionary<string, string?>
{
{ "Content", Encoding.UTF8.GetString(payloadData) },
});
RenderMetadataDictionary(
"Text Payload",
new Dictionary<string, string?>
{
{ "Content", Encoding.UTF8.GetString(payloadData) },
}
);
}
else
{
var unknown = new RawPayload(payloadData);
RenderMetadataDictionary("Link Unknown", new Dictionary<string, string?>
{
{ "Unknown", string.Join(" ", unknown.Data.Select(b => b.ToString("X2"))) },
});
RenderMetadataDictionary(
"Link Unknown",
new Dictionary<string, string?>
{
{
"Unknown",
string.Join(" ", unknown.Data.Select(b => b.ToString("X2")))
},
}
);
}
break;
}
}
}
private static string? EnumName<T>(T? value) where T : Enum
private static string? EnumName<T>(T? value)
where T : Enum
{
if (value == null)
{
+61 -31
View File
@@ -1,11 +1,11 @@
using System.Numerics;
using HellionChat.Resources;
using HellionChat.Ui.SettingsTabs;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using Dalamud.Bindings.ImGui;
using HellionChat.Resources;
using HellionChat.Ui.SettingsTabs;
using HellionChat.Util;
namespace HellionChat.Ui;
@@ -25,7 +25,8 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
private SettingsView View = SettingsView.Overview;
private readonly SettingsOverview Overview;
internal SettingsWindow(Plugin plugin) : base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings")
internal SettingsWindow(Plugin plugin)
: base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings")
{
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;
@@ -33,7 +34,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(475, 600),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
};
Plugin = plugin;
@@ -60,7 +61,9 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
Initialise();
Plugin.Commands.Register("/hellion", "Perform various actions with Hellion Chat.").Execute += Command;
Plugin
.Commands.Register("/hellion", "Perform various actions with Hellion Chat.")
.Execute += Command;
Plugin.Interface.UiBuilder.OpenConfigUi += Toggle;
}
@@ -93,9 +96,11 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
// Pflicht — sonst triggert ESC auch wenn der User ein anderes Fenster
// fokussiert hat und ESC fürs Game-Menü drückt (Codebase-Pattern siehe
// Util/SearchSelector.cs:37).
if (View == SettingsView.Detail
if (
View == SettingsView.Detail
&& ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)
&& ImGui.IsKeyPressed(ImGuiKey.Escape))
&& ImGui.IsKeyPressed(ImGuiKey.Escape)
)
{
View = SettingsView.Overview;
return;
@@ -150,7 +155,12 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
// der User in eine andere Section will, geht er zurück zur Overview
// (Breadcrumb / ESC).
var style = ImGui.GetStyle();
var height = ImGui.GetContentRegionAvail().Y - style.FramePadding.Y * 2 - style.ItemSpacing.Y - style.ItemInnerSpacing.Y * 2 - ImGui.CalcTextSize("A").Y;
var height =
ImGui.GetContentRegionAvail().Y
- style.FramePadding.Y * 2
- style.ItemSpacing.Y
- style.ItemInnerSpacing.Y * 2
- ImGui.CalcTextSize("A").Y;
using var child = ImRaii.Child("##chat2-settings-detail", new Vector2(-1, height));
if (child.Success)
@@ -184,9 +194,16 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
using (ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.RgbaToAbgr(0xFF4542FF)))
using (ImRaii.PushColor(ImGuiCol.Text, 0xFFFFFFFF))
{
var buttonWidth = ImGui.CalcTextSize(buttonLabel).X + ImGui.GetStyle().FramePadding.X * 2;
var buttonWidth2 = ImGui.CalcTextSize(buttonLabel2).X + ImGui.GetStyle().FramePadding.X * 2;
ImGui.SameLine(ImGui.GetContentRegionAvail().X - buttonWidth - buttonWidth2 - ImGui.GetStyle().ItemSpacing.X);
var buttonWidth =
ImGui.CalcTextSize(buttonLabel).X + ImGui.GetStyle().FramePadding.X * 2;
var buttonWidth2 =
ImGui.CalcTextSize(buttonLabel2).X + ImGui.GetStyle().FramePadding.X * 2;
ImGui.SameLine(
ImGui.GetContentRegionAvail().X
- buttonWidth
- buttonWidth2
- ImGui.GetStyle().ItemSpacing.X
);
if (ImGui.Button(buttonLabel2))
Dalamud.Utility.Util.OpenLink("https://ko-fi.com/infiii");
@@ -203,13 +220,15 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
// calculate all conditions before updating config
var hideChanged = !Mutable.HideChat && Mutable.HideChat != Plugin.Config.HideChat;
var languageChanged = Mutable.LanguageOverride != Plugin.Config.LanguageOverride;
var fontChanged = Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2
|| Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2
|| Mutable.ItalicFontV2 != Plugin.Config.ItalicFontV2
|| Mutable.ExtraGlyphRanges != Plugin.Config.ExtraGlyphRanges
|| Mutable.UseHellionFont != Plugin.Config.UseHellionFont;
var fontSizeChanged = Math.Abs(Mutable.SymbolsFontSizeV2 - Plugin.Config.SymbolsFontSizeV2) > 0.001
|| Math.Abs(Mutable.FontSizeV2 - Plugin.Config.FontSizeV2) > 0.001;
var fontChanged =
Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2
|| Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2
|| Mutable.ItalicFontV2 != Plugin.Config.ItalicFontV2
|| Mutable.ExtraGlyphRanges != Plugin.Config.ExtraGlyphRanges
|| Mutable.UseHellionFont != Plugin.Config.UseHellionFont;
var fontSizeChanged =
Math.Abs(Mutable.SymbolsFontSizeV2 - Plugin.Config.SymbolsFontSizeV2) > 0.001
|| Math.Abs(Mutable.FontSizeV2 - Plugin.Config.FontSizeV2) > 0.001;
var italicStateChanged = Mutable.ItalicEnabled != Plugin.Config.ItalicEnabled;
// v1.2.0 — Refilter only if a filter-relevant setting actually
// changed. The Clear+Refilter cycle reloads messages from the DB,
@@ -258,21 +277,26 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
private bool HasFilterRelevantChanges()
{
// Top-level privacy controls.
if (Mutable.PrivacyFilterEnabled != Plugin.Config.PrivacyFilterEnabled) return true;
if (Mutable.PrivacyPersistUnknownChannels != Plugin.Config.PrivacyPersistUnknownChannels) return true;
if (!Mutable.PrivacyPersistChannels.SetEquals(Plugin.Config.PrivacyPersistChannels)) return true;
if (Mutable.PrivacyFilterEnabled != Plugin.Config.PrivacyFilterEnabled)
return true;
if (Mutable.PrivacyPersistUnknownChannels != Plugin.Config.PrivacyPersistUnknownChannels)
return true;
if (!Mutable.PrivacyPersistChannels.SetEquals(Plugin.Config.PrivacyPersistChannels))
return true;
// FilterIncludePreviousSessions changes the GetMostRecentMessages
// window in MessageManager.FilterAllTabs and is therefore filter-
// relevant even though it lives outside the Privacy block.
if (Mutable.FilterIncludePreviousSessions != Plugin.Config.FilterIncludePreviousSessions) return true;
if (Mutable.FilterIncludePreviousSessions != Plugin.Config.FilterIncludePreviousSessions)
return true;
// Per-tab channel selection. Compare persistent tabs only —
// TempTabs are session-only and never refiltered anyway.
var origPersistent = Plugin.Config.Tabs.Where(t => !t.IsTempTab).ToList();
var newPersistent = Mutable.Tabs.Where(t => !t.IsTempTab).ToList();
if (origPersistent.Count != newPersistent.Count) return true; // add or delete
if (origPersistent.Count != newPersistent.Count)
return true; // add or delete
for (var i = 0; i < origPersistent.Count; i++)
{
@@ -282,18 +306,24 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
// Identifier mismatch at the same index means reorder or
// a slot got swapped — treat as filter-relevant so the new
// channel-selection layout actually applies.
if (orig.Identifier != neu.Identifier) return true;
if (orig.Identifier != neu.Identifier)
return true;
if (orig.ExtraChatAll != neu.ExtraChatAll) return true;
if (!orig.ExtraChatChannels.SetEquals(neu.ExtraChatChannels)) return true;
if (orig.ExtraChatAll != neu.ExtraChatAll)
return true;
if (!orig.ExtraChatChannels.SetEquals(neu.ExtraChatChannels))
return true;
// SelectedChannels is a Dictionary<ChatType, (ChatSource, ChatSource)>
// — value-tuple equality already does the right thing per-pair.
if (orig.SelectedChannels.Count != neu.SelectedChannels.Count) return true;
if (orig.SelectedChannels.Count != neu.SelectedChannels.Count)
return true;
foreach (var pair in orig.SelectedChannels)
{
if (!neu.SelectedChannels.TryGetValue(pair.Key, out var nv)) return true;
if (!pair.Value.Equals(nv)) return true;
if (!neu.SelectedChannels.TryGetValue(pair.Key, out var nv))
return true;
if (!pair.Value.Equals(nv))
return true;
}
}
+50 -12
View File
@@ -18,16 +18,40 @@ internal sealed class SettingsOverview
// + Export + DB-Viewer + Advanced.
private static readonly (FontAwesomeIcon Icon, string TitleKey, string SubtextKey)[] CardDefs =
[
(FontAwesomeIcon.SlidersH, "Settings_Card_General_Title", "Settings_Card_General_Subtext"),
(FontAwesomeIcon.Palette, "Settings_Card_ThemeAndLayout_Title", "Settings_Card_ThemeAndLayout_Subtext"),
(FontAwesomeIcon.Font, "Settings_Card_FontsAndColours_Title", "Settings_Card_FontsAndColours_Subtext"),
(FontAwesomeIcon.WindowMaximize, "Settings_Card_Window_Title", "Settings_Card_Window_Subtext"),
(FontAwesomeIcon.Comments, "Settings_Card_Chat_Title", "Settings_Card_Chat_Subtext"),
(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"),
(FontAwesomeIcon.SlidersH, "Settings_Card_General_Title", "Settings_Card_General_Subtext"),
(
FontAwesomeIcon.Palette,
"Settings_Card_ThemeAndLayout_Title",
"Settings_Card_ThemeAndLayout_Subtext"
),
(
FontAwesomeIcon.Font,
"Settings_Card_FontsAndColours_Title",
"Settings_Card_FontsAndColours_Subtext"
),
(
FontAwesomeIcon.WindowMaximize,
"Settings_Card_Window_Title",
"Settings_Card_Window_Subtext"
),
(FontAwesomeIcon.Comments, "Settings_Card_Chat_Title", "Settings_Card_Chat_Subtext"),
(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"
),
];
public SettingsOverview(SettingsWindow window)
@@ -57,7 +81,14 @@ internal sealed class SettingsOverview
}
}
private void DrawCard(int index, FontAwesomeIcon icon, string title, string subtext, float w, float h)
private void DrawCard(
int index,
FontAwesomeIcon icon,
string title,
string subtext,
float w,
float h
)
{
// BeginGroup macht den Card-Bereich zu einem einzelnen ImGui-Layout-Item.
// Damit funktioniert SameLine() im Caller-Loop — sonst tracked ImGui die
@@ -94,7 +125,14 @@ internal sealed class SettingsOverview
// erweitern und das SameLine-Wrapping in der Card-Reihe brechen, daher
// bleibt der Subtext bewusst beim DrawList-Overlay-Pattern.
var subtextWrapWidth = w - 32f;
draw.AddText(ImGui.GetFont(), ImGui.GetFontSize(), subtextPos, subtextColor, subtext, subtextWrapWidth);
draw.AddText(
ImGui.GetFont(),
ImGui.GetFontSize(),
subtextPos,
subtextColor,
subtext,
subtextWrapWidth
);
ImGui.EndGroup();
+80 -19
View File
@@ -1,11 +1,11 @@
using System.Numerics;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui.SettingsTabs;
@@ -21,6 +21,7 @@ internal sealed class Chat : ISettingsTab
public string Name => HellionStrings.Settings_Tab_Chat + "###tabs-chat";
private SearchSelector.SelectorPopupOptions WordPopupOptions;
// Snapshot of EmoteCache.State for which we last built WordPopupOptions.
// Without this, an empty FilteredSheet (e.g., the user blocked every emote)
// would trigger a refill every frame the settings tab is open.
@@ -39,7 +40,9 @@ internal sealed class Chat : ISettingsTab
{
return new SearchSelector.SelectorPopupOptions
{
FilteredSheet = EmoteCache.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)).ToArray()
FilteredSheet = EmoteCache
.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w))
.ToArray(),
};
}
@@ -64,7 +67,10 @@ internal sealed class Chat : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Enable_Name, ref Mutable.EnableAutoTellTabs);
ImGui.Checkbox(
HellionStrings.ChatLog_AutoTellTabs_Enable_Name,
ref Mutable.EnableAutoTellTabs
);
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Enable_Description);
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
@@ -75,13 +81,22 @@ internal sealed class Chat : ISettingsTab
}
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description);
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Compact_Name, ref Mutable.AutoTellTabsCompactDisplay);
ImGui.Checkbox(
HellionStrings.ChatLog_AutoTellTabs_Compact_Name,
ref Mutable.AutoTellTabsCompactDisplay
);
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Compact_Description);
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Name, ref Mutable.AutoTellTabsOpenAsPopout);
ImGui.Checkbox(
HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Name,
ref Mutable.AutoTellTabsOpenAsPopout
);
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Description);
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name, ref Mutable.AutoTellTabsShowGreetedToggle);
ImGui.Checkbox(
HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name,
ref Mutable.AutoTellTabsShowGreetedToggle
);
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description);
ImGui.Spacing();
@@ -96,7 +111,14 @@ internal sealed class Chat : ISettingsTab
var preload = Mutable.AutoTellTabsHistoryPreload;
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
if (ImGui.SliderInt(HellionStrings.Privacy_AutoTellTabs_Preload_Name, ref preload, 0, 100))
if (
ImGui.SliderInt(
HellionStrings.Privacy_AutoTellTabs_Preload_Name,
ref preload,
0,
100
)
)
{
Mutable.AutoTellTabsHistoryPreload = preload;
}
@@ -117,12 +139,18 @@ internal sealed class Chat : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
ImGui.Checkbox(Language.Options_CollapseDuplicateMessages_Name, ref Mutable.CollapseDuplicateMessages);
ImGui.Checkbox(
Language.Options_CollapseDuplicateMessages_Name,
ref Mutable.CollapseDuplicateMessages
);
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMessages_Description);
if (Mutable.CollapseDuplicateMessages)
{
ImGui.Checkbox(Language.Options_CollapseDuplicateMsgUniqueLink_Name, ref Mutable.CollapseKeepUniqueLinks);
ImGui.Checkbox(
Language.Options_CollapseDuplicateMsgUniqueLink_Name,
ref Mutable.CollapseKeepUniqueLinks
);
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description);
}
}
@@ -138,7 +166,12 @@ internal sealed class Chat : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Preview_Name, Mutable.PreviewPosition.Name()))
using (
var combo = ImGuiUtil.BeginComboVertical(
Language.Options_Preview_Name,
Mutable.PreviewPosition.Name()
)
)
{
if (combo)
{
@@ -153,7 +186,13 @@ internal sealed class Chat : ISettingsTab
}
ImGuiUtil.HelpMarker(Language.Options_Preview_Description);
if (ImGuiUtil.InputIntVertical(Language.Options_PreviewMinimum_Name, Language.Options_PreviewMinimum_Description, ref Mutable.PreviewMinimum))
if (
ImGuiUtil.InputIntVertical(
Language.Options_PreviewMinimum_Name,
Language.Options_PreviewMinimum_Description,
ref Mutable.PreviewMinimum
)
)
{
Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250);
}
@@ -180,9 +219,11 @@ internal sealed class Chat : ISettingsTab
ImGui.TextUnformatted(Language.Options_Emote_BlockedEmotes);
ImGui.Spacing();
if (EmoteCache.State is EmoteCache.LoadingState.Done
if (
EmoteCache.State is EmoteCache.LoadingState.Done
&& WordPopupOptions.FilteredSheet.Length == 0
&& WordPopupOptionsBuiltFor != EmoteCache.LoadingState.Done)
&& WordPopupOptionsBuiltFor != EmoteCache.LoadingState.Done
)
{
WordPopupOptions = RefillSheet();
WordPopupOptionsBuiltFor = EmoteCache.LoadingState.Done;
@@ -204,7 +245,13 @@ internal sealed class Chat : ISettingsTab
Mutable.BlockedEmotes.Add(newWord);
}
using (var table = ImRaii.Table("##BlockedWords", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
using (
var table = ImRaii.Table(
"##BlockedWords",
2,
ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner
)
)
{
if (table)
{
@@ -220,7 +267,13 @@ internal sealed class Chat : ISettingsTab
ImGui.TextUnformatted(word);
ImGui.TableNextColumn();
if (ImGuiUtil.Button($"##{word}Del", FontAwesomeIcon.Trash, !ImGui.GetIO().KeyCtrl))
if (
ImGuiUtil.Button(
$"##{word}Del",
FontAwesomeIcon.Trash,
!ImGui.GetIO().KeyCtrl
)
)
{
Mutable.BlockedEmotes.Remove(word);
}
@@ -244,8 +297,16 @@ internal sealed class Chat : ISettingsTab
ImGui.TextColored(ImGuiColors.DPSRed, Language.Options_Emote_NotReady);
}
ImGui.TextUnformatted($"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}");
using (var emoteTable = ImRaii.Table("##LoadedEmotes", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
ImGui.TextUnformatted(
$"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}"
);
using (
var emoteTable = ImRaii.Table(
"##LoadedEmotes",
5,
ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner
)
)
{
if (emoteTable)
{
+428 -174
View File
@@ -1,16 +1,16 @@
using System.Diagnostics;
using HellionChat.Code;
using HellionChat.Export;
using HellionChat.Privacy;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.Export;
using HellionChat.Privacy;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui.SettingsTabs;
@@ -19,7 +19,8 @@ internal sealed class DataManagement : ISettingsTab
private Plugin Plugin { get; }
private Configuration Mutable { get; }
public string Name => HellionStrings.Settings_Card_DataManagement_Title + "###tabs-datamanagement";
public string Name =>
HellionStrings.Settings_Card_DataManagement_Title + "###tabs-datamanagement";
// Cleanup state (was in Privacy.cs)
private Dictionary<int, long>? CleanupCounts;
@@ -49,28 +50,90 @@ internal sealed class DataManagement : ISettingsTab
// call updates the labels immediately. 1:1 from Privacy.cs Groups.
private static readonly (Func<string> Heading, ChatType[] Types)[] Groups =
[
(() => HellionStrings.Privacy_Group_DirectMessages, [ChatType.TellIncoming, ChatType.TellOutgoing]),
(() => HellionStrings.Privacy_Group_PartyAlliance, [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]),
(() => HellionStrings.Privacy_Group_FreeCompany, [ChatType.FreeCompany, ChatType.FreeCompanyAnnouncement, ChatType.FreeCompanyLoginLogout]),
(() => HellionStrings.Privacy_Group_Linkshells, [
ChatType.Linkshell1, ChatType.Linkshell2, ChatType.Linkshell3, ChatType.Linkshell4,
ChatType.Linkshell5, ChatType.Linkshell6, ChatType.Linkshell7, ChatType.Linkshell8,
]),
(() => HellionStrings.Privacy_Group_CrossLinkshells, [
ChatType.CrossLinkshell1, ChatType.CrossLinkshell2, ChatType.CrossLinkshell3, ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5, ChatType.CrossLinkshell6, ChatType.CrossLinkshell7, ChatType.CrossLinkshell8,
]),
(() => HellionStrings.Privacy_Group_ExtraChat, [
ChatType.ExtraChatLinkshell1, ChatType.ExtraChatLinkshell2, ChatType.ExtraChatLinkshell3, ChatType.ExtraChatLinkshell4,
ChatType.ExtraChatLinkshell5, ChatType.ExtraChatLinkshell6, ChatType.ExtraChatLinkshell7, ChatType.ExtraChatLinkshell8,
]),
(() => HellionStrings.Privacy_Group_PublicChat, [ChatType.Say, ChatType.Shout, ChatType.Yell, ChatType.NoviceNetwork, ChatType.CustomEmote, ChatType.StandardEmote]),
(() => HellionStrings.Privacy_Group_SystemLogs, [
ChatType.System, ChatType.Notice, ChatType.Urgent, ChatType.Echo,
ChatType.NpcDialogue, ChatType.NpcAnnouncement,
ChatType.LootNotice, ChatType.LootRoll, ChatType.RetainerSale,
ChatType.Crafting, ChatType.Gathering, ChatType.Sign, ChatType.RandomNumber,
]),
(
() => HellionStrings.Privacy_Group_DirectMessages,
[ChatType.TellIncoming, ChatType.TellOutgoing]
),
(
() => HellionStrings.Privacy_Group_PartyAlliance,
[ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]
),
(
() => HellionStrings.Privacy_Group_FreeCompany,
[
ChatType.FreeCompany,
ChatType.FreeCompanyAnnouncement,
ChatType.FreeCompanyLoginLogout,
]
),
(
() => HellionStrings.Privacy_Group_Linkshells,
[
ChatType.Linkshell1,
ChatType.Linkshell2,
ChatType.Linkshell3,
ChatType.Linkshell4,
ChatType.Linkshell5,
ChatType.Linkshell6,
ChatType.Linkshell7,
ChatType.Linkshell8,
]
),
(
() => HellionStrings.Privacy_Group_CrossLinkshells,
[
ChatType.CrossLinkshell1,
ChatType.CrossLinkshell2,
ChatType.CrossLinkshell3,
ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5,
ChatType.CrossLinkshell6,
ChatType.CrossLinkshell7,
ChatType.CrossLinkshell8,
]
),
(
() => HellionStrings.Privacy_Group_ExtraChat,
[
ChatType.ExtraChatLinkshell1,
ChatType.ExtraChatLinkshell2,
ChatType.ExtraChatLinkshell3,
ChatType.ExtraChatLinkshell4,
ChatType.ExtraChatLinkshell5,
ChatType.ExtraChatLinkshell6,
ChatType.ExtraChatLinkshell7,
ChatType.ExtraChatLinkshell8,
]
),
(
() => HellionStrings.Privacy_Group_PublicChat,
[
ChatType.Say,
ChatType.Shout,
ChatType.Yell,
ChatType.NoviceNetwork,
ChatType.CustomEmote,
ChatType.StandardEmote,
]
),
(
() => HellionStrings.Privacy_Group_SystemLogs,
[
ChatType.System,
ChatType.Notice,
ChatType.Urgent,
ChatType.Echo,
ChatType.NpcDialogue,
ChatType.NpcAnnouncement,
ChatType.LootNotice,
ChatType.LootRoll,
ChatType.RetainerSale,
ChatType.Crafting,
ChatType.Gathering,
ChatType.Sign,
ChatType.RandomNumber,
]
),
];
internal DataManagement(Plugin plugin, Configuration mutable)
@@ -107,21 +170,36 @@ internal sealed class DataManagement : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
ImGui.Checkbox(Language.Options_DatabaseBattleMessages_Name, ref Mutable.DatabaseBattleMessages);
ImGui.Checkbox(
Language.Options_DatabaseBattleMessages_Name,
ref Mutable.DatabaseBattleMessages
);
ImGuiUtil.HelpMarker(Language.Options_DatabaseBattleMessages_Description);
if (ImGui.Checkbox(Language.Options_LoadPreviousSession_Name, ref Mutable.LoadPreviousSession))
if (
ImGui.Checkbox(
Language.Options_LoadPreviousSession_Name,
ref Mutable.LoadPreviousSession
)
)
if (Mutable.LoadPreviousSession)
Mutable.FilterIncludePreviousSessions = true;
ImGuiUtil.HelpMarker(Language.Options_LoadPreviousSession_Description);
if (ImGui.Checkbox(Language.Options_FilterIncludePreviousSessions_Name, ref Mutable.FilterIncludePreviousSessions))
if (
ImGui.Checkbox(
Language.Options_FilterIncludePreviousSessions_Name,
ref Mutable.FilterIncludePreviousSessions
)
)
if (!Mutable.FilterIncludePreviousSessions)
Mutable.LoadPreviousSession = false;
ImGuiUtil.HelpMarker(Language.Options_FilterIncludePreviousSessions_Description);
var old = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat.db"));
var migratedOld = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-litedb.db"));
var migratedOld = new FileInfo(
Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-litedb.db")
);
if (old.Exists || migratedOld.Exists)
{
ImGui.Spacing();
@@ -131,7 +209,12 @@ internal sealed class DataManagement : ISettingsTab
ImGui.TextUnformatted(Language.Options_Database_Old_Heading);
ImGui.Spacing();
if (ImGuiUtil.CtrlShiftButton(Language.Options_Database_Old_Delete, Language.Options_Database_Old_Delete_Tooltip))
if (
ImGuiUtil.CtrlShiftButton(
Language.Options_Database_Old_Delete,
Language.Options_Database_Old_Delete_Tooltip
)
)
{
try
{
@@ -139,12 +222,18 @@ internal sealed class DataManagement : ISettingsTab
old.Delete();
if (migratedOld.Exists)
migratedOld.Delete();
WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Success, NotificationType.Success);
WrapperUtil.AddNotification(
Language.Options_Database_Old_Delete_Success,
NotificationType.Success
);
}
catch (Exception e)
{
Plugin.Log.Error(e, "Unable to delete old database");
WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Error, NotificationType.Error);
WrapperUtil.AddNotification(
Language.Options_Database_Old_Delete_Error,
NotificationType.Error
);
}
}
}
@@ -162,7 +251,8 @@ internal sealed class DataManagement : ISettingsTab
ImGuiUtil.OptionCheckbox(
ref Mutable.RetentionEnabled,
HellionStrings.Retention_Enabled_Name,
HellionStrings.Retention_Enabled_Description);
HellionStrings.Retention_Enabled_Description
);
using (ImRaii.Disabled(!Mutable.RetentionEnabled))
{
@@ -191,39 +281,57 @@ internal sealed class DataManagement : ISettingsTab
if (perChannelTree.Success)
{
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
foreach (var (heading, types) in Groups)
{
using var subTree = ImRaii.TreeNode(heading());
if (!subTree.Success)
continue;
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
foreach (var type in types)
foreach (var (heading, types) in Groups)
{
var hasOverride = Mutable.RetentionPerChannelDays.TryGetValue(type, out var days);
var hasSpecDefault = PrivacyDefaults.DefaultRetentionDays.TryGetValue(type, out var specDays);
if (!hasOverride)
days = hasSpecDefault ? specDays : Mutable.RetentionDefaultDays;
using var subTree = ImRaii.TreeNode(heading());
if (!subTree.Success)
continue;
var tag = hasOverride
? HellionStrings.Retention_Tag_Override
: hasSpecDefault
? HellionStrings.Retention_Tag_Spec
: HellionStrings.Retention_Tag_Global;
if (ImGui.InputInt($"{type} {tag}##retention-{(int)type}", ref days))
{
days = Math.Max(0, days);
Mutable.RetentionPerChannelDays[type] = days;
}
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
foreach (var type in types)
{
var hasOverride =
Mutable.RetentionPerChannelDays.TryGetValue(
type,
out var days
);
var hasSpecDefault =
PrivacyDefaults.DefaultRetentionDays.TryGetValue(
type,
out var specDays
);
if (!hasOverride)
days = hasSpecDefault
? specDays
: Mutable.RetentionDefaultDays;
if (hasOverride)
{
ImGui.SameLine();
if (ImGui.Button($"{HellionStrings.Retention_Reset_Button}##retention-reset-{(int)type}"))
Mutable.RetentionPerChannelDays.Remove(type);
}
var tag =
hasOverride ? HellionStrings.Retention_Tag_Override
: hasSpecDefault ? HellionStrings.Retention_Tag_Spec
: HellionStrings.Retention_Tag_Global;
if (
ImGui.InputInt(
$"{type} {tag}##retention-{(int)type}",
ref days
)
)
{
days = Math.Max(0, days);
Mutable.RetentionPerChannelDays[type] = days;
}
if (hasOverride)
{
ImGui.SameLine();
if (
ImGui.Button(
$"{HellionStrings.Retention_Reset_Button}##retention-reset-{(int)type}"
)
)
Mutable.RetentionPerChannelDays.Remove(type);
}
}
}
}
}
}
@@ -234,7 +342,12 @@ internal sealed class DataManagement : ISettingsTab
using (ImRaii.Disabled(RetentionRunning))
{
if (ImGuiUtil.CtrlShiftButton(HellionStrings.Retention_Apply_Label, HellionStrings.Retention_Apply_Tooltip))
if (
ImGuiUtil.CtrlShiftButton(
HellionStrings.Retention_Apply_Label,
HellionStrings.Retention_Apply_Tooltip
)
)
StartRetentionRun();
}
@@ -243,9 +356,11 @@ internal sealed class DataManagement : ISettingsTab
ImGui.Spacing();
var lastRun = Plugin.Config.RetentionLastRunAt;
ImGuiUtil.HelpText(lastRun == DateTimeOffset.MinValue
? HellionStrings.Retention_LastRun_Never
: string.Format(HellionStrings.Retention_LastRun_At, lastRun.ToLocalTime()));
ImGuiUtil.HelpText(
lastRun == DateTimeOffset.MinValue
? HellionStrings.Retention_LastRun_Never
: string.Format(HellionStrings.Retention_LastRun_At, lastRun.ToLocalTime())
);
}
}
}
@@ -259,14 +374,20 @@ internal sealed class DataManagement : ISettingsTab
Plugin.RetentionSweepRunning = true;
}
var policy = Plugin.Config.RetentionPerChannelDays.ToDictionary(p => (int)(ushort)p.Key, p => p.Value);
var policy = Plugin.Config.RetentionPerChannelDays.ToDictionary(
p => (int)(ushort)p.Key,
p => p.Value
);
var defaultDays = Plugin.Config.RetentionDefaultDays;
new Thread(() =>
{
try
{
var deleted = Plugin.MessageManager.Store.DeleteByRetentionPolicy(policy, defaultDays);
var deleted = Plugin.MessageManager.Store.DeleteByRetentionPolicy(
policy,
defaultDays
);
Plugin.Config.RetentionLastRunAt = DateTimeOffset.UtcNow;
Plugin.SaveConfig();
@@ -274,17 +395,26 @@ internal sealed class DataManagement : ISettingsTab
if (deleted > 0)
{
if (!Plugin.Framework.Run(() =>
{
Plugin.MessageManager.ClearAllTabs();
Plugin.MessageManager.FilterAllTabsAsync();
}).Wait(TimeSpan.FromSeconds(5)))
if (
!Plugin
.Framework.Run(() =>
{
Plugin.MessageManager.ClearAllTabs();
Plugin.MessageManager.FilterAllTabsAsync();
})
.Wait(TimeSpan.FromSeconds(5))
)
{
Plugin.Log.Warning("Retention sweep: framework refresh timed out after 5s.");
Plugin.Log.Warning(
"Retention sweep: framework refresh timed out after 5s."
);
}
}
WrapperUtil.AddNotification(string.Format(HellionStrings.Retention_Success, deleted), NotificationType.Success);
WrapperUtil.AddNotification(
string.Format(HellionStrings.Retention_Success, deleted),
NotificationType.Success
);
}
catch (Exception e)
{
@@ -296,7 +426,10 @@ internal sealed class DataManagement : ISettingsTab
lock (Plugin.RetentionSweepLock)
Plugin.RetentionSweepRunning = false;
}
}) { IsBackground = true }.Start();
})
{
IsBackground = true,
}.Start();
}
private void DrawCleanupSection()
@@ -312,15 +445,19 @@ internal sealed class DataManagement : ISettingsTab
ImGui.Spacing();
if (CleanupPreviewSnapshot is not null
&& !CleanupPreviewSnapshot.SetEquals(Mutable.PrivacyPersistChannels))
if (
CleanupPreviewSnapshot is not null
&& !CleanupPreviewSnapshot.SetEquals(Mutable.PrivacyPersistChannels)
)
{
CleanupPreviewStale = true;
}
using (var emphasis = CleanupPreviewStale
? ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.6f })
: null)
using (
var emphasis = CleanupPreviewStale
? ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.6f })
: null
)
using (ImRaii.Disabled(CleanupRunning))
{
if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview))
@@ -341,13 +478,24 @@ internal sealed class DataManagement : ISettingsTab
ImGui.Spacing();
using (var staleColor = CleanupPreviewStale
? ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)
: null)
using (
var staleColor = CleanupPreviewStale
? ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)
: null
)
{
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount));
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount));
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount));
ImGuiUtil.HelpText(
string.Format(
HellionStrings.Cleanup_TotalStored,
CleanupKeepCount + CleanupDeleteCount
)
);
ImGuiUtil.HelpText(
string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount)
);
ImGuiUtil.HelpText(
string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount)
);
}
using (var breakdownTree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown))
@@ -355,15 +503,19 @@ internal sealed class DataManagement : ISettingsTab
if (breakdownTree.Success)
{
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
foreach (var (chatType, count) in CleanupCounts.OrderByDescending(p => p.Value))
{
var name = Enum.IsDefined(typeof(ChatType), (ushort)chatType)
? ((ChatType)(ushort)chatType).ToString()
: $"Unknown({chatType})";
var keeps = WouldBeKept(chatType);
var marker = keeps ? HellionStrings.Cleanup_Marker_Keep : HellionStrings.Cleanup_Marker_Delete;
ImGuiUtil.HelpText($"{marker} {name} — {count:N0}");
}
foreach (
var (chatType, count) in CleanupCounts.OrderByDescending(p => p.Value)
)
{
var name = Enum.IsDefined(typeof(ChatType), (ushort)chatType)
? ((ChatType)(ushort)chatType).ToString()
: $"Unknown({chatType})";
var keeps = WouldBeKept(chatType);
var marker = keeps
? HellionStrings.Cleanup_Marker_Keep
: HellionStrings.Cleanup_Marker_Delete;
ImGuiUtil.HelpText($"{marker} {name} — {count:N0}");
}
}
}
@@ -371,8 +523,12 @@ internal sealed class DataManagement : ISettingsTab
using (ImRaii.Disabled(CleanupRunning || CleanupDeleteCount == 0))
{
if (ImGuiUtil.CtrlShiftButton(HellionStrings.Cleanup_Apply_Label,
string.Format(HellionStrings.Cleanup_Apply_Tooltip, CleanupDeleteCount)))
if (
ImGuiUtil.CtrlShiftButton(
HellionStrings.Cleanup_Apply_Label,
string.Format(HellionStrings.Cleanup_Apply_Tooltip, CleanupDeleteCount)
)
)
StartCleanup();
}
@@ -411,7 +567,10 @@ internal sealed class DataManagement : ISettingsTab
catch (Exception e)
{
Plugin.Log.Error(e, "Failed to compute cleanup preview");
WrapperUtil.AddNotification(HellionStrings.Cleanup_PreviewError, NotificationType.Error);
WrapperUtil.AddNotification(
HellionStrings.Cleanup_PreviewError,
NotificationType.Error
);
}
}
@@ -430,16 +589,23 @@ internal sealed class DataManagement : ISettingsTab
var deleted = Plugin.MessageManager.Store.CleanupRetainOnly(allowed);
Plugin.Log.Information($"Privacy cleanup: deleted {deleted} messages");
if (!Plugin.Framework.Run(() =>
{
Plugin.MessageManager.ClearAllTabs();
Plugin.MessageManager.FilterAllTabs();
}).Wait(TimeSpan.FromSeconds(5)))
if (
!Plugin
.Framework.Run(() =>
{
Plugin.MessageManager.ClearAllTabs();
Plugin.MessageManager.FilterAllTabs();
})
.Wait(TimeSpan.FromSeconds(5))
)
{
Plugin.Log.Warning("Privacy cleanup: framework refresh timed out after 5s.");
}
WrapperUtil.AddNotification(string.Format(HellionStrings.Cleanup_Success, deleted), NotificationType.Success);
WrapperUtil.AddNotification(
string.Format(HellionStrings.Cleanup_Success, deleted),
NotificationType.Success
);
}
catch (Exception e)
{
@@ -482,22 +648,24 @@ internal sealed class DataManagement : ISettingsTab
ImGuiUtil.HelpText(HellionStrings.Export_Channels_AllOff);
foreach (var (heading, types) in Groups)
{
using var subTree = ImRaii.TreeNode($"{heading()}##export-group-{heading()}");
using var subTree = ImRaii.TreeNode(
$"{heading()}##export-group-{heading()}"
);
if (!subTree.Success)
continue;
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
foreach (var type in types)
{
var enabled = ExportSelectedChannels.Contains(type);
if (ImGui.Checkbox($"{type}##export-{(int)type}", ref enabled))
foreach (var type in types)
{
if (enabled)
ExportSelectedChannels.Add(type);
else
ExportSelectedChannels.Remove(type);
var enabled = ExportSelectedChannels.Contains(type);
if (ImGui.Checkbox($"{type}##export-{(int)type}", ref enabled))
{
if (enabled)
ExportSelectedChannels.Add(type);
else
ExportSelectedChannels.Remove(type);
}
}
}
}
}
}
@@ -507,10 +675,22 @@ internal sealed class DataManagement : ISettingsTab
ImGui.TextUnformatted(HellionStrings.Export_Format_Label);
ImGui.SameLine();
var fmt = (int)ExportFormat;
if (ImGui.RadioButton(HellionStrings.Export_Format_Markdown, ref fmt, (int)ExportFormat.Markdown))
if (
ImGui.RadioButton(
HellionStrings.Export_Format_Markdown,
ref fmt,
(int)ExportFormat.Markdown
)
)
ExportFormat = ExportFormat.Markdown;
ImGui.SameLine();
if (ImGui.RadioButton(HellionStrings.Export_Format_Json, ref fmt, (int)ExportFormat.Json))
if (
ImGui.RadioButton(
HellionStrings.Export_Format_Json,
ref fmt,
(int)ExportFormat.Json
)
)
ExportFormat = ExportFormat.Json;
ImGui.SameLine();
if (ImGui.RadioButton(HellionStrings.Export_Format_Csv, ref fmt, (int)ExportFormat.Csv))
@@ -544,7 +724,8 @@ internal sealed class DataManagement : ISettingsTab
if (!success || string.IsNullOrWhiteSpace(path))
return;
StartExport(path);
});
}
);
}
private void StartExport(string path)
@@ -553,15 +734,17 @@ internal sealed class DataManagement : ISettingsTab
return;
ExportRunning = true;
var types = ExportSelectedChannels.Count > 0
? ExportSelectedChannels.Select(t => (int)(ushort)t).ToList()
: null;
var types =
ExportSelectedChannels.Count > 0
? ExportSelectedChannels.Select(t => (int)(ushort)t).ToList()
: null;
DateTimeOffset? from = ExportRangeDays > 0
? DateTimeOffset.UtcNow.AddDays(-ExportRangeDays)
: null;
DateTimeOffset? from =
ExportRangeDays > 0 ? DateTimeOffset.UtcNow.AddDays(-ExportRangeDays) : null;
var senderSubstring = string.IsNullOrWhiteSpace(ExportSenderSubstring) ? null : ExportSenderSubstring.Trim();
var senderSubstring = string.IsNullOrWhiteSpace(ExportSenderSubstring)
? null
: ExportSenderSubstring.Trim();
var format = ExportFormat;
var filterDesc = new MessageExporter.FilterDescription(types, from, null, senderSubstring);
@@ -569,11 +752,18 @@ internal sealed class DataManagement : ISettingsTab
{
try
{
using var enumerator = Plugin.MessageManager.Store.StreamForExport(types, from, null);
using var enumerator = Plugin.MessageManager.Store.StreamForExport(
types,
from,
null
);
var written = MessageExporter.ExportToFile(path, format, enumerator, filterDesc);
if (written > 0)
WrapperUtil.AddNotification(string.Format(HellionStrings.Export_Success, written, path), NotificationType.Success);
WrapperUtil.AddNotification(
string.Format(HellionStrings.Export_Success, written, path),
NotificationType.Success
);
else
WrapperUtil.AddNotification(HellionStrings.Export_Empty, NotificationType.Info);
}
@@ -586,7 +776,10 @@ internal sealed class DataManagement : ISettingsTab
{
ExportRunning = false;
}
}) { IsBackground = true }.Start();
})
{
IsBackground = true,
}.Start();
}
private void DrawDatabaseViewerSection()
@@ -605,12 +798,20 @@ internal sealed class DataManagement : ISettingsTab
DatabaseLastRefreshTicks = Environment.TickCount64;
}
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Path, MessageManager.DatabasePath()));
ImGuiUtil.HelpText(
string.Format(
Language.Options_Database_Metadata_Path,
MessageManager.DatabasePath()
)
);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
var path = Path.GetDirectoryName(MessageManager.DatabasePath());
ImGui.SetClipboardText(path);
WrapperUtil.AddNotification(Language.Options_Database_Metadata_CopyConfigPathNotification, NotificationType.Info);
WrapperUtil.AddNotification(
Language.Options_Database_Metadata_CopyConfigPathNotification,
NotificationType.Info
);
}
if (ImGui.IsItemHovered())
@@ -619,24 +820,44 @@ internal sealed class DataManagement : ISettingsTab
ImGuiUtil.Tooltip(Language.Options_Database_Metadata_CopyConfigPath);
}
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Size, StringUtil.BytesToString(DatabaseSize)));
ImGuiUtil.HelpText(
string.Format(
Language.Options_Database_Metadata_Size,
StringUtil.BytesToString(DatabaseSize)
)
);
if (ImGui.IsItemHovered())
ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseSize));
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_LogSize, StringUtil.BytesToString(DatabaseLogSize)));
ImGuiUtil.HelpText(
string.Format(
Language.Options_Database_Metadata_LogSize,
StringUtil.BytesToString(DatabaseLogSize)
)
);
if (ImGui.IsItemHovered())
ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseLogSize));
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_MessageCount, DatabaseMessageCount));
ImGuiUtil.HelpText(
string.Format(Language.Options_Database_Metadata_MessageCount, DatabaseMessageCount)
);
if (ImGuiUtil.CtrlShiftButton(Language.Options_ClearDatabase_Button, Language.Options_ClearDatabase_Tooltip))
if (
ImGuiUtil.CtrlShiftButton(
Language.Options_ClearDatabase_Button,
Language.Options_ClearDatabase_Tooltip
)
)
{
Plugin.Log.Warning("Clearing messages from database");
Plugin.MessageManager.Store.ClearMessages();
Plugin.MessageManager.ClearAllTabs();
DatabaseLastRefreshTicks = 0;
WrapperUtil.AddNotification(Language.Options_ClearDatabase_Success, NotificationType.Info);
WrapperUtil.AddNotification(
Language.Options_ClearDatabase_Success,
NotificationType.Info
);
}
}
}
@@ -655,16 +876,31 @@ internal sealed class DataManagement : ISettingsTab
using var wrap = ImRaii.TextWrapPos(0.0f);
ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning);
if (ImGuiUtil.CtrlShiftButton("Perform maintenance", "Ctrl+Shift: MessageManager.Store.PerformMaintenance()"))
if (
ImGuiUtil.CtrlShiftButton(
"Perform maintenance",
"Ctrl+Shift: MessageManager.Store.PerformMaintenance()"
)
)
Plugin.MessageManager.Store.PerformMaintenance();
if (ImGuiUtil.CtrlShiftButton("Reload messages from database", "Ctrl+Shift: MessageManager.FilterAllTabs()"))
if (
ImGuiUtil.CtrlShiftButton(
"Reload messages from database",
"Ctrl+Shift: MessageManager.FilterAllTabs()"
)
)
{
Plugin.MessageManager.ClearAllTabs();
Plugin.MessageManager.FilterAllTabsAsync();
}
if (ImGuiUtil.CtrlShiftButton("Inject 10,000 messages", "Ctrl+Shift: creates 10,000 unique messages (async)"))
if (
ImGuiUtil.CtrlShiftButton(
"Inject 10,000 messages",
"Ctrl+Shift: creates 10,000 unique messages (async)"
)
)
new Thread(() => InsertMessages(10_000)).Start();
}
}
@@ -683,7 +919,9 @@ internal sealed class DataManagement : ISettingsTab
.Add(RawPayload.LinkTerminator)
.AddText(">: ")
.Build();
var senderChunks = ChunkUtil.ToChunks(senderSource, ChunkSource.Sender, ChatType.Debug).ToList();
var senderChunks = ChunkUtil
.ToChunks(senderSource, ChunkSource.Sender, ChatType.Debug)
.ToList();
var messages = new List<Message>(count);
for (var i = 0; i < count; i++)
{
@@ -691,26 +929,32 @@ internal sealed class DataManagement : ISettingsTab
.AddText("Random message payload - ")
.AddItalics(Guid.NewGuid().ToString())
.Build();
var contentChunks = ChunkUtil.ToChunks(contentSource, ChunkSource.Content, ChatType.Debug).ToList();
var contentChunks = ChunkUtil
.ToChunks(contentSource, ChunkSource.Content, ChatType.Debug)
.ToList();
var chatCode = new ChatCode(XivChatType.Say, 0, 0);
messages.Add(new Message(
Guid.NewGuid(),
Plugin.MessageManager.CurrentContentId,
Plugin.MessageManager.CurrentContentId,
DateTimeOffset.UtcNow,
chatCode,
senderChunks,
contentChunks,
senderSource,
contentSource,
Guid.Empty
));
messages.Add(
new Message(
Guid.NewGuid(),
Plugin.MessageManager.CurrentContentId,
Plugin.MessageManager.CurrentContentId,
DateTimeOffset.UtcNow,
chatCode,
senderChunks,
contentChunks,
senderSource,
contentSource,
Guid.Empty
)
);
}
var elapsedTicks = stopwatch.ElapsedTicks;
stopwatch.Stop();
Plugin.Log.Info($"Crafted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
Plugin.Log.Info(
$"Crafted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
);
stopwatch = Stopwatch.StartNew();
foreach (var message in messages)
@@ -718,24 +962,34 @@ internal sealed class DataManagement : ISettingsTab
elapsedTicks = stopwatch.ElapsedTicks;
stopwatch.Stop();
Plugin.Log.Info($"Upserted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
Plugin.Log.Info(
$"Upserted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
);
Plugin.Framework.Run(() =>
{
stopwatch = Stopwatch.StartNew();
Plugin.MessageManager.ClearAllTabs();
elapsedTicks = stopwatch.ElapsedTicks;
stopwatch.Stop();
Plugin.Log.Info($"Cleared {Plugin.Config.Tabs.Count} tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
}).Wait();
Plugin
.Framework.Run(() =>
{
stopwatch = Stopwatch.StartNew();
Plugin.MessageManager.ClearAllTabs();
elapsedTicks = stopwatch.ElapsedTicks;
stopwatch.Stop();
Plugin.Log.Info(
$"Cleared {Plugin.Config.Tabs.Count} tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
);
})
.Wait();
Plugin.Framework.Run(() =>
{
stopwatch = Stopwatch.StartNew();
Plugin.MessageManager.FilterAllTabs();
elapsedTicks = stopwatch.ElapsedTicks;
stopwatch.Stop();
Plugin.Log.Info($"Fetched and filtered all tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
}).Wait();
Plugin
.Framework.Run(() =>
{
stopwatch = Stopwatch.StartNew();
Plugin.MessageManager.FilterAllTabs();
elapsedTicks = stopwatch.ElapsedTicks;
stopwatch.Stop();
Plugin.Log.Info(
$"Fetched and filtered all tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
);
})
.Wait();
}
}
+94 -24
View File
@@ -1,12 +1,12 @@
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui.SettingsTabs;
@@ -15,7 +15,8 @@ internal sealed class FontsAndColours : ISettingsTab
private Plugin Plugin { get; }
private Configuration Mutable { get; }
public string Name => HellionStrings.Settings_Card_FontsAndColours_Title + "###tabs-fontsandcolours";
public string Name =>
HellionStrings.Settings_Card_FontsAndColours_Title + "###tabs-fontsandcolours";
internal FontsAndColours(Plugin plugin, Configuration mutable)
{
@@ -38,7 +39,9 @@ internal sealed class FontsAndColours : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
if (ImGui.Checkbox(HellionStrings.Theme_UseHellionFont_Name, ref Mutable.UseHellionFont))
if (
ImGui.Checkbox(HellionStrings.Theme_UseHellionFont_Name, ref Mutable.UseHellionFont)
)
{
if (Mutable.UseHellionFont)
Mutable.FontsEnabled = false;
@@ -51,7 +54,10 @@ internal sealed class FontsAndColours : ISettingsTab
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
ImGui.Spacing();
ImGuiUtil.FontSizeCombo(Language.Options_SymbolsFontSize_Name, ref Mutable.SymbolsFontSizeV2);
ImGuiUtil.FontSizeCombo(
Language.Options_SymbolsFontSize_Name,
ref Mutable.SymbolsFontSizeV2
);
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
ImGui.Spacing();
@@ -68,7 +74,12 @@ internal sealed class FontsAndColours : ISettingsTab
}
else
{
var globalChooser = ImGuiUtil.FontChooser(Language.Options_Font_Name, Mutable.GlobalFontV2, false, ref unused);
var globalChooser = ImGuiUtil.FontChooser(
Language.Options_Font_Name,
Mutable.GlobalFontV2,
false,
ref unused
);
globalChooser?.ResultTask.ContinueWith(r =>
{
if (r.IsCompletedSuccessfully)
@@ -79,14 +90,27 @@ internal sealed class FontsAndColours : ISettingsTab
ImGui.SameLine();
if (ImGui.Button("Reset##global"))
{
Mutable.GlobalFontV2 = new SingleFontSpec { FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
Mutable.GlobalFontV2 = new SingleFontSpec
{
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
SizePt = 12.75f,
};
}
ImGuiUtil.HelpMarker(string.Format(Language.Options_Font_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_Font_Description, Plugin.PluginName)
);
ImGuiUtil.WarningText(Language.Options_Font_Warning);
ImGui.Spacing();
var japaneseChooser = ImGuiUtil.FontChooser(Language.Options_JapaneseFont_Name, Mutable.JapaneseFontV2, false, ref unused, id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false, "いろはにほへと ちりぬるを");
var japaneseChooser = ImGuiUtil.FontChooser(
Language.Options_JapaneseFont_Name,
Mutable.JapaneseFontV2,
false,
ref unused,
id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false,
"いろはにほへと ちりぬるを"
);
japaneseChooser?.ResultTask.ContinueWith(r =>
{
if (r.IsCompletedSuccessfully)
@@ -97,13 +121,24 @@ internal sealed class FontsAndColours : ISettingsTab
ImGui.SameLine();
if (ImGui.Button("Reset##japanese"))
{
Mutable.JapaneseFontV2 = new SingleFontSpec { FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium), SizePt = 12.75f };
Mutable.JapaneseFontV2 = new SingleFontSpec
{
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium),
SizePt = 12.75f,
};
}
ImGuiUtil.HelpMarker(string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName)
);
ImGui.Spacing();
var italicChooser = ImGuiUtil.FontChooser(Language.Options_ItalicFont_Name, Mutable.ItalicFontV2, true, ref Mutable.ItalicEnabled);
var italicChooser = ImGuiUtil.FontChooser(
Language.Options_ItalicFont_Name,
Mutable.ItalicFontV2,
true,
ref Mutable.ItalicEnabled
);
italicChooser?.ResultTask.ContinueWith(r =>
{
if (r.IsCompletedSuccessfully)
@@ -115,15 +150,23 @@ internal sealed class FontsAndColours : ISettingsTab
if (ImGui.Button("Reset##italic"))
{
Mutable.ItalicEnabled = false;
Mutable.ItalicFontV2 = new SingleFontSpec { FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
Mutable.ItalicFontV2 = new SingleFontSpec
{
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
SizePt = 12.75f,
};
}
ImGuiUtil.HelpMarker(string.Format(Language.Options_Italic_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_Italic_Description, Plugin.PluginName)
);
ImGui.Spacing();
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
{
ImGuiUtil.HelpMarker(string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName)
);
var range = (int)Mutable.ExtraGlyphRanges;
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
@@ -137,7 +180,10 @@ internal sealed class FontsAndColours : ISettingsTab
ImGui.Spacing();
}
ImGuiUtil.FontSizeCombo(Language.Options_SymbolsFontSize_Name, ref Mutable.SymbolsFontSizeV2);
ImGuiUtil.FontSizeCombo(
Language.Options_SymbolsFontSize_Name,
ref Mutable.SymbolsFontSizeV2
);
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
ImGui.Spacing();
@@ -158,7 +204,10 @@ internal sealed class FontsAndColours : ISettingsTab
ImGui.Separator();
ImGui.Spacing();
ImGui.Checkbox(Language.Options_ColorSelectedInputChannelButton_Name, ref Mutable.ColorSelectedInputChannelButton);
ImGui.Checkbox(
Language.Options_ColorSelectedInputChannelButton_Name,
ref Mutable.ColorSelectedInputChannelButton
);
ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description);
ImGui.Spacing();
@@ -166,14 +215,26 @@ internal sealed class FontsAndColours : ISettingsTab
{
foreach (var type in types)
{
if (ImGuiUtil.IconButton(FontAwesomeIcon.UndoAlt, $"{type}", Language.Options_ChatColours_Reset))
if (
ImGuiUtil.IconButton(
FontAwesomeIcon.UndoAlt,
$"{type}",
Language.Options_ChatColours_Reset
)
)
{
Mutable.ChatColours.Remove(type);
}
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.LongArrowAltDown, $"{type}", Language.Options_ChatColours_Import))
if (
ImGuiUtil.IconButton(
FontAwesomeIcon.LongArrowAltDown,
$"{type}",
Language.Options_ChatColours_Import
)
)
{
var gameColour = Plugin.Functions.Chat.GetChannelColor(type);
Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
@@ -210,8 +271,14 @@ internal sealed class FontsAndColours : ISettingsTab
{
var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200));
var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106));
ImGui.PushStyleColor(ImGuiCol.Border, new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f));
ImGui.PushStyleColor(ImGuiCol.Button, new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f));
ImGui.PushStyleColor(
ImGuiCol.Border,
new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f)
);
ImGui.PushStyleColor(
ImGuiCol.Button,
new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f)
);
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f);
}
@@ -230,7 +297,10 @@ internal sealed class FontsAndColours : ISettingsTab
private static string GetPresetLabel(ChatColourPreset preset)
{
var localized = HellionStrings.ResourceManager.GetString(preset.LocalizationKey, HellionStrings.Culture);
var localized = HellionStrings.ResourceManager.GetString(
preset.LocalizationKey,
HellionStrings.Culture
);
return string.IsNullOrEmpty(localized) ? preset.DisplayName : localized;
}
+30 -9
View File
@@ -1,8 +1,8 @@
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui.SettingsTabs;
@@ -54,7 +54,12 @@ internal sealed class General : ISettingsTab
ImGui.Spacing();
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_KeybindMode_Name, Mutable.KeybindMode.Name()))
using (
var combo = ImGuiUtil.BeginComboVertical(
Language.Options_KeybindMode_Name,
Mutable.KeybindMode.Name()
)
)
{
if (combo.Success)
{
@@ -72,7 +77,9 @@ internal sealed class General : ISettingsTab
}
}
}
ImGuiUtil.HelpMarker(string.Format(Language.Options_KeybindMode_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_KeybindMode_Description, Plugin.PluginName)
);
}
}
@@ -123,7 +130,12 @@ internal sealed class General : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Language_Name, Mutable.LanguageOverride.Name()))
using (
var combo = ImGuiUtil.BeginComboVertical(
Language.Options_Language_Name,
Mutable.LanguageOverride.Name()
)
)
{
if (combo.Success)
{
@@ -136,10 +148,17 @@ internal sealed class General : ISettingsTab
}
}
}
ImGuiUtil.HelpMarker(string.Format(Language.Options_Language_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_Language_Description, Plugin.PluginName)
);
ImGui.Spacing();
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_CommandHelpSide_Name, Mutable.CommandHelpSide.Name()))
using (
var combo = ImGuiUtil.BeginComboVertical(
Language.Options_CommandHelpSide_Name,
Mutable.CommandHelpSide.Name()
)
)
{
if (combo.Success)
{
@@ -152,7 +171,9 @@ internal sealed class General : ISettingsTab
}
}
}
ImGuiUtil.HelpMarker(string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName)
);
ImGui.Spacing();
ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate);
+43 -16
View File
@@ -1,10 +1,10 @@
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using HellionChat.Resources;
using HellionChat.Util;
namespace HellionChat.Ui.SettingsTabs;
@@ -19,21 +19,41 @@ internal sealed class Information : ISettingsTab
private readonly List<string> Translators =
[
"q673135110", "Akizem", "d0tiKs",
"Moonlight_Everlit", "Dark32", "andreycout",
"Button_", "Cali666", "cassandra308",
"lokinmodar", "jtabox", "AkiraYorumoto",
"MKhayle", "elena.space", "imlisa",
"andrei5125", "ShivaMaheshvara", "aislinn87",
"nishinatsu051", "lichuyuan", "Risu64",
"yummypillow", "witchymary", "Yuzumi",
"zomsakura", "Sirayuki"
"q673135110",
"Akizem",
"d0tiKs",
"Moonlight_Everlit",
"Dark32",
"andreycout",
"Button_",
"Cali666",
"cassandra308",
"lokinmodar",
"jtabox",
"AkiraYorumoto",
"MKhayle",
"elena.space",
"imlisa",
"andrei5125",
"ShivaMaheshvara",
"aislinn87",
"nishinatsu051",
"lichuyuan",
"Risu64",
"yummypillow",
"witchymary",
"Yuzumi",
"zomsakura",
"Sirayuki",
];
internal Information(Configuration mutable)
{
Mutable = mutable;
Translators.Sort((a, b) => string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal));
Translators.Sort(
(a, b) =>
string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal)
);
}
public void Draw(bool changed)
@@ -69,14 +89,19 @@ internal sealed class Information : ISettingsTab
ImGui.TextUnformatted(Language.Options_About_Version);
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.ParsedOrange, Plugin.Interface.Manifest.AssemblyVersion.ToString(3));
ImGui.TextColored(
ImGuiColors.ParsedOrange,
Plugin.Interface.Manifest.AssemblyVersion.ToString(3)
);
ImGuiHelpers.ScaledDummy(10.0f);
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
Dalamud.Utility.Util.OpenLink("https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues");
Dalamud.Utility.Util.OpenLink(
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues"
);
}
}
@@ -169,7 +194,9 @@ internal sealed class Information : ISettingsTab
return;
ImGui.TextUnformatted(Language.Options_Changelog_Header);
ImGui.TextUnformatted($"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}");
ImGui.TextUnformatted(
$"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}"
);
ImGui.Spacing();
foreach (var sentence in changelog.Split("\n"))
{
+40 -16
View File
@@ -53,16 +53,24 @@ internal sealed class Integrations : ISettingsTab
// 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))
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)))
using (
ImRaii.PushColor(
ImGuiCol.Text,
ColourUtil.RgbaToAbgr(Plugin.ThemeRegistry.Active.Colors.TextMuted)
)
)
{
ImGui.TextWrapped(HellionStrings.Settings_Integrations_Honorific_ToggleHint);
}
@@ -94,23 +102,34 @@ internal sealed class Integrations : ISettingsTab
{
DrawStatusGlyph('●', theme.Colors.StatusSuccess);
ImGui.SameLine();
ImGui.TextUnformatted(string.Format(
HellionStrings.Settings_Integrations_Honorific_Status_Detected,
version.Major, version.Minor));
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,
HonorificService.ExpectedApiMajor, incompatibleVersion.Major, incompatibleVersion.Minor));
ImGui.TextUnformatted(
string.Format(
HellionStrings.Settings_Integrations_Honorific_Status_Incompatible,
HonorificService.ExpectedApiMajor,
incompatibleVersion.Major,
incompatibleVersion.Minor
)
);
}
else
{
DrawStatusGlyph('○', theme.Colors.TextMuted);
ImGui.SameLine();
ImGui.TextUnformatted(HellionStrings.Settings_Integrations_Honorific_Status_NotInstalled);
ImGui.TextUnformatted(
HellionStrings.Settings_Integrations_Honorific_Status_NotInstalled
);
}
}
@@ -133,19 +152,24 @@ internal sealed class Integrations : ISettingsTab
// full section above the Coming Soon block.
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Title,
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Description);
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Title,
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Description);
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Title,
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Description);
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Title,
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Description);
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Title,
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Description);
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Description
);
}
private void DrawComingSoonItem(string title, string description)
+91 -27
View File
@@ -1,9 +1,9 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.Privacy;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
namespace HellionChat.Ui.SettingsTabs;
@@ -24,28 +24,90 @@ internal sealed class Privacy : ISettingsTab
// a runtime LanguageChanged call updates the labels immediately.
private static readonly (Func<string> Heading, ChatType[] Types)[] Groups =
[
(() => HellionStrings.Privacy_Group_DirectMessages, [ChatType.TellIncoming, ChatType.TellOutgoing]),
(() => HellionStrings.Privacy_Group_PartyAlliance, [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]),
(() => HellionStrings.Privacy_Group_FreeCompany, [ChatType.FreeCompany, ChatType.FreeCompanyAnnouncement, ChatType.FreeCompanyLoginLogout]),
(() => HellionStrings.Privacy_Group_Linkshells, [
ChatType.Linkshell1, ChatType.Linkshell2, ChatType.Linkshell3, ChatType.Linkshell4,
ChatType.Linkshell5, ChatType.Linkshell6, ChatType.Linkshell7, ChatType.Linkshell8,
]),
(() => HellionStrings.Privacy_Group_CrossLinkshells, [
ChatType.CrossLinkshell1, ChatType.CrossLinkshell2, ChatType.CrossLinkshell3, ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5, ChatType.CrossLinkshell6, ChatType.CrossLinkshell7, ChatType.CrossLinkshell8,
]),
(() => HellionStrings.Privacy_Group_ExtraChat, [
ChatType.ExtraChatLinkshell1, ChatType.ExtraChatLinkshell2, ChatType.ExtraChatLinkshell3, ChatType.ExtraChatLinkshell4,
ChatType.ExtraChatLinkshell5, ChatType.ExtraChatLinkshell6, ChatType.ExtraChatLinkshell7, ChatType.ExtraChatLinkshell8,
]),
(() => HellionStrings.Privacy_Group_PublicChat, [ChatType.Say, ChatType.Shout, ChatType.Yell, ChatType.NoviceNetwork, ChatType.CustomEmote, ChatType.StandardEmote]),
(() => HellionStrings.Privacy_Group_SystemLogs, [
ChatType.System, ChatType.Notice, ChatType.Urgent, ChatType.Echo,
ChatType.NpcDialogue, ChatType.NpcAnnouncement,
ChatType.LootNotice, ChatType.LootRoll, ChatType.RetainerSale,
ChatType.Crafting, ChatType.Gathering, ChatType.Sign, ChatType.RandomNumber,
]),
(
() => HellionStrings.Privacy_Group_DirectMessages,
[ChatType.TellIncoming, ChatType.TellOutgoing]
),
(
() => HellionStrings.Privacy_Group_PartyAlliance,
[ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]
),
(
() => HellionStrings.Privacy_Group_FreeCompany,
[
ChatType.FreeCompany,
ChatType.FreeCompanyAnnouncement,
ChatType.FreeCompanyLoginLogout,
]
),
(
() => HellionStrings.Privacy_Group_Linkshells,
[
ChatType.Linkshell1,
ChatType.Linkshell2,
ChatType.Linkshell3,
ChatType.Linkshell4,
ChatType.Linkshell5,
ChatType.Linkshell6,
ChatType.Linkshell7,
ChatType.Linkshell8,
]
),
(
() => HellionStrings.Privacy_Group_CrossLinkshells,
[
ChatType.CrossLinkshell1,
ChatType.CrossLinkshell2,
ChatType.CrossLinkshell3,
ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5,
ChatType.CrossLinkshell6,
ChatType.CrossLinkshell7,
ChatType.CrossLinkshell8,
]
),
(
() => HellionStrings.Privacy_Group_ExtraChat,
[
ChatType.ExtraChatLinkshell1,
ChatType.ExtraChatLinkshell2,
ChatType.ExtraChatLinkshell3,
ChatType.ExtraChatLinkshell4,
ChatType.ExtraChatLinkshell5,
ChatType.ExtraChatLinkshell6,
ChatType.ExtraChatLinkshell7,
ChatType.ExtraChatLinkshell8,
]
),
(
() => HellionStrings.Privacy_Group_PublicChat,
[
ChatType.Say,
ChatType.Shout,
ChatType.Yell,
ChatType.NoviceNetwork,
ChatType.CustomEmote,
ChatType.StandardEmote,
]
),
(
() => HellionStrings.Privacy_Group_SystemLogs,
[
ChatType.System,
ChatType.Notice,
ChatType.Urgent,
ChatType.Echo,
ChatType.NpcDialogue,
ChatType.NpcAnnouncement,
ChatType.LootNotice,
ChatType.LootRoll,
ChatType.RetainerSale,
ChatType.Crafting,
ChatType.Gathering,
ChatType.Sign,
ChatType.RandomNumber,
]
),
];
public void Draw(bool changed)
@@ -68,7 +130,8 @@ internal sealed class Privacy : ISettingsTab
ImGuiUtil.OptionCheckbox(
ref Mutable.PrivacyFilterEnabled,
HellionStrings.Privacy_FilterEnabled_Name,
HellionStrings.Privacy_FilterEnabled_Description);
HellionStrings.Privacy_FilterEnabled_Description
);
ImGuiUtil.HelpMarker(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help);
ImGui.Spacing();
@@ -82,7 +145,7 @@ internal sealed class Privacy : ISettingsTab
ImGui.Spacing();
if (ImGui.Button(HellionStrings.Privacy_Preset_PrivacyFirst))
Mutable.PrivacyPersistChannels = [..PrivacyDefaults.PrivacyFirstWhitelist];
Mutable.PrivacyPersistChannels = [.. PrivacyDefaults.PrivacyFirstWhitelist];
ImGui.SameLine();
if (ImGui.Button(HellionStrings.Privacy_Preset_ClearAll))
@@ -128,7 +191,8 @@ internal sealed class Privacy : ISettingsTab
ImGuiUtil.OptionCheckbox(
ref Mutable.PrivacyPersistUnknownChannels,
HellionStrings.Privacy_PersistUnknown_Name,
HellionStrings.Privacy_PersistUnknown_Description);
HellionStrings.Privacy_PersistUnknown_Description
);
}
}
}
+142 -34
View File
@@ -1,10 +1,10 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects.SubKinds;
namespace HellionChat.Ui.SettingsTabs;
@@ -42,13 +42,25 @@ internal sealed class Tabs : ISettingsTab
ImGui.Separator();
if (ImGui.Selectable(string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_General)))
if (
ImGui.Selectable(
string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_General)
)
)
Mutable.Tabs.Add(TabsUtil.VanillaGeneral);
if (ImGui.Selectable(string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_Event)))
if (
ImGui.Selectable(
string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_Event)
)
)
Mutable.Tabs.Add(TabsUtil.VanillaEvent);
if (ImGui.Selectable(string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_Tell)))
if (
ImGui.Selectable(
string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_Tell)
)
)
Mutable.Tabs.Add(TabsUtil.VanillaTellExclusive);
}
}
@@ -68,7 +80,12 @@ internal sealed class Tabs : ISettingsTab
using var pushedId = ImRaii.PushId($"tab-{i}");
if (ImGuiUtil.IconButton(FontAwesomeIcon.TrashAlt, tooltip: Language.Options_Tabs_Delete))
if (
ImGuiUtil.IconButton(
FontAwesomeIcon.TrashAlt,
tooltip: Language.Options_Tabs_Delete
)
)
{
toRemove = i;
ToOpen = -1;
@@ -76,7 +93,10 @@ internal sealed class Tabs : ISettingsTab
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.ArrowUp, tooltip: Language.Options_Tabs_MoveUp) && i > 0)
if (
ImGuiUtil.IconButton(FontAwesomeIcon.ArrowUp, tooltip: Language.Options_Tabs_MoveUp)
&& i > 0
)
{
(Mutable.Tabs[i - 1], Mutable.Tabs[i]) = (Mutable.Tabs[i], Mutable.Tabs[i - 1]);
ToOpen = i - 1;
@@ -84,13 +104,24 @@ internal sealed class Tabs : ISettingsTab
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.ArrowDown, tooltip: Language.Options_Tabs_MoveDown) && i < Mutable.Tabs.Count - 1)
if (
ImGuiUtil.IconButton(
FontAwesomeIcon.ArrowDown,
tooltip: Language.Options_Tabs_MoveDown
)
&& i < Mutable.Tabs.Count - 1
)
{
(Mutable.Tabs[i + 1], Mutable.Tabs[i]) = (Mutable.Tabs[i], Mutable.Tabs[i + 1]);
ToOpen = i + 1;
}
ImGui.InputText(Language.Options_Tabs_Name, ref tab.Name, 512, ImGuiInputTextFlags.EnterReturnsTrue);
ImGui.InputText(
Language.Options_Tabs_Name,
ref tab.Name,
512,
ImGuiInputTextFlags.EnterReturnsTrue
);
// v1.2.0 — Per-Tab Icon-Override. Default-Mapping greift falls nichts gesetzt.
ImGui.TextUnformatted(HellionStrings.Tabs_Icon_Label);
@@ -98,15 +129,19 @@ internal sealed class Tabs : ISettingsTab
ImGuiUtil.HelpMarker(HellionStrings.Tabs_Icon_HelpMarker);
var iconCurrent = string.IsNullOrEmpty(tab.Icon) ? "" : tab.Icon;
var iconPreview = iconCurrent.Length == 0
? HellionStrings.Tabs_Icon_DefaultOption
: iconCurrent;
var iconPreview =
iconCurrent.Length == 0 ? HellionStrings.Tabs_Icon_DefaultOption : iconCurrent;
using (var combo = ImRaii.Combo($"##icon-{i}", iconPreview))
{
if (combo.Success)
{
// Erste Option: Default (löscht Icon, lässt Mapping greifen).
if (ImGui.Selectable(HellionStrings.Tabs_Icon_DefaultOption, iconCurrent.Length == 0))
if (
ImGui.Selectable(
HellionStrings.Tabs_Icon_DefaultOption,
iconCurrent.Length == 0
)
)
{
tab.Icon = null;
}
@@ -116,7 +151,11 @@ internal sealed class Tabs : ISettingsTab
// Pool-Optionen aus TabIconGlyphResolver.PickerOptions (Single-Source-of-Truth).
foreach (var option in TabIconGlyphResolver.PickerOptions)
{
var isSelected = string.Equals(iconCurrent, option, StringComparison.OrdinalIgnoreCase);
var isSelected = string.Equals(
iconCurrent,
option,
StringComparison.OrdinalIgnoreCase
);
if (ImGui.Selectable(option, isSelected))
{
tab.Icon = option;
@@ -130,27 +169,53 @@ internal sealed class Tabs : ISettingsTab
if (tab.PopOut)
{
using var _ = ImRaii.PushIndent(10.0f);
ImGui.Checkbox(Language.Options_Tabs_IndependentOpacity, ref tab.IndependentOpacity);
ImGui.Checkbox(
Language.Options_Tabs_IndependentOpacity,
ref tab.IndependentOpacity
);
if (tab.IndependentOpacity)
ImGuiUtil.DragFloatVertical(Language.Options_Tabs_Opacity, ref tab.Opacity, 0.25f, 0f, 100f, $"{tab.Opacity:N2}%%", ImGuiSliderFlags.AlwaysClamp);
ImGuiUtil.DragFloatVertical(
Language.Options_Tabs_Opacity,
ref tab.Opacity,
0.25f,
0f,
100f,
$"{tab.Opacity:N2}%%",
ImGuiSliderFlags.AlwaysClamp
);
ImGui.Checkbox(Language.Options_Tabs_IndependentHide, ref tab.IndependentHide);
if (tab.IndependentHide)
{
using var __ = ImRaii.PushIndent(10.0f);
ImGuiUtil.OptionCheckbox(ref tab.HideDuringCutscenes, Language.Options_HideDuringCutscenes_Name);
ImGuiUtil.OptionCheckbox(
ref tab.HideDuringCutscenes,
Language.Options_HideDuringCutscenes_Name
);
ImGui.Spacing();
ImGuiUtil.OptionCheckbox(ref tab.HideWhenNotLoggedIn, Language.Options_HideWhenNotLoggedIn_Name);
ImGuiUtil.OptionCheckbox(
ref tab.HideWhenNotLoggedIn,
Language.Options_HideWhenNotLoggedIn_Name
);
ImGui.Spacing();
ImGuiUtil.OptionCheckbox(ref tab.HideWhenUiHidden, Language.Options_HideWhenUiHidden_Name);
ImGuiUtil.OptionCheckbox(
ref tab.HideWhenUiHidden,
Language.Options_HideWhenUiHidden_Name
);
ImGui.Spacing();
ImGuiUtil.OptionCheckbox(ref tab.HideInLoadingScreens, Language.Options_HideInLoadingScreens_Name);
ImGuiUtil.OptionCheckbox(
ref tab.HideInLoadingScreens,
Language.Options_HideInLoadingScreens_Name
);
ImGui.Spacing();
ImGuiUtil.OptionCheckbox(ref tab.HideInBattle, Language.Options_HideInBattle_Name);
ImGuiUtil.OptionCheckbox(
ref tab.HideInBattle,
Language.Options_HideInBattle_Name
);
ImGui.Spacing();
}
@@ -161,7 +226,12 @@ internal sealed class Tabs : ISettingsTab
ImGui.Spacing();
}
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Tabs_UnreadMode, tab.UnreadMode.Name()))
using (
var combo = ImGuiUtil.BeginComboVertical(
Language.Options_Tabs_UnreadMode,
tab.UnreadMode.Name()
)
)
{
if (combo.Success)
{
@@ -182,16 +252,32 @@ internal sealed class Tabs : ISettingsTab
ImGui.Checkbox(Language.Options_Tabs_NoInput, ref tab.InputDisabled);
if (!tab.InputDisabled)
{
var input = tab.Channel?.ToChatType().Name() ?? Language.Options_Tabs_NoInputChannel;
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Tabs_InputChannel, input))
var input =
tab.Channel?.ToChatType().Name() ?? Language.Options_Tabs_NoInputChannel;
using (
var combo = ImGuiUtil.BeginComboVertical(
Language.Options_Tabs_InputChannel,
input
)
)
{
if (combo.Success)
{
if (ImGui.Selectable(Language.Options_Tabs_NoInputChannel, tab.Channel == null))
if (
ImGui.Selectable(
Language.Options_Tabs_NoInputChannel,
tab.Channel == null
)
)
tab.Channel = null;
foreach (var channel in Enum.GetValues<InputChannel>())
if (ImGui.Selectable(channel.ToChatType().Name(), tab.Channel == channel))
if (
ImGui.Selectable(
channel.ToChatType().Name(),
tab.Channel == channel
)
)
tab.Channel = channel;
}
}
@@ -202,7 +288,11 @@ internal sealed class Tabs : ISettingsTab
ImGui.Checkbox(Language.Options_Tabs_SenderMessages, ref tab.AllSenderMessages);
ImGuiUtil.HelpText(Language.Options_Help_SenderMessages);
var worlds = Sheets.WorldsOnDatacenter(player).OrderByDescending(world => world.DataCenter.RowId).ThenBy(world => world.Name.ToString()).ToList();
var worlds = Sheets
.WorldsOnDatacenter(player)
.OrderByDescending(world => world.DataCenter.RowId)
.ThenBy(world => world.Name.ToString())
.ToList();
using (ImRaii.ItemWidth(ImGui.GetWindowWidth() / 3f))
{
@@ -225,18 +315,30 @@ internal sealed class Tabs : ISettingsTab
}
else
{
var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World);
var selectedWorld = worlds.FindIndex(world =>
world.RowId == tab.TellTarget.World
);
if (selectedWorld == -1)
selectedWorld = 0;
using (var combo = ImRaii.Combo("###player-world", worlds[selectedWorld].Name.ToString()))
using (
var combo = ImRaii.Combo(
"###player-world",
worlds[selectedWorld].Name.ToString()
)
)
{
if (combo.Success)
{
var lastDc = worlds.First().DataCenter.RowId;
foreach (var (idx, world) in worlds.Index())
{
if (ImGui.Selectable(world.Name.ToString(), selectedWorld == idx))
if (
ImGui.Selectable(
world.Name.ToString(),
selectedWorld == idx
)
)
{
selectedWorld = idx;
tab.TellTarget.World = worlds[selectedWorld].RowId;
@@ -253,7 +355,9 @@ internal sealed class Tabs : ISettingsTab
}
}
var target = (Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target) as IPlayerCharacter;
var target =
(Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target)
as IPlayerCharacter;
using (ImRaii.Disabled(target == null))
{
if (ImGui.Button("Set to target") && target != null)
@@ -263,7 +367,11 @@ internal sealed class Tabs : ISettingsTab
}
ImGuiUtil.ChannelSelector(Language.Options_Tabs_Channels, tab.SelectedChannels);
ImGuiUtil.ExtraChatSelector(Language.Options_Tabs_ExtraChatChannels, ref tab.ExtraChatAll, tab.ExtraChatChannels);
ImGuiUtil.ExtraChatSelector(
Language.Options_Tabs_ExtraChatChannels,
ref tab.ExtraChatAll,
tab.ExtraChatChannels
);
}
if (toRemove > -1)
+77 -25
View File
@@ -14,7 +14,8 @@ internal sealed class ThemeAndLayout : ISettingsTab
private string? _applyDismissedFor;
public string Name => HellionStrings.Settings_Card_ThemeAndLayout_Title + "###tabs-themeandlayout";
public string Name =>
HellionStrings.Settings_Card_ThemeAndLayout_Title + "###tabs-themeandlayout";
internal ThemeAndLayout(Plugin plugin, Configuration mutable)
{
@@ -42,7 +43,8 @@ internal sealed class ThemeAndLayout : ISettingsTab
var registry = Plugin.ThemeRegistry;
var active = registry.Get(Mutable.Theme);
var activeLabelTemplate = HellionStrings.ResourceManager.GetString("Settings_Themes_Active") ?? "Active: {0}";
var activeLabelTemplate =
HellionStrings.ResourceManager.GetString("Settings_Themes_Active") ?? "Active: {0}";
ImGui.TextUnformatted(string.Format(activeLabelTemplate, active.Name));
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF8FA3B5u))
ImGui.TextUnformatted(active.Author);
@@ -53,7 +55,9 @@ internal sealed class ThemeAndLayout : ISettingsTab
ImGui.Separator();
ImGui.Spacing();
var builtInsLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_BuiltIns") ?? "Built-in themes";
var builtInsLabel =
HellionStrings.ResourceManager.GetString("Settings_Themes_BuiltIns")
?? "Built-in themes";
ImGui.TextUnformatted(builtInsLabel);
ImGui.Spacing();
DrawThemeGrid(registry.AllBuiltIns(), active.Slug);
@@ -64,7 +68,9 @@ internal sealed class ThemeAndLayout : ISettingsTab
ImGui.Spacing();
ImGui.Separator();
ImGui.Spacing();
var customLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_Custom") ?? "Custom themes";
var customLabel =
HellionStrings.ResourceManager.GetString("Settings_Themes_Custom")
?? "Custom themes";
ImGui.TextUnformatted(customLabel);
ImGui.Spacing();
DrawThemeGrid(customs, active.Slug);
@@ -74,7 +80,9 @@ internal sealed class ThemeAndLayout : ISettingsTab
ImGui.Separator();
ImGui.Spacing();
var openFolderLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_OpenFolder") ?? "Open themes folder";
var openFolderLabel =
HellionStrings.ResourceManager.GetString("Settings_Themes_OpenFolder")
?? "Open themes folder";
if (ImGui.Button(openFolderLabel))
{
var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes");
@@ -83,7 +91,9 @@ internal sealed class ThemeAndLayout : ISettingsTab
}
ImGui.SameLine();
var exportLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_ExportActive") ?? "Export active...";
var exportLabel =
HellionStrings.ResourceManager.GetString("Settings_Themes_ExportActive")
?? "Export active...";
if (ImGui.Button(exportLabel))
{
var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes");
@@ -130,12 +140,26 @@ internal sealed class ThemeAndLayout : ISettingsTab
if (isActive)
{
var border = ColourUtil.RgbaToAbgr(theme.Colors.Primary);
draw.AddRect(cursorBefore, cursorBefore + new Vector2(w, h), border, 4f, ImDrawFlags.None, 2f);
draw.AddRect(
cursorBefore,
cursorBefore + new Vector2(w, h),
border,
4f,
ImDrawFlags.None,
2f
);
}
else if (hovered)
{
var border = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryLight & 0xFFFFFF99u);
draw.AddRect(cursorBefore, cursorBefore + new Vector2(w, h), border, 4f, ImDrawFlags.None, 1f);
draw.AddRect(
cursorBefore,
cursorBefore + new Vector2(w, h),
border,
4f,
ImDrawFlags.None,
1f
);
}
var mockupOrigin = cursorBefore + new Vector2(12f, 12f);
@@ -166,7 +190,8 @@ internal sealed class ThemeAndLayout : ISettingsTab
return;
var alreadyMatching = themeChatColors.Channels.All(kvp =>
Mutable.ChatColours.TryGetValue(kvp.Key, out var current) && current == kvp.Value);
Mutable.ChatColours.TryGetValue(kvp.Key, out var current) && current == kvp.Value
);
if (alreadyMatching)
return;
@@ -181,12 +206,15 @@ internal sealed class ThemeAndLayout : ISettingsTab
draw.AddRectFilled(origin, origin + new Vector2(width, height), bgFill, 4f);
draw.AddRect(origin, origin + new Vector2(width, height), border, 4f, ImDrawFlags.None, 1f);
var hint = HellionStrings.ResourceManager.GetString("Settings_Themes_ApplyChatColors_Hint")
?? "This theme suggests its own chat channel colours.";
var applyLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_ApplyChatColors_Apply")
?? "Apply";
var keepLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_ApplyChatColors_Keep")
?? "Keep current";
var hint =
HellionStrings.ResourceManager.GetString("Settings_Themes_ApplyChatColors_Hint")
?? "This theme suggests its own chat channel colours.";
var applyLabel =
HellionStrings.ResourceManager.GetString("Settings_Themes_ApplyChatColors_Apply")
?? "Apply";
var keepLabel =
HellionStrings.ResourceManager.GetString("Settings_Themes_ApplyChatColors_Keep")
?? "Keep current";
var textColor = ColourUtil.RgbaToAbgr(active.Colors.TextPrimary);
draw.AddText(origin + new Vector2(12f, 10f), textColor, hint);
@@ -217,7 +245,9 @@ internal sealed class ThemeAndLayout : ISettingsTab
private void DrawWindowStyleSection()
{
using var tree = ImRaii.TreeNode(HellionStrings.Settings_ThemeAndLayout_WindowStyle_Heading);
using var tree = ImRaii.TreeNode(
HellionStrings.Settings_ThemeAndLayout_WindowStyle_Heading
);
if (!tree.Success)
return;
@@ -225,13 +255,18 @@ internal sealed class ThemeAndLayout : ISettingsTab
{
ImGui.Checkbox(Language.Options_ShowTitleBar_Name, ref Mutable.ShowTitleBar);
ImGui.Checkbox(Language.Options_ShowPopOutTitleBar_Name, ref Mutable.ShowPopOutTitleBar);
ImGui.Checkbox(
Language.Options_ShowPopOutTitleBar_Name,
ref Mutable.ShowPopOutTitleBar
);
ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton);
ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description);
ImGui.Checkbox(Language.Options_SidebarTabView_Name, ref Mutable.SidebarTabView);
ImGuiUtil.HelpMarker(string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName)
);
ImGui.Spacing();
ImGui.Separator();
@@ -241,14 +276,17 @@ internal sealed class ThemeAndLayout : ISettingsTab
// Untere Schwelle 50 % verhindert versehentliches Komplett-Wegblenden
// des Chat-Hintergrunds (war v1.2.0 Bug bei WindowAlpha=0).
var opacityPercent = Mutable.WindowOpacity * 100f;
if (ImGuiUtil.DragFloatVertical(
if (
ImGuiUtil.DragFloatVertical(
HellionStrings.Settings_ThemeAndLayout_WindowOpacity_Name,
ref opacityPercent,
.25f,
50f,
100f,
$"{opacityPercent:N0}%%",
ImGuiSliderFlags.AlwaysClamp))
ImGuiSliderFlags.AlwaysClamp
)
)
{
Mutable.WindowOpacity = opacityPercent / 100f;
}
@@ -258,24 +296,38 @@ internal sealed class ThemeAndLayout : ISettingsTab
private void DrawTimestampStyleSection()
{
using var tree = ImRaii.TreeNode(HellionStrings.Settings_ThemeAndLayout_TimestampStyle_Heading);
using var tree = ImRaii.TreeNode(
HellionStrings.Settings_ThemeAndLayout_TimestampStyle_Heading
);
if (!tree.Success)
return;
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
ImGui.Checkbox(Language.Options_PrettierTimestamps_Name, ref Mutable.PrettierTimestamps);
ImGui.Checkbox(
Language.Options_PrettierTimestamps_Name,
ref Mutable.PrettierTimestamps
);
ImGuiUtil.HelpMarker(Language.Options_PrettierTimestamps_Description);
if (Mutable.PrettierTimestamps)
{
ImGui.Checkbox(Language.Options_MoreCompactPretty_Name, ref Mutable.MoreCompactPretty);
ImGui.Checkbox(
Language.Options_MoreCompactPretty_Name,
ref Mutable.MoreCompactPretty
);
ImGuiUtil.HelpMarker(Language.Options_MoreCompactPretty_Description);
ImGui.Checkbox(HellionStrings.Appearance_UseCompactDensity_Name, ref Mutable.UseCompactDensity);
ImGui.Checkbox(
HellionStrings.Appearance_UseCompactDensity_Name,
ref Mutable.UseCompactDensity
);
ImGuiUtil.HelpMarker(HellionStrings.Appearance_UseCompactDensity_Description);
ImGui.Checkbox(Language.Options_HideSameTimestamps_Name, ref Mutable.HideSameTimestamps);
ImGui.Checkbox(
Language.Options_HideSameTimestamps_Name,
ref Mutable.HideSameTimestamps
);
ImGuiUtil.HelpMarker(Language.Options_HideSameTimestamps_Description);
}
+27 -8
View File
@@ -16,14 +16,21 @@ internal static class ThemeMockup
var c = theme.Colors;
// Window-Bg
draw.AddRectFilled(origin, origin + size, ColourUtil.RgbaToAbgr(c.WindowBg | 0xFFu), theme.Layout.WindowRounding);
draw.AddRectFilled(
origin,
origin + size,
ColourUtil.RgbaToAbgr(c.WindowBg | 0xFFu),
theme.Layout.WindowRounding
);
// Title-Bar
var titleHeight = 14f;
draw.AddRectFilled(
origin,
new Vector2(origin.X + size.X, origin.Y + titleHeight),
ColourUtil.RgbaToAbgr(c.Identity), theme.Layout.WindowRounding);
ColourUtil.RgbaToAbgr(c.Identity),
theme.Layout.WindowRounding
);
// Tab-Bar — 3 Mini-Tabs
var tabY = origin.Y + titleHeight + 4f;
@@ -35,14 +42,17 @@ internal static class ThemeMockup
draw.AddRectFilled(
new Vector2(tabX, tabY),
new Vector2(tabX + 26f, tabY + tabHeight),
ColourUtil.RgbaToAbgr(color), theme.Layout.TabRounding);
ColourUtil.RgbaToAbgr(color),
theme.Layout.TabRounding
);
if (i == 0) // Active-Pill
if (i == 0) // Active-Pill
{
draw.AddRectFilled(
new Vector2(tabX, tabY + tabHeight - 2f),
new Vector2(tabX + 26f, tabY + tabHeight),
ColourUtil.RgbaToAbgr(c.Primary));
ColourUtil.RgbaToAbgr(c.Primary)
);
}
}
@@ -52,7 +62,9 @@ internal static class ThemeMockup
draw.AddRectFilled(
new Vector2(origin.X + 6f, rowY),
new Vector2(origin.X + size.X - 6f, rowY + rowHeight),
ColourUtil.RgbaToAbgr(c.Surface), 2f);
ColourUtil.RgbaToAbgr(c.Surface),
2f
);
// Akzent-Button rechts unten
var btnW = 28f;
@@ -62,9 +74,16 @@ internal static class ThemeMockup
draw.AddRectFilled(
new Vector2(btnX, btnY),
new Vector2(btnX + btnW, btnY + btnH),
ColourUtil.RgbaToAbgr(c.Accent), theme.Layout.FrameRounding);
ColourUtil.RgbaToAbgr(c.Accent),
theme.Layout.FrameRounding
);
// Border um das gesamte Mockup
draw.AddRect(origin, origin + size, ColourUtil.RgbaToAbgr(c.Border), theme.Layout.WindowRounding);
draw.AddRect(
origin,
origin + size,
ColourUtil.RgbaToAbgr(c.Border),
theme.Layout.WindowRounding
);
}
}
+83 -20
View File
@@ -1,7 +1,7 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
namespace HellionChat.Ui.SettingsTabs;
@@ -42,22 +42,42 @@ internal sealed class Window : ISettingsTab
ImGui.Checkbox(Language.Options_HideChat_Name, ref Mutable.HideChat);
ImGuiUtil.HelpMarker(Language.Options_HideChat_Description);
ImGui.Checkbox(Language.Options_HideDuringCutscenes_Name, ref Mutable.HideDuringCutscenes);
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideDuringCutscenes_Description, Plugin.PluginName));
ImGui.Checkbox(
Language.Options_HideDuringCutscenes_Name,
ref Mutable.HideDuringCutscenes
);
ImGuiUtil.HelpMarker(
string.Format(Language.Options_HideDuringCutscenes_Description, Plugin.PluginName)
);
ImGui.Checkbox(Language.Options_HideWhenNotLoggedIn_Name, ref Mutable.HideWhenNotLoggedIn);
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideWhenNotLoggedIn_Description, Plugin.PluginName));
ImGui.Checkbox(
Language.Options_HideWhenNotLoggedIn_Name,
ref Mutable.HideWhenNotLoggedIn
);
ImGuiUtil.HelpMarker(
string.Format(Language.Options_HideWhenNotLoggedIn_Description, Plugin.PluginName)
);
ImGui.Checkbox(Language.Options_HideWhenUiHidden_Name, ref Mutable.HideWhenUiHidden);
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideWhenUiHidden_Description, Plugin.PluginName));
ImGuiUtil.HelpMarker(
string.Format(Language.Options_HideWhenUiHidden_Description, Plugin.PluginName)
);
ImGui.Checkbox(Language.Options_HideInLoadingScreens_Name, ref Mutable.HideInLoadingScreens);
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideInLoadingScreens_Description, Plugin.PluginName));
ImGui.Checkbox(
Language.Options_HideInLoadingScreens_Name,
ref Mutable.HideInLoadingScreens
);
ImGuiUtil.HelpMarker(
string.Format(Language.Options_HideInLoadingScreens_Description, Plugin.PluginName)
);
ImGui.Checkbox(Language.Options_HideInBattle_Name, ref Mutable.HideInBattle);
ImGuiUtil.HelpMarker(Language.Options_HideInBattle_Description);
ImGui.Checkbox(Language.Options_HideInNewGamePlusMenu_Name, ref Mutable.HideInNewGamePlusMenu);
ImGui.Checkbox(
Language.Options_HideInNewGamePlusMenu_Name,
ref Mutable.HideInNewGamePlusMenu
);
ImGuiUtil.HelpMarker(Language.Options_HideInNewGamePlusMenu_Description);
}
}
@@ -80,13 +100,22 @@ internal sealed class Window : ISettingsTab
return;
}
ImGuiUtil.InputIntVertical(Language.Options_InactivityHideTimeout_Name, Language.Options_InactivityHideTimeout_Description, ref Mutable.InactivityHideTimeout, 1, 10);
ImGuiUtil.InputIntVertical(
Language.Options_InactivityHideTimeout_Name,
Language.Options_InactivityHideTimeout_Description,
ref Mutable.InactivityHideTimeout,
1,
10
);
// Untergrenze von 2 Sekunden gegen Selbst-Soft-Lock.
Mutable.InactivityHideTimeout = Math.Max(2, Mutable.InactivityHideTimeout);
using (ImRaii.Disabled(Mutable.HideInBattle))
{
ImGui.Checkbox(Language.Options_InactivityHideActiveDuringBattle_Name, ref Mutable.InactivityHideActiveDuringBattle);
ImGui.Checkbox(
Language.Options_InactivityHideActiveDuringBattle_Name,
ref Mutable.InactivityHideActiveDuringBattle
);
ImGuiUtil.HelpMarker(Language.Options_InactivityHideActiveDuringBattle_Description);
}
@@ -96,7 +125,12 @@ internal sealed class Window : ISettingsTab
return;
}
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_All_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
if (
ImGuiUtil.CtrlShiftButton(
Language.Options_InactivityHideChannels_All_Label,
Language.Options_InactivityHideChannels_Button_Tooltip
)
)
{
Mutable.InactivityHideChannelsV2 = TabsUtil.AllChannels();
Mutable.InactivityHideExtraChatAll = true;
@@ -104,7 +138,12 @@ internal sealed class Window : ISettingsTab
}
ImGui.SameLine();
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_None_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
if (
ImGuiUtil.CtrlShiftButton(
Language.Options_InactivityHideChannels_None_Label,
Language.Options_InactivityHideChannels_Button_Tooltip
)
)
{
Mutable.InactivityHideChannelsV2 = [];
Mutable.InactivityHideExtraChatAll = false;
@@ -113,8 +152,15 @@ internal sealed class Window : ISettingsTab
ImGui.Spacing();
ImGuiUtil.ChannelSelector(Language.Options_Tabs_Channels, Mutable.InactivityHideChannelsV2);
ImGuiUtil.ExtraChatSelector(Language.Options_Tabs_ExtraChatChannels, ref Mutable.InactivityHideExtraChatAll, Mutable.InactivityHideExtraChatChannels);
ImGuiUtil.ChannelSelector(
Language.Options_Tabs_Channels,
Mutable.InactivityHideChannelsV2
);
ImGuiUtil.ExtraChatSelector(
Language.Options_Tabs_ExtraChatChannels,
ref Mutable.InactivityHideExtraChatAll,
Mutable.InactivityHideExtraChatChannels
);
}
}
@@ -132,7 +178,10 @@ internal sealed class Window : ISettingsTab
ImGui.Checkbox(Language.Options_CanResize_Name, ref Mutable.CanResize);
// v0.6.0 — global master switch for the pop-out input bar.
ImGui.Checkbox(HellionStrings.Settings_Window_PopOutInputEnabled_Name, ref Mutable.PopOutInputEnabled);
ImGui.Checkbox(
HellionStrings.Settings_Window_PopOutInputEnabled_Name,
ref Mutable.PopOutInputEnabled
);
ImGuiUtil.HelpMarker(HellionStrings.Settings_Window_PopOutInputEnabled_Description);
ImGui.Spacing();
@@ -156,12 +205,26 @@ internal sealed class Window : ISettingsTab
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
{
ImGui.Checkbox(Language.Options_NativeItemTooltips_Name, ref Mutable.NativeItemTooltips);
ImGuiUtil.HelpMarker(string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName));
ImGui.Checkbox(
Language.Options_NativeItemTooltips_Name,
ref Mutable.NativeItemTooltips
);
ImGuiUtil.HelpMarker(
string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName)
);
if (Mutable.NativeItemTooltips)
{
ImGuiUtil.DragFloatVertical(Language.Options_TooltipOffset_Name, Language.Options_TooltipOffset_Desc, ref Mutable.TooltipOffset, 1, 0f, 400f, $"{Mutable.TooltipOffset:N0}px", ImGuiSliderFlags.AlwaysClamp);
ImGuiUtil.DragFloatVertical(
Language.Options_TooltipOffset_Name,
Language.Options_TooltipOffset_Desc,
ref Mutable.TooltipOffset,
1,
0f,
400f,
$"{Mutable.TooltipOffset:N0}px",
ImGuiSliderFlags.AlwaysClamp
);
}
}
}
+31 -16
View File
@@ -35,9 +35,10 @@ internal sealed class StatusBar
{
// InvariantCulture: User-System-Locale darf das Format nicht
// verändern (de_DE würde sonst "1,2k" statt "1.2k" liefern).
var msgPart = messages >= 1000
? string.Format(CultureInfo.InvariantCulture, "{0:0.0}k msg", messages / 1000.0)
: $"{messages} msg";
var msgPart =
messages >= 1000
? string.Format(CultureInfo.InvariantCulture, "{0:0.0}k msg", messages / 1000.0)
: $"{messages} msg";
var tabsPart = $"{tabs} {(tabs == 1 ? "tab" : "tabs")}";
return $"{tabsPart} · {msgPart}";
}
@@ -48,7 +49,8 @@ internal sealed class StatusBar
/// </summary>
public static string FormatTells(int count)
{
if (count <= 0) return string.Empty;
if (count <= 0)
return string.Empty;
return $"{count} {(count == 1 ? "tell" : "tells")}";
}
@@ -56,11 +58,13 @@ internal sealed class StatusBar
// helper so a future LINQ regression gets pinned by xUnit.
internal static (int messages, int tells) AggregateForStatusBar(IList<Tab> tabs)
{
int messages = 0, tells = 0;
int messages = 0,
tells = 0;
foreach (var t in tabs)
{
messages += t.Messages.Count;
if (t.IsTempTab) tells++;
if (t.IsTempTab)
tells++;
}
return (messages, tells);
}
@@ -69,7 +73,12 @@ internal sealed class StatusBar
/// Test-Hook: Cache-Logic ohne reale Time-Source verifizieren.
/// Nicht für Production-Render.
/// </summary>
internal (string counts, string tells) SnapshotForTest(long now, int tabs, int messages, int tells)
internal (string counts, string tells) SnapshotForTest(
long now,
int tabs,
int messages,
int tells
)
{
UpdateCacheIfDue(now, tabs, messages, tells);
return (_cachedCountsText, _cachedTellsText);
@@ -105,11 +114,14 @@ internal sealed class StatusBar
var cursorY = ImGui.GetCursorScreenPos().Y;
var winLeft = ImGui.GetWindowPos().X;
var winRight = winLeft + ImGui.GetWindowSize().X;
ImGui.GetWindowDrawList().AddLine(
new Vector2(winLeft, cursorY),
new Vector2(winRight, cursorY),
ColourUtil.RgbaToAbgr(theme.Colors.Border),
1f);
ImGui
.GetWindowDrawList()
.AddLine(
new Vector2(winLeft, cursorY),
new Vector2(winRight, cursorY),
ColourUtil.RgbaToAbgr(theme.Colors.Border),
1f
);
ImGui.Dummy(new Vector2(0, 2)); // BorderTop-Spacing
@@ -169,10 +181,13 @@ internal sealed class StatusBar
{
var pos = ImGui.GetCursorScreenPos();
const float radius = 4f;
ImGui.GetWindowDrawList().AddCircleFilled(
new Vector2(pos.X + radius, pos.Y + ImGui.GetTextLineHeight() / 2f),
radius,
ColourUtil.RgbaToAbgr(rgba));
ImGui
.GetWindowDrawList()
.AddCircleFilled(
new Vector2(pos.X + radius, pos.Y + ImGui.GetTextLineHeight() / 2f),
radius,
ColourUtil.RgbaToAbgr(rgba)
);
ImGui.Dummy(new Vector2(radius * 2 + 4, ImGui.GetTextLineHeight()));
}
+24 -6
View File
@@ -18,23 +18,41 @@ internal static class TabIconGlyphResolver
/// Reihenfolge ist die UI-Reihenfolge im Settings-Tab Icon-Combobox.
/// </summary>
public static readonly IReadOnlyList<string> PickerOptions =
["comment", "comments", "cog", "users", "user-friends", "link",
"envelope", "clock", "hashtag", "star", "heart", "bell",
"bookmark", "flag", "fire"];
[
"comment",
"comments",
"cog",
"users",
"user-friends",
"link",
"envelope",
"clock",
"hashtag",
"star",
"heart",
"bell",
"bookmark",
"flag",
"fire",
];
/// <summary>
/// Glyph-Set, das überhaupt als Override akzeptiert wird. Aus
/// <see cref="PickerOptions"/> abgeleitet — KnownGlyphs nie
/// manuell pflegen.
/// </summary>
private static readonly HashSet<string> KnownGlyphs =
new(PickerOptions, StringComparer.OrdinalIgnoreCase);
private static readonly HashSet<string> KnownGlyphs = new(
PickerOptions,
StringComparer.OrdinalIgnoreCase
);
/// <summary>
/// Tab-Name → Default-Glyph-Name. Tab.Name wird per Lokalisierung
/// gesetzt; wir matchen daher gegen einen Pool aus DE/EN-Synonymen.
/// </summary>
private static readonly Dictionary<string, string> NameDefaults = new(StringComparer.OrdinalIgnoreCase)
private static readonly Dictionary<string, string> NameDefaults = new(
StringComparer.OrdinalIgnoreCase
)
{
["allgemein"] = "comment",
["general"] = "comment",
+4 -4
View File
@@ -27,7 +27,9 @@ internal static class TabIconMapping
/// zurück (degraded, kein Crash). Build-Time-Enforcement ist nicht
/// möglich, weil PickerOptions ohne Dalamud-Reference auskommt.
/// </summary>
private static readonly Dictionary<string, FontAwesomeIcon> GlyphLookup = new(StringComparer.OrdinalIgnoreCase)
private static readonly Dictionary<string, FontAwesomeIcon> GlyphLookup = new(
StringComparer.OrdinalIgnoreCase
)
{
["comment"] = FontAwesomeIcon.Comment,
["comments"] = FontAwesomeIcon.Comments,
@@ -65,8 +67,6 @@ internal static class TabIconMapping
}
var glyph = TabIconGlyphResolver.ResolveGlyphName(tab, autoTellGlyph);
return GlyphLookup.TryGetValue(glyph, out var icon)
? icon
: FontAwesomeIcon.Hashtag;
return GlyphLookup.TryGetValue(glyph, out var icon) ? icon : FontAwesomeIcon.Hashtag;
}
}
+4 -2
View File
@@ -23,9 +23,11 @@ internal static class TabTintCache
{
var name = tab.TellTarget.Name;
var world = tab.TellTarget.World;
if (tab._cachedTellIcon is null
if (
tab._cachedTellIcon is null
|| tab._cachedIconTellName != name
|| tab._cachedIconTellWorld != world)
|| tab._cachedIconTellWorld != world
)
{
tab._cachedIconTellName = name;
tab._cachedIconTellWorld = world;
+70 -28
View File
@@ -7,7 +7,6 @@ using Lumina.Excel;
using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
using Pidgin;
using static Pidgin.Parser;
using static Pidgin.Parser<char>;
@@ -26,14 +25,18 @@ internal static class AutoTranslate
private static Parser<char, (string name, Maybe<IEnumerable<ISelectorPart>> selector)> Parser()
{
var sheetName = Any
.AtLeastOnceUntil(Lookahead(Char('[').IgnoreResult().Or(End)))
var sheetName = Any.AtLeastOnceUntil(Lookahead(Char('[').IgnoreResult().Or(End)))
.Select(string.Concat)
.Labelled("sheetName");
var numPair = Map(ISelectorPart (first, second) =>
new IndexRange(uint.Parse(string.Concat(first)), uint.Parse(string.Concat(second))),
var numPair = Map(
ISelectorPart (first, second) =>
new IndexRange(
uint.Parse(string.Concat(first)),
uint.Parse(string.Concat(second))
),
Digit.AtLeastOnce().Before(Char('-')),
Digit.AtLeastOnce())
Digit.AtLeastOnce()
)
.Labelled("numPair");
var singleRow = Digit
.AtLeastOnce()
@@ -43,14 +46,11 @@ internal static class AutoTranslate
.Then(Digit.AtLeastOnce())
.Select(string.Concat)
.Select(ISelectorPart (num) => new ColumnSpecifier(uint.Parse(num)));
var noun = String("noun")
.Select(ISelectorPart (_) => new NounMarker());
var noun = String("noun").Select(ISelectorPart (_) => new NounMarker());
var selectorItems = OneOf(Try(numPair), singleRow, column, noun)
.Separated(Char(','))
.Labelled("selectorItems");
var selector = selectorItems
.Between(Char('['), Char(']'))
.Labelled("selector");
var selector = selectorItems.Between(Char('['), Char(']')).Labelled("selector");
return Map((name, sel) => (name, sel), sheetName, selector.Optional());
}
@@ -90,7 +90,16 @@ internal static class AutoTranslate
var list = new List<AutoTranslateEntry>();
foreach (var row in Sheets.CompletionSheet)
{
var lookup = string.Concat(row.LookupTable.Select(p => p.Type == ReadOnlySePayloadType.Text ? Encoding.UTF8.GetString(p.Body.Span) : p.MacroCode == MacroCode.Num && p.TryGetExpression(out var num) && num.TryGetInt(out var val) ? val.ToString(CultureInfo.InvariantCulture) : ",,,unexpected macro code,,,"));
var lookup = string.Concat(
row.LookupTable.Select(p =>
p.Type == ReadOnlySePayloadType.Text ? Encoding.UTF8.GetString(p.Body.Span)
: p.MacroCode == MacroCode.Num
&& p.TryGetExpression(out var num)
&& num.TryGetInt(out var val)
? val.ToString(CultureInfo.InvariantCulture)
: ",,,unexpected macro code,,,"
)
);
try
{
if (lookup is not ("" or "@"))
@@ -114,7 +123,7 @@ internal static class AutoTranslate
case IndexRange range:
{
var start = (int)range.Start;
var end = (int)(range.End + 1);
var end = (int)(range.End + 1);
rows.Add(start..end);
break;
}
@@ -158,7 +167,14 @@ internal static class AutoTranslate
var rawName = rowParser.ReadStringColumn(col);
if (!rawName.IsEmpty)
{
list.Add(new AutoTranslateEntry(row.Group, (uint)i, rawName.ToString(), string.Empty));
list.Add(
new AutoTranslateEntry(
row.Group,
(uint)i,
rawName.ToString(),
string.Empty
)
);
if (shouldAdd)
ValidEntries.Add((row.Group, (uint)i));
@@ -172,7 +188,14 @@ internal static class AutoTranslate
if (row.Text.IsEmpty)
continue;
list.Add(new AutoTranslateEntry(row.Group, row.RowId, row.Text.ToString(), row.GroupTitle.ToString()));
list.Add(
new AutoTranslateEntry(
row.Group,
row.RowId,
row.Text.ToString(),
row.GroupTitle.ToString()
)
);
if (shouldAdd)
ValidEntries.Add((row.Group, row.RowId));
@@ -220,16 +243,16 @@ internal static class AutoTranslate
if (sort)
{
return wholeMatches.OrderBy(entry => entry.Text, StringComparer.OrdinalIgnoreCase)
.Concat(prefixMatches.OrderBy(entry => entry.Text, StringComparer.OrdinalIgnoreCase))
return wholeMatches
.OrderBy(entry => entry.Text, StringComparer.OrdinalIgnoreCase)
.Concat(
prefixMatches.OrderBy(entry => entry.Text, StringComparer.OrdinalIgnoreCase)
)
.Concat(otherMatches.OrderBy(entry => entry.Text, StringComparer.OrdinalIgnoreCase))
.ToList();
}
return wholeMatches
.Concat(prefixMatches)
.Concat(otherMatches)
.ToList();
return wholeMatches.Concat(prefixMatches).Concat(otherMatches).ToList();
}
internal static void ReplaceWithPayload(ref byte[] bytes)
@@ -255,7 +278,11 @@ internal static class AutoTranslate
var tag = Encoding.UTF8.GetString(bytes[start..(i + 1)]);
var parts = tag[4..^1].Split(',', 2);
if (parts.Length == 2 && uint.TryParse(parts[0], out var group) && uint.TryParse(parts[1], out var key))
if (
parts.Length == 2
&& uint.TryParse(parts[0], out var group)
&& uint.TryParse(parts[1], out var key)
)
{
bool isValid;
lock (EntriesLock)
@@ -267,7 +294,13 @@ internal static class AutoTranslate
bytes = new byte[oldBytes.Length + lengthDiff];
Array.Copy(oldBytes, bytes, start);
Array.Copy(payload, 0, bytes, start, payload.Length);
Array.Copy(oldBytes, i + 1, bytes, start + payload.Length, oldBytes.Length - (i + 1));
Array.Copy(
oldBytes,
i + 1,
bytes,
start + payload.Length,
oldBytes.Length - (i + 1)
);
i += lengthDiff;
}
@@ -278,7 +311,10 @@ internal static class AutoTranslate
// Pure managed comparison via Span avoids the msvcrt.dll P/Invoke,
// which is fragile under Wine and triggered an extra managed-to-
// unmanaged copy per check.
if (i + search.Length < bytes.Length && bytes.AsSpan(i, search.Length).SequenceEqual(search))
if (
i + search.Length < bytes.Length
&& bytes.AsSpan(i, search.Length).SequenceEqual(search)
)
start = i;
}
}
@@ -309,7 +345,11 @@ internal static class AutoTranslate
var tag = Encoding.UTF8.GetString(bytes[..(i + 1)]);
var parts = tag[4..^1].Split(',', 2);
if (parts.Length == 2 && uint.TryParse(parts[0], out var group) && uint.TryParse(parts[1], out var key))
if (
parts.Length == 2
&& uint.TryParse(parts[0], out var group)
&& uint.TryParse(parts[1], out var key)
)
{
bool isValid;
lock (EntriesLock)
@@ -317,7 +357,9 @@ internal static class AutoTranslate
if (!isValid)
return false;
var evaluated = Plugin.Evaluator.Evaluate(new ReadOnlySeString(CreateFixedTranslation(group, key))).ToString();
var evaluated = Plugin
.Evaluator.Evaluate(new ReadOnlySeString(CreateFixedTranslation(group, key)))
.ToString();
if (!evaluated.StartsWith('/'))
return false;
@@ -332,8 +374,8 @@ internal static class AutoTranslate
private static byte[] CreateFixedTranslation(uint group, uint key)
{
using var rssb = new RentedSeStringBuilder();
return rssb.Builder
.BeginMacro(MacroCode.Fixed)
return rssb
.Builder.BeginMacro(MacroCode.Fixed)
.AppendUIntExpression(group - 1)
.AppendUIntExpression(key)
.EndMacro()
+57 -25
View File
@@ -1,9 +1,8 @@
using HellionChat.Code;
using System.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using System.Text;
using HellionChat.Code;
using Lumina.Text.Payloads;
using PayloadType = Dalamud.Game.Text.SeStringHandling.PayloadType;
namespace HellionChat.Util;
@@ -248,7 +247,11 @@ internal static class ChunkUtil
// return chunks;
// }
internal static IEnumerable<Chunk> ToChunks(SeString msg, ChunkSource source, ChatType? defaultColour)
internal static IEnumerable<Chunk> ToChunks(
SeString msg,
ChunkSource source,
ChatType? defaultColour
)
{
var chunks = new List<Chunk>();
@@ -259,13 +262,15 @@ internal static class ChunkUtil
void Append(string text)
{
chunks.Add(new TextChunk(source, link, text)
{
FallbackColour = defaultColour,
Foreground = foreground.Count > 0 ? foreground.Peek() : null,
Glow = glow.Count > 0 ? glow.Peek() : null,
Italic = italic,
});
chunks.Add(
new TextChunk(source, link, text)
{
FallbackColour = defaultColour,
Foreground = foreground.Count > 0 ? foreground.Peek() : null,
Glow = glow.Count > 0 ? glow.Peek() : null,
Italic = italic,
}
);
}
foreach (var payload in msg.Payloads)
@@ -273,18 +278,18 @@ internal static class ChunkUtil
switch (payload.Type)
{
case PayloadType.EmphasisItalic:
var newStatus = ((EmphasisItalicPayload) payload).IsEnabled;
var newStatus = ((EmphasisItalicPayload)payload).IsEnabled;
italic = newStatus;
break;
case PayloadType.UIForeground:
var foregroundPayload = (UIForegroundPayload) payload;
var foregroundPayload = (UIForegroundPayload)payload;
if (foregroundPayload.IsEnabled)
foreground.Push(foregroundPayload.UIColor.Value.Dark);
else if (foreground.Count > 0)
foreground.Pop();
break;
case PayloadType.UIGlow:
var glowPayload = (UIGlowPayload) payload;
var glowPayload = (UIGlowPayload)payload;
if (glowPayload.IsEnabled)
glow.Push(glowPayload.UIColor.Value.Light);
else if (glow.Count > 0)
@@ -292,12 +297,12 @@ internal static class ChunkUtil
break;
case PayloadType.AutoTranslateText:
chunks.Add(new IconChunk(source, payload, BitmapFontIcon.AutoTranslateBegin));
var autoText = ((AutoTranslatePayload) payload).Text;
var autoText = ((AutoTranslatePayload)payload).Text;
Append(autoText.Substring(2, autoText.Length - 4));
chunks.Add(new IconChunk(source, link, BitmapFontIcon.AutoTranslateEnd));
break;
case PayloadType.Icon:
chunks.Add(new IconChunk(source, link, ((IconPayload) payload).Icon));
chunks.Add(new IconChunk(source, link, ((IconPayload)payload).Icon));
break;
case PayloadType.MapLink:
case PayloadType.Quest:
@@ -311,7 +316,7 @@ internal static class ChunkUtil
link = payload;
break;
case PayloadType.Unknown:
var rawPayload = (RawPayload) payload;
var rawPayload = (RawPayload)payload;
var colorPayload = ColorPayload.From(rawPayload.Data);
if (colorPayload != null)
{
@@ -333,20 +338,36 @@ internal static class ChunkUtil
{
glow.Pop();
}
else if (rawPayload.Data.Length > 6 && rawPayload.Data[2] == 0x05 && rawPayload.Data[3] == 0xF6)
else if (
rawPayload.Data.Length > 6
&& rawPayload.Data[2] == 0x05
&& rawPayload.Data[3] == 0xF6
)
{
var (r, g, b) = (rawPayload.Data[4], rawPayload.Data[5], rawPayload.Data[6]);
var (r, g, b) = (
rawPayload.Data[4],
rawPayload.Data[5],
rawPayload.Data[6]
);
glow.Push(ColourUtil.ComponentsToRgba(r, g, b));
}
}
else if (rawPayload.Data.Length > 7 && rawPayload.Data[1] == 0x27 && rawPayload.Data[3] == 0x0A)
else if (
rawPayload.Data.Length > 7
&& rawPayload.Data[1] == 0x27
&& rawPayload.Data[3] == 0x0A
)
{
// pf payload
var reader = new BinaryReader(new MemoryStream(rawPayload.Data[4..]));
var id = GetInteger(reader);
link = new PartyFinderPayload(id);
}
else if (rawPayload.Data.Length > 5 && rawPayload.Data[1] == 0x27 && rawPayload.Data[3] == 0x06)
else if (
rawPayload.Data.Length > 5
&& rawPayload.Data[1] == 0x27
&& rawPayload.Data[3] == 0x06
)
{
// achievement payload
var reader = new BinaryReader(new MemoryStream(rawPayload.Data[4..]));
@@ -452,18 +473,29 @@ internal static class ChunkUtil
return payload.World.RowId == senderWorld;
}
internal static readonly RawPayload PeriodicRecruitmentLink = new([0x02, 0x27, 0x07, 0x08, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]);
internal static readonly RawPayload PeriodicRecruitmentLink = new([
0x02,
0x27,
0x07,
0x08,
0x01,
0x01,
0x01,
0xFF,
0x01,
0x03,
]);
private static uint GetInteger(BinaryReader input)
{
var num1 = (uint) input.ReadByte();
var num1 = (uint)input.ReadByte();
if (num1 < 208U)
return num1 - 1U;
var num2 = (uint) ((int) num1 + 1 & 15);
var num2 = (uint)((int)num1 + 1 & 15);
var numArray = new byte[4];
for (var index = 3; index >= 0; --index)
numArray[index] = (num2 & 1 << index) == 0L ? (byte) 0 : input.ReadByte();
numArray[index] = (num2 & 1 << index) == 0L ? (byte)0 : input.ReadByte();
return BitConverter.ToUInt32(numArray, 0);
}
+39 -27
View File
@@ -3,12 +3,13 @@ using System.Numerics;
namespace HellionChat.Util;
internal static class ColourUtil {
internal static class ColourUtil
{
private static (byte r, byte g, byte b) RgbaToRgbComponents(uint rgba)
{
var r = (byte) ((rgba & 0xFF000000) >> 24);
var g = (byte) ((rgba & 0xFF0000) >> 16);
var b = (byte) ((rgba & 0xFF00) >> 8);
var r = (byte)((rgba & 0xFF000000) >> 24);
var g = (byte)((rgba & 0xFF0000) >> 16);
var b = (byte)((rgba & 0xFF00) >> 8);
return (r, g, b);
}
@@ -17,26 +18,28 @@ internal static class ColourUtil {
internal static Vector3 RgbaToVector3(uint rgba)
{
var (r, g, b) = RgbaToRgbComponents(rgba);
return new Vector3((float) r / 255, (float) g / 255, (float) b / 255);
return new Vector3((float)r / 255, (float)g / 255, (float)b / 255);
}
internal static uint Vector3ToRgba(Vector3 col)
{
return ComponentsToRgba(
(byte) Math.Round(col.X * 255),
(byte) Math.Round(col.Y * 255),
(byte) Math.Round(col.Z * 255)
(byte)Math.Round(col.X * 255),
(byte)Math.Round(col.Y * 255),
(byte)Math.Round(col.Z * 255)
);
}
internal static uint Vector4ToAbgr(Vector4 col)
{
return RgbaToAbgr(ComponentsToRgba(
(byte) Math.Round(col.X * 255),
(byte) Math.Round(col.Y * 255),
(byte) Math.Round(col.Z * 255),
(byte) Math.Round(col.W * 255)
));
return RgbaToAbgr(
ComponentsToRgba(
(byte)Math.Round(col.X * 255),
(byte)Math.Round(col.Y * 255),
(byte)Math.Round(col.Z * 255),
(byte)Math.Round(col.W * 255)
)
);
}
public static unsafe uint ArgbToRgba(uint x)
@@ -46,21 +49,21 @@ internal static class ColourUtil {
return x;
}
internal static uint ComponentsToRgba(byte red, byte green, byte blue, byte alpha = 0xFF)
=> alpha | (uint) (red << 24) | (uint) (green << 16) | (uint) (blue << 8);
internal static uint ComponentsToRgba(byte red, byte green, byte blue, byte alpha = 0xFF) =>
alpha | (uint)(red << 24) | (uint)(green << 16) | (uint)(blue << 8);
internal static uint AdjustBrightness(uint abgr, float factor)
{
var a = (byte) ((abgr & 0xFF000000) >> 24);
var b = (byte) ((abgr & 0x00FF0000) >> 16);
var g = (byte) ((abgr & 0x0000FF00) >> 8);
var r = (byte) (abgr & 0x000000FF);
var a = (byte)((abgr & 0xFF000000) >> 24);
var b = (byte)((abgr & 0x00FF0000) >> 16);
var g = (byte)((abgr & 0x0000FF00) >> 8);
var r = (byte)(abgr & 0x000000FF);
var nr = (byte) Math.Clamp(r * factor, 0f, 255f);
var ng = (byte) Math.Clamp(g * factor, 0f, 255f);
var nb = (byte) Math.Clamp(b * factor, 0f, 255f);
var nr = (byte)Math.Clamp(r * factor, 0f, 255f);
var ng = (byte)Math.Clamp(g * factor, 0f, 255f);
var nb = (byte)Math.Clamp(b * factor, 0f, 255f);
return ((uint) a << 24) | ((uint) nb << 16) | ((uint) ng << 8) | nr;
return ((uint)a << 24) | ((uint)nb << 16) | ((uint)ng << 8) | nr;
}
public static uint HexToRgba(string hex)
@@ -68,13 +71,22 @@ internal static class ColourUtil {
ArgumentNullException.ThrowIfNull(hex);
var s = hex.StartsWith('#') ? hex[1..] : hex;
if (s.Length != 6 && s.Length != 8)
throw new FormatException($"Hex colour must be 6 or 8 hex digits, got {s.Length}: '{hex}'");
throw new FormatException(
$"Hex colour must be 6 or 8 hex digits, got {s.Length}: '{hex}'"
);
if (!uint.TryParse(s, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out var value))
if (
!uint.TryParse(
s,
System.Globalization.NumberStyles.HexNumber,
System.Globalization.CultureInfo.InvariantCulture,
out var value
)
)
throw new FormatException($"Hex colour '{hex}' is not a valid hexadecimal value");
if (s.Length == 6)
value = (value << 8) | 0xFFu; // RRGGBB → RRGGBBFF
value = (value << 8) | 0xFFu; // RRGGBB → RRGGBBFF
return value;
}
+122 -24
View File
@@ -1,13 +1,13 @@
using System.Globalization;
using System.Numerics;
using HellionChat.Resources;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using Dalamud.Bindings.ImGui;
using HellionChat.Resources;
namespace HellionChat.Util;
@@ -18,9 +18,46 @@ public static class DateWidget
private static readonly DateTime Sample = DateTime.UnixEpoch;
private static readonly Vector4 Transparent = new(1, 1, 1, 0);
private static readonly string[] DayNames = [Language.DateWidget_Day_Sun, Language.DateWidget_Day_Mon, Language.DateWidget_Day_Tue, Language.DateWidget_Day_Wed, Language.DateWidget_Day_Thu, Language.DateWidget_Day_Fri, Language.DateWidget_Day_Sat];
private static readonly string[] MonthNames = [Language.DateWidget_Month_January, Language.DateWidget_Month_February, Language.DateWidget_Month_March, Language.DateWidget_Month_April, Language.DateWidget_Month_May, Language.DateWidget_Month_June, Language.DateWidget_Month_July, Language.DateWidget_Month_August, Language.DateWidget_Month_September, Language.DateWidget_Month_October, Language.DateWidget_Month_November, Language.DateWidget_Month_December];
private static readonly int[] NumDaysPerMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
private static readonly string[] DayNames =
[
Language.DateWidget_Day_Sun,
Language.DateWidget_Day_Mon,
Language.DateWidget_Day_Tue,
Language.DateWidget_Day_Wed,
Language.DateWidget_Day_Thu,
Language.DateWidget_Day_Fri,
Language.DateWidget_Day_Sat,
];
private static readonly string[] MonthNames =
[
Language.DateWidget_Month_January,
Language.DateWidget_Month_February,
Language.DateWidget_Month_March,
Language.DateWidget_Month_April,
Language.DateWidget_Month_May,
Language.DateWidget_Month_June,
Language.DateWidget_Month_July,
Language.DateWidget_Month_August,
Language.DateWidget_Month_September,
Language.DateWidget_Month_October,
Language.DateWidget_Month_November,
Language.DateWidget_Month_December,
];
private static readonly int[] NumDaysPerMonth =
[
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
private static float LongestMonthWidth;
private static readonly float[] MonthWidths = new float[12];
@@ -33,12 +70,14 @@ public static class DateWidget
if (minimal > currentMin)
{
currentMin = minimal;
Plugin.Notification.AddNotification(new Notification
{
Content = Language.DateWidget_InvalidDate.Format(minimal.ToShortDateString()),
Type = NotificationType.Warning,
Minimized = false,
});
Plugin.Notification.AddNotification(
new Notification
{
Content = Language.DateWidget_InvalidDate.Format(minimal.ToShortDateString()),
Type = NotificationType.Warning,
Minimized = false,
}
);
needsRefresh = true;
}
else if (currentMin > currentMax)
@@ -50,15 +89,41 @@ public static class DateWidget
return needsRefresh;
}
public static void DatePickerWithInput(string label, int id, ref string dateString, ref DateTime date, string format, bool sameLine = false, bool closeWhenMouseLeavesIt = true)
public static void DatePickerWithInput(
string label,
int id,
ref string dateString,
ref DateTime date,
string format,
bool sameLine = false,
bool closeWhenMouseLeavesIt = true
)
{
if (sameLine)
ImGui.SameLine();
ImGui.SetNextItemWidth(ImGui.CalcTextSize(Sample.ToString(format)).X + ImGui.GetStyle().ItemInnerSpacing.X * 2);
if (ImGui.InputTextWithHint($"##{label}Input", format.ToUpper(), ref dateString, 32, ImGuiInputTextFlags.CallbackCompletion))
ImGui.SetNextItemWidth(
ImGui.CalcTextSize(Sample.ToString(format)).X + ImGui.GetStyle().ItemInnerSpacing.X * 2
);
if (
ImGui.InputTextWithHint(
$"##{label}Input",
format.ToUpper(),
ref dateString,
32,
ImGuiInputTextFlags.CallbackCompletion
)
)
{
if (DateTime.TryParseExact(dateString, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var tmp))
if (
DateTime.TryParseExact(
dateString,
format,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var tmp
)
)
date = tmp;
}
@@ -72,7 +137,13 @@ public static class DateWidget
dateString = date.ToString(format);
}
private static bool DatePicker(string label, ref DateTime dateOut, bool closeWhenMouseLeavesIt, string leftArrow = "", string rightArrow = "")
private static bool DatePicker(
string label,
ref DateTime dateOut,
bool closeWhenMouseLeavesIt,
string leftArrow = "",
string rightArrow = ""
)
{
using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
if (LongestMonthWidth == 0.0f)
@@ -96,12 +167,22 @@ public static class DateWidget
var labelSize = ImGui.CalcTextSize(label, true, 0);
var widthRequiredByCalendar = (2.0f * arrowLeftWidth) + (2.0f * arrowRightWidth) + LongestMonthWidth + ImGui.CalcTextSize("9999").X + (120.0f * ImGuiHelpers.GlobalScale);
var popupHeight = ((labelSize.Y + (2 * style.ItemSpacing.Y)) * HeightInItems) + (style.FramePadding.Y * 3);
var widthRequiredByCalendar =
(2.0f * arrowLeftWidth)
+ (2.0f * arrowRightWidth)
+ LongestMonthWidth
+ ImGui.CalcTextSize("9999").X
+ (120.0f * ImGuiHelpers.GlobalScale);
var popupHeight =
((labelSize.Y + (2 * style.ItemSpacing.Y)) * HeightInItems)
+ (style.FramePadding.Y * 3);
var valueChanged = false;
ImGui.SetNextWindowSize(new Vector2(widthRequiredByCalendar, widthRequiredByCalendar));
ImGui.SetNextWindowSizeConstraints(new Vector2(widthRequiredByCalendar, popupHeight + 40), new Vector2(widthRequiredByCalendar, popupHeight + 40));
ImGui.SetNextWindowSizeConstraints(
new Vector2(widthRequiredByCalendar, popupHeight + 40),
new Vector2(widthRequiredByCalendar, popupHeight + 40)
);
using var popupItem = ImRaii.ContextPopupItem(label, ImGuiPopupFlags.None);
if (!popupItem.Success)
@@ -156,7 +237,12 @@ public static class DateWidget
}
}
ImGui.SameLine(ImGui.GetWindowWidth() - yearPartWidth - style.WindowPadding.X - style.ItemSpacing.X * 4.0f);
ImGui.SameLine(
ImGui.GetWindowWidth()
- yearPartWidth
- style.WindowPadding.X
- style.ItemSpacing.X * 4.0f
);
using (ImRaii.PushId(1235))
{
@@ -189,7 +275,10 @@ public static class DateWidget
maxDayOfCurMonth = 29;
}
using var buttonHovered = ImRaii.PushColor(ImGuiCol.ButtonHovered, ImGuiColors.DalamudOrange);
using var buttonHovered = ImRaii.PushColor(
ImGuiCol.ButtonHovered,
ImGuiColors.DalamudOrange
);
using var buttonActive = ImRaii.PushColor(ImGuiCol.ButtonActive, ImGuiColors.DalamudYellow);
ImGui.Separator();
@@ -201,7 +290,11 @@ public static class DateWidget
{
using (ImRaii.Group())
{
using var textColor = ImRaii.PushColor(ImGuiCol.Text, CalculateTextColor(), dw == 0);
using var textColor = ImRaii.PushColor(
ImGuiCol.Text,
CalculateTextColor(),
dw == 0
);
ImGui.Text($"{(dw == 0 ? "" : " ")}{DayNames[dw]}");
if (dw == 0)
@@ -253,7 +346,12 @@ public static class DateWidget
size.X += 2.0f * distance;
size.Y += 2.0f * distance;
var mousePos = ImGui.GetIO().MousePos;
if (mousePos.X < pos.X || mousePos.Y < pos.Y || mousePos.X > pos.X + size.X || mousePos.Y > pos.Y + size.Y)
if (
mousePos.X < pos.X
|| mousePos.Y < pos.Y
|| mousePos.X > pos.X + size.X
|| mousePos.Y > pos.Y + size.Y
)
mustCloseCombo = true;
}
@@ -270,4 +368,4 @@ public static class DateWidget
var l = (textColor.X + textColor.Y + textColor.Z) * 0.33334f;
return new Vector4(l * 2.0f > 1 ? 1 : l * 2.0f, l * .5f, l * .5f, textColor.W);
}
}
}
+21 -9
View File
@@ -26,8 +26,11 @@ public class ColorPayload
case 0xE9:
var param = stream.ReadByte();
if (param == -1)
throw new ArgumentException("Encountered premature end of input (unexpected EOF).", nameof(stream));
var globalValue = (uint) GlobalParametersCache.GetValue(param - 2);
throw new ArgumentException(
"Encountered premature end of input (unexpected EOF).",
nameof(stream)
);
var globalValue = (uint)GlobalParametersCache.GetValue(param - 2);
payload.Enabled = true;
payload.UnshiftedColor = globalValue;
payload.Color = ColourUtil.ArgbToRgba(globalValue);
@@ -40,10 +43,16 @@ public class ColorPayload
return v switch
{
// ReSharper disable once LocalizableElement
-1 => throw new ArgumentException("Encountered premature end of input (unexpected EOF).", nameof(v)),
-1 => throw new ArgumentException(
"Encountered premature end of input (unexpected EOF).",
nameof(v)
),
// ReSharper disable once LocalizableElement
0 => throw new ArgumentException("Encountered premature end of input (unexpected null character).", nameof(v)),
_ => (uint)v << shift
0 => throw new ArgumentException(
"Encountered premature end of input (unexpected null character).",
nameof(v)
),
_ => (uint)v << shift,
};
}
@@ -54,9 +63,12 @@ public class ColorPayload
else
argbValue |= 0xff000000u;
if( (typeByte & 4) != 0 ) argbValue |= ShiftAndThrowIfZero( stream.ReadByte(), 16 );
if( (typeByte & 2) != 0 ) argbValue |= ShiftAndThrowIfZero( stream.ReadByte(), 8 );
if( (typeByte & 1) != 0 ) argbValue |= ShiftAndThrowIfZero( stream.ReadByte(), 0 );
if ((typeByte & 4) != 0)
argbValue |= ShiftAndThrowIfZero(stream.ReadByte(), 16);
if ((typeByte & 2) != 0)
argbValue |= ShiftAndThrowIfZero(stream.ReadByte(), 8);
if ((typeByte & 1) != 0)
argbValue |= ShiftAndThrowIfZero(stream.ReadByte(), 0);
payload.Enabled = true;
payload.Color = ColourUtil.ArgbToRgba(argbValue);
@@ -66,4 +78,4 @@ public class ColorPayload
return null;
}
}
}
}
+4 -2
View File
@@ -29,7 +29,9 @@ public static class GlobalParametersCache
public static unsafe void Refresh()
{
if (!ThreadSafety.IsMainThread)
throw new InvalidOperationException("GlobalParametersCache.Refresh must be called on the main thread.");
throw new InvalidOperationException(
"GlobalParametersCache.Refresh must be called on the main thread."
);
var rtm = RaptureTextModule.Instance();
if (rtm is null)
@@ -48,4 +50,4 @@ public static class GlobalParametersCache
Cache[(int)i] = 0;
}
}
}
}
+7 -5
View File
@@ -31,7 +31,8 @@ public readonly unsafe ref struct GfdFileView
private ref readonly GfdHeader Header => ref MemoryMarshal.AsRef<GfdHeader>(Span);
/// <summary>Gets the entries.</summary>
private ReadOnlySpan<GfdEntry> Entries => MemoryMarshal.Cast<byte, GfdEntry>(Span[sizeof(GfdHeader)..]);
private ReadOnlySpan<GfdEntry> Entries =>
MemoryMarshal.Cast<byte, GfdEntry>(Span[sizeof(GfdHeader)..]);
/// <summary>Attempts to get an entry.</summary>
/// <param name="iconId">The icon ID.</param>
@@ -153,8 +154,6 @@ public readonly unsafe ref struct GfdFileView
}
}
internal static class IconUtil
{
private static byte[]? GfdFile;
@@ -164,8 +163,11 @@ internal static class IconUtil
{
if (GfdFile is null)
{
var file = Plugin.DataManager.GetFile("common/font/gfdata.gfd")
?? throw new FileNotFoundException("Failed to load common/font/gfdata.gfd from the game data.");
var file =
Plugin.DataManager.GetFile("common/font/gfdata.gfd")
?? throw new FileNotFoundException(
"Failed to load common/font/gfdata.gfd from the game data."
);
GfdFile = file.Data;
}
return new GfdFileView(GfdFile);
+126 -28
View File
@@ -1,9 +1,7 @@
using System.Buffers;
using System.Numerics;
using System.Text;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
@@ -13,7 +11,9 @@ using Dalamud.Interface.ImGuiFontChooserDialog;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using HellionChat.Code;
using HellionChat.GameFunctions.Types;
using HellionChat.Resources;
namespace HellionChat.Util;
@@ -30,7 +30,7 @@ internal static class ImGuiUtil
[
ImGuiMouseButton.Left,
ImGuiMouseButton.Middle,
ImGuiMouseButton.Right
ImGuiMouseButton.Right,
];
private static Payload? Hovered;
@@ -66,7 +66,13 @@ internal static class ImGuiUtil
// ever feeds in a degenerate input.
private const int MaxLineByteCount = 16 * 1024;
internal static void WrapText(string csText, Chunk chunk, PayloadHandler? handler, Vector4 defaultText, float lineWidth)
internal static void WrapText(
string csText,
Chunk chunk,
PayloadHandler? handler,
Vector4 defaultText,
float lineWidth
)
{
if (csText.Length == 0)
return;
@@ -111,7 +117,13 @@ internal static class ImGuiUtil
}
}
private static unsafe void WrapEncodedLine(ReadOnlySpan<byte> bytes, Chunk chunk, PayloadHandler? handler, Vector4 defaultText, float lineWidth)
private static unsafe void WrapEncodedLine(
ReadOnlySpan<byte> bytes,
Chunk chunk,
PayloadHandler? handler,
Vector4 defaultText,
float lineWidth
)
{
var byteCount = bytes.Length;
if (byteCount == 0)
@@ -187,13 +199,21 @@ internal static class ImGuiUtil
ImGuiHelpers.GlobalScale,
basePtr + start,
basePtr + end,
width);
width
);
if (result == null)
return -1;
return (int)(result - basePtr);
}
private static unsafe void DrawText(byte* basePtr, int start, int end, Chunk chunk, PayloadHandler? handler, Vector4 defaultText)
private static unsafe void DrawText(
byte* basePtr,
int start,
int end,
Chunk chunk,
PayloadHandler? handler,
Vector4 defaultText
)
{
var oldPos = ImGui.GetCursorScreenPos();
@@ -209,10 +229,14 @@ internal static class ImGuiUtil
{
defaultText.W = 0.25f;
var actualCol = ColourUtil.Vector4ToAbgr(defaultText);
ImGui.GetWindowDrawList().AddRectFilled(oldPos, oldPos + ImGui.GetItemRectSize(), actualCol);
ImGui
.GetWindowDrawList()
.AddRectFilled(oldPos, oldPos + ImGui.GetItemRectSize(), actualCol);
foreach (var (boundsStart, boundsSize) in PayloadBounds)
ImGui.GetWindowDrawList().AddRectFilled(boundsStart, boundsStart + boundsSize, actualCol);
ImGui
.GetWindowDrawList()
.AddRectFilled(boundsStart, boundsStart + boundsSize, actualCol);
PayloadBounds.Clear();
}
@@ -230,7 +254,12 @@ internal static class ImGuiUtil
return end;
}
internal static bool IconButton(FontAwesomeIcon icon, string? id = null, string? tooltip = null, int width = 0)
internal static bool IconButton(
FontAwesomeIcon icon,
string? id = null,
string? tooltip = null,
int width = 0
)
{
var label = icon.ToIconString();
if (id != null)
@@ -246,7 +275,10 @@ internal static class ImGuiUtil
ret = ImGui.Button(label, size);
}
if (!string.IsNullOrEmpty(tooltip) && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
if (
!string.IsNullOrEmpty(tooltip)
&& ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)
)
Tooltip(tooltip);
return ret;
@@ -264,7 +296,7 @@ internal static class ImGuiUtil
internal static void HelpText(string text)
{
using (ImRaii.TextWrapPos(0.0f))
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]))
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]))
ImGui.TextUnformatted(text);
}
@@ -275,7 +307,7 @@ internal static class ImGuiUtil
internal static void HelpMarker(string description)
{
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]))
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]))
ImGui.TextUnformatted("(?)");
// AllowWhenDisabled — ohne das Flag liefert IsItemHovered bei
@@ -296,25 +328,48 @@ internal static class ImGuiUtil
var dalamudOrange = style.BuiltInColors?.DalamudOrange;
using (ImRaii.TextWrapPos(wrap ? 0.0f : ImGui.GetFontSize() * 35.0f))
using (ImRaii.PushColor(ImGuiCol.Text, dalamudOrange ?? Vector4.Zero, dalamudOrange != null))
using (
ImRaii.PushColor(ImGuiCol.Text, dalamudOrange ?? Vector4.Zero, dalamudOrange != null)
)
ImGui.TextUnformatted(text);
}
internal static ImRaii.ComboDisposable BeginComboVertical(string label, string previewValue, ImGuiComboFlags flags = ImGuiComboFlags.None)
internal static ImRaii.ComboDisposable BeginComboVertical(
string label,
string previewValue,
ImGuiComboFlags flags = ImGuiComboFlags.None
)
{
ImGui.TextUnformatted(label);
ImGui.SetNextItemWidth(-1);
return ImRaii.Combo($"##{label}", previewValue, flags);
}
internal static bool DragFloatVertical(string label, ref float value, float vSpeed = 1.0f, float vMin = float.MinValue, float vMax = float.MaxValue, string? format = null, ImGuiSliderFlags flags = ImGuiSliderFlags.None)
internal static bool DragFloatVertical(
string label,
ref float value,
float vSpeed = 1.0f,
float vMin = float.MinValue,
float vMax = float.MaxValue,
string? format = null,
ImGuiSliderFlags flags = ImGuiSliderFlags.None
)
{
ImGui.TextUnformatted(label);
ImGui.SetNextItemWidth(-1);
return ImGui.DragFloat($"##{label}", ref value, vSpeed, vMin, vMax, format, flags);
}
internal static bool DragFloatVertical(string label, string description, ref float value, float vSpeed = 1.0f, float vMin = float.MinValue, float vMax = float.MaxValue, string? format = null, ImGuiSliderFlags flags = ImGuiSliderFlags.None)
internal static bool DragFloatVertical(
string label,
string description,
ref float value,
float vSpeed = 1.0f,
float vMin = float.MinValue,
float vMax = float.MaxValue,
string? format = null,
ImGuiSliderFlags flags = ImGuiSliderFlags.None
)
{
ImGui.TextUnformatted(label);
ImGui.SetNextItemWidth(-1);
@@ -324,7 +379,14 @@ internal static class ImGuiUtil
return r;
}
internal static bool InputIntVertical(string label, string description, ref int value, int step = 1, int stepFast = 100, ImGuiInputTextFlags flags = ImGuiInputTextFlags.None)
internal static bool InputIntVertical(
string label,
string description,
ref int value,
int step = 1,
int stepFast = 100,
ImGuiInputTextFlags flags = ImGuiInputTextFlags.None
)
{
ImGui.TextUnformatted(label);
ImGui.SetNextItemWidth(-1);
@@ -341,7 +403,14 @@ internal static class ImGuiUtil
ImGui.TextUnformatted(tooltip);
}
public static SingleFontChooserDialog? FontChooser(string label, SingleFontSpec font, bool checkbox, ref bool checkboxValue, Predicate<IFontFamilyId>? exclusion = null, string? preview = null)
public static SingleFontChooserDialog? FontChooser(
string label,
SingleFontSpec font,
bool checkbox,
ref bool checkboxValue,
Predicate<IFontFamilyId>? exclusion = null,
string? preview = null
)
{
using var id = ImRaii.PushId(label);
@@ -360,7 +429,7 @@ internal static class ImGuiUtil
if (!ImGui.Button($"{buttonText}##{label}"))
return null;
var chooser = SingleFontChooserDialog.CreateAuto((UiBuilder) Plugin.Interface.UiBuilder);
var chooser = SingleFontChooserDialog.CreateAuto((UiBuilder)Plugin.Interface.UiBuilder);
chooser.SelectedFont = font;
if (exclusion is not null)
chooser.FontFamilyExcludeFilter = exclusion;
@@ -445,7 +514,19 @@ internal static class ImGuiUtil
foreach (var vk in Enum.GetValues<VirtualKey>())
{
if (vk is VirtualKey.NO_KEY or VirtualKey.CONTROL or VirtualKey.LCONTROL or VirtualKey.RCONTROL or VirtualKey.SHIFT or VirtualKey.LSHIFT or VirtualKey.RSHIFT or VirtualKey.MENU or VirtualKey.LMENU or VirtualKey.RMENU)
if (
vk
is VirtualKey.NO_KEY
or VirtualKey.CONTROL
or VirtualKey.LCONTROL
or VirtualKey.RCONTROL
or VirtualKey.SHIFT
or VirtualKey.LSHIFT
or VirtualKey.RSHIFT
or VirtualKey.MENU
or VirtualKey.LMENU
or VirtualKey.RMENU
)
continue;
if (!vk.TryToImGui(out var imKey) || !ImGui.IsKeyPressed(imKey))
@@ -466,7 +547,15 @@ internal static class ImGuiUtil
}
}
public static void DrawArrows(ref int selected, int min, int max, float spacing, int id = 0, string? tooltipLeft = null, string? tooltipRight = null)
public static void DrawArrows(
ref int selected,
int min,
int max,
float spacing,
int id = 0,
string? tooltipLeft = null,
string? tooltipRight = null
)
{
// Prevents changing values from triggering EndDisable
var isMin = selected == min;
@@ -486,7 +575,7 @@ internal static class ImGuiUtil
using (ImRaii.Disabled(isMax))
{
if (IconButton(FontAwesomeIcon.ArrowRight, id+1.ToString()))
if (IconButton(FontAwesomeIcon.ArrowRight, id + 1.ToString()))
selected++;
}
@@ -503,7 +592,9 @@ internal static class ImGuiUtil
public static void CenterText(string text, float indent = 0.0f)
{
indent *= ImGuiHelpers.GlobalScale;
ImGui.SameLine(((ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(text).X) * 0.5f) + indent);
ImGui.SameLine(
((ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(text).X) * 0.5f) + indent
);
ImGui.TextUnformatted(text);
}
@@ -624,7 +715,10 @@ internal static class ImGuiUtil
return result != 0 || key == VirtualKey.NO_KEY;
}
public static void ChannelSelector(string headerText, Dictionary<ChatType, (ChatSource Source, ChatSource Target)> chatCodes)
public static void ChannelSelector(
string headerText,
Dictionary<ChatType, (ChatSource Source, ChatSource Target)> chatCodes
)
{
var spacing = 3.0f * ImGuiHelpers.GlobalScale;
@@ -710,7 +804,11 @@ internal static class ImGuiUtil
}
}
public static void ExtraChatSelector(string headerText, ref bool all, HashSet<Guid> extraChatChannels)
public static void ExtraChatSelector(
string headerText,
ref bool all,
HashSet<Guid> extraChatChannels
)
{
if (Plugin.ExtraChat.ChannelNames.Count <= 0)
return;
+8 -6
View File
@@ -25,10 +25,10 @@ public static class MathUtil
SizeY = Y + Height;
}
public Rectangle(Vector2 pos, Vector2 size) : this((int) pos.X, (int) pos.Y, (int) size.X, (int) size.Y) { }
public Rectangle(Vector2 pos, Vector2 size)
: this((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y) { }
public override string ToString()
=> $"X: {X} Y: {Y} Width: {Width} Height: {Height}";
public override string ToString() => $"X: {X} Y: {Y} Width: {Width} Height: {Height}";
}
/// <summary>
@@ -43,7 +43,9 @@ public static class MathUtil
// overlap on both axes. The previous nested ValueInRange approach
// used strict inequalities at both ends, which dropped identical
// rectangles and shared-edge cases as false negatives.
return a.X < b.X + b.Width && a.X + a.Width > b.X &&
a.Y < b.Y + b.Height && a.Y + a.Height > b.Y;
return a.X < b.X + b.Width
&& a.X + a.Width > b.X
&& a.Y < b.Y + b.Height
&& a.Y + a.Height > b.Y;
}
}
}
+14 -6
View File
@@ -15,25 +15,33 @@ public static class MemoryUtil
if (address == nint.Zero)
throw new ArgumentException("Memory address cannot be zero.", nameof(address));
if (length <= 0)
throw new ArgumentOutOfRangeException(nameof(length), length, "Length must be positive.");
throw new ArgumentOutOfRangeException(
nameof(length),
length,
"Length must be positive."
);
if (length > MaxDumpLength)
throw new ArgumentOutOfRangeException(nameof(length), length, $"Length exceeds the {MaxDumpLength}-byte safety cap.");
throw new ArgumentOutOfRangeException(
nameof(length),
length,
$"Length exceeds the {MaxDumpLength}-byte safety cap."
);
var ptr = (byte*)address;
var str = new StringBuilder("\n");
for(var i = 0; i < length; i++)
for (var i = 0; i < length; i++)
{
str.Append($"{ptr![i]:X02}");
if (i == 0)
continue;
if ((i+1) % 16 == 0)
if ((i + 1) % 16 == 0)
str.Append('\n');
else if ((i+1) % 4 == 0)
else if ((i + 1) % 4 == 0)
str.Append(' ');
}
Plugin.Log.Information(str.ToString());
}
}
}
+4 -5
View File
@@ -4,7 +4,7 @@ namespace HellionChat.Util;
internal class PartyFinderPayload : Payload
{
public override PayloadType Type => (PayloadType) 0x50;
public override PayloadType Type => (PayloadType)0x50;
internal uint Id { get; }
@@ -26,7 +26,7 @@ internal class PartyFinderPayload : Payload
internal class AchievementPayload : Payload
{
public override PayloadType Type => (PayloadType) 0x51;
public override PayloadType Type => (PayloadType)0x51;
internal uint Id { get; }
@@ -46,10 +46,9 @@ internal class AchievementPayload : Payload
}
}
internal class UriPayload(Uri uri) : Payload
{
public override PayloadType Type => (PayloadType) 0x52;
public override PayloadType Type => (PayloadType)0x52;
public Uri Uri { get; } = uri;
@@ -92,7 +91,7 @@ internal class UriPayload(Uri uri) : Payload
internal class EmotePayload : Payload
{
public override PayloadType Type => (PayloadType) 0x53;
public override PayloadType Type => (PayloadType)0x53;
public string Code = string.Empty;
+47 -13
View File
@@ -1,7 +1,7 @@
using System.Numerics;
using Dalamud.Interface.Utility;
using System.Collections;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using System.Collections;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
namespace HellionChat.Util;
@@ -15,7 +15,6 @@ public static class SearchSelector
private static string PrevSearchId = null!;
private static Type PrevSearchType = null!;
public record SelectorOptions
{
public Func<string, string> FormatRow { get; init; } = row => row.ToString();
@@ -25,14 +24,18 @@ public static class SearchSelector
public Vector2? Size { get; init; } = null;
}
public record SelectorPopupOptions: SelectorOptions
public record SelectorPopupOptions : SelectorOptions
{
public ImGuiPopupFlags PopupFlags { get; init; } = ImGuiPopupFlags.None;
public bool CloseOnSelection { get; init; } = false;
public Func<string, bool> IsSelected { get; init; } = _ => false;
}
private static void SearchInput(string id, IEnumerable<string> filteredSheet, Func<string, string, bool> searchPredicate)
private static void SearchInput(
string id,
IEnumerable<string> filteredSheet,
Func<string, string, bool> searchPredicate
)
{
if (ImGui.IsWindowAppearing() && ImGui.IsWindowFocused() && !ImGui.IsAnyItemActive())
{
@@ -51,13 +54,28 @@ public static class SearchSelector
ImGui.SetKeyboardFocusHere(0);
}
if (ImGui.InputTextWithHint("##ExcelSheetSearch", "Search", ref SheetSearchText, 128, ImGuiInputTextFlags.AutoSelectAll))
if (
ImGui.InputTextWithHint(
"##ExcelSheetSearch",
"Search",
ref SheetSearchText,
128,
ImGuiInputTextFlags.AutoSelectAll
)
)
FilteredSearchSheet = null;
FilteredSearchSheet ??= filteredSheet.Where(s => searchPredicate(s, SheetSearchText)).ToArray();
FilteredSearchSheet ??= filteredSheet
.Where(s => searchPredicate(s, SheetSearchText))
.ToArray();
}
public static bool SelectorPopup(string id, out string selected, SelectorPopupOptions? options = null, bool close = false)
public static bool SelectorPopup(
string id,
out string selected,
SelectorPopupOptions? options = null,
bool close = false
)
{
options ??= new SelectorPopupOptions();
var sheet = options.FilteredSheet;
@@ -71,14 +89,26 @@ public static class SearchSelector
if (!popup.Success)
return false;
SearchInput(id, sheet, options.SearchPredicate ?? ((row, s) => options.FormatRow(row).Contains(s, StringComparison.CurrentCultureIgnoreCase)));
SearchInput(
id,
sheet,
options.SearchPredicate
?? (
(row, s) =>
options
.FormatRow(row)
.Contains(s, StringComparison.CurrentCultureIgnoreCase)
)
);
using var child = ImRaii.Child("SearchList", Vector2.Zero, true);
if (!child.Success)
return false;
var ret = false;
var drawSelectable = options.DrawSelectable ?? ((row, selected) => ImGui.Selectable(options.FormatRow(row), selected));
var drawSelectable =
options.DrawSelectable
?? ((row, selected) => ImGui.Selectable(options.FormatRow(row), selected));
using (var clipper = new ListClipper(FilteredSearchSheet!.Length))
{
foreach (var i in clipper.Rows)
@@ -139,7 +169,10 @@ public unsafe class ListClipper : IEnumerable<(int, int)>, IDisposable
{
get
{
var cols = (ItemRemainder == 0 || CurrentRows != DisplayEnd || CurrentRow != DisplayEnd - 1) ? CurrentColumns : ItemRemainder;
var cols =
(ItemRemainder == 0 || CurrentRows != DisplayEnd || CurrentRow != DisplayEnd - 1)
? CurrentColumns
: ItemRemainder;
for (var j = 0; j < cols; j++)
yield return j;
}
@@ -155,7 +188,8 @@ public unsafe class ListClipper : IEnumerable<(int, int)>, IDisposable
Clipper.Begin(CurrentRows, itemHeight);
}
public IEnumerator<(int, int)> GetEnumerator() => (from i in Rows from j in Columns select (i, j)).GetEnumerator();
public IEnumerator<(int, int)> GetEnumerator() =>
(from i in Rows from j in Columns select (i, j)).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+2 -1
View File
@@ -28,7 +28,8 @@ internal static class StringUtil
// "0.#" keeps the rounded fractional digit (1.5 GB stays "1.5GB"); "N0"
// would truncate it back to integer. InvariantCulture pins the decimal
// separator to '.' so a German locale doesn't render "1,5GB".
return (Math.Sign(byteCount) * num).ToString("0.#", CultureInfo.InvariantCulture) + suf[place];
return (Math.Sign(byteCount) * num).ToString("0.#", CultureInfo.InvariantCulture)
+ suf[place];
}
// Returns the text unchanged when it already fits the width budget,
+137 -125
View File
@@ -20,149 +20,161 @@ public static class TabsUtil
// events (loot, crafting, gathering, NPC dialogue, PF pings) move to
// the System tab where they belong — keeps the General view focused
// on actual conversation in the immediate surroundings.
public static Tab VanillaGeneral => new()
{
Name = Language.Tabs_Presets_General,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab VanillaGeneral =>
new()
{
[ChatType.Say] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Yell] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Shout] = (ChatSourceExt.All, ChatSourceExt.All),
}
};
Name = Language.Tabs_Presets_General,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.Say] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Yell] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Shout] = (ChatSourceExt.All, ChatSourceExt.All),
},
};
public static Tab VanillaEvent => new()
{
Name = Language.Tabs_Presets_Event,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)> { [ChatType.NpcDialogue] = (ChatSourceExt.All, ChatSourceExt.All), },
};
public static Tab VanillaTellExclusive => new()
{
Name = Language.Tabs_Presets_Tell,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab VanillaEvent =>
new()
{
[ChatType.TellIncoming] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.TellOutgoing] = (ChatSourceExt.All, ChatSourceExt.All),
},
Channel = InputChannel.Tell,
AllSenderMessages = true,
};
Name = Language.Tabs_Presets_Event,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.NpcDialogue] = (ChatSourceExt.All, ChatSourceExt.All),
},
};
public static Tab VanillaTellExclusive =>
new()
{
Name = Language.Tabs_Presets_Tell,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.TellIncoming] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.TellOutgoing] = (ChatSourceExt.All, ChatSourceExt.All),
},
Channel = InputChannel.Tell,
AllSenderMessages = true,
};
// Hellion default-tab presets used by the v10 wipe migration. Names are
// kept in HellionStrings (EN+DE) instead of Language.* so the upstream
// resource files stay untouched. Channel selections cover the channels
// a typical Eorzea raider uses without forcing the user to hand-tick
// each box on first start.
public static Tab HellionFreeCompany => new()
{
Name = HellionStrings.Tabs_Presets_FreeCompany,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab HellionFreeCompany =>
new()
{
[ChatType.FreeCompany] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
},
Channel = InputChannel.FreeCompany,
};
Name = HellionStrings.Tabs_Presets_FreeCompany,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.FreeCompany] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
},
Channel = InputChannel.FreeCompany,
};
public static Tab HellionParty => new()
{
Name = HellionStrings.Tabs_Presets_Party,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab HellionParty =>
new()
{
[ChatType.Party] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossParty] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Alliance] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeam] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
},
// No automatic input-channel switch; the Gruppe tab is a read
// surface that pulls in Party, CrossParty, Alliance and PvpTeam
// together. Auto-routing /party into this tab would surprise the
// user when they actually wanted /alliance or /pvpteam.
};
Name = HellionStrings.Tabs_Presets_Party,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.Party] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossParty] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Alliance] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeam] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
},
// No automatic input-channel switch; the Gruppe tab is a read
// surface that pulls in Party, CrossParty, Alliance and PvpTeam
// together. Auto-routing /party into this tab would surprise the
// user when they actually wanted /alliance or /pvpteam.
};
public static Tab HellionBeginner => new()
{
Name = HellionStrings.Tabs_Presets_Beginner,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab HellionBeginner =>
new()
{
[ChatType.NoviceNetwork] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
},
Channel = InputChannel.NoviceNetwork,
};
Name = HellionStrings.Tabs_Presets_Beginner,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.NoviceNetwork] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
},
Channel = InputChannel.NoviceNetwork,
};
public static Tab HellionSystem => new()
{
Name = HellionStrings.Tabs_Presets_System,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab HellionSystem =>
new()
{
// Plain system noise
[ChatType.Debug] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Urgent] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Notice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.System] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Error] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Echo] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.GatheringSystem] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.BattleSystem] = (ChatSourceExt.All, ChatSourceExt.All),
// Login / logout / announcement noise
[ChatType.NpcAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.RetainerSale] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PeriodicRecruitmentNotification] = (ChatSourceExt.All, ChatSourceExt.All),
// Gameplay-event streams (moved out of General in v1.0.0)
[ChatType.NpcDialogue] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Crafting] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Gathering] = (ChatSourceExt.All, ChatSourceExt.All),
// Misc
[ChatType.Progress] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.RandomNumber] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Orchestrion] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.MessageBook] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Alarm] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Sign] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.GlamourNotifications] = (ChatSourceExt.All, ChatSourceExt.All),
},
};
Name = HellionStrings.Tabs_Presets_System,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
// Plain system noise
[ChatType.Debug] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Urgent] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Notice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.System] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Error] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Echo] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.GatheringSystem] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.BattleSystem] = (ChatSourceExt.All, ChatSourceExt.All),
// Login / logout / announcement noise
[ChatType.NpcAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.RetainerSale] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PeriodicRecruitmentNotification] = (ChatSourceExt.All, ChatSourceExt.All),
// Gameplay-event streams (moved out of General in v1.0.0)
[ChatType.NpcDialogue] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Crafting] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Gathering] = (ChatSourceExt.All, ChatSourceExt.All),
// Misc
[ChatType.Progress] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.RandomNumber] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Orchestrion] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.MessageBook] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Alarm] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Sign] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.GlamourNotifications] = (ChatSourceExt.All, ChatSourceExt.All),
},
};
public static Tab HellionLinkshell => new()
{
Name = HellionStrings.Tabs_Presets_Linkshell,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
public static Tab HellionLinkshell =>
new()
{
[ChatType.Linkshell1] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell2] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell3] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell4] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell5] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell1] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell2] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell3] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell4] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell5] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
},
};
Name = HellionStrings.Tabs_Presets_Linkshell,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
[ChatType.Linkshell1] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell2] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell3] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell4] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell5] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Linkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell1] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell2] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell3] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell4] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell5] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.CrossLinkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
},
};
public static Dictionary<ChatType, (ChatSource, ChatSource)> MostlyPlayer => new()
{
public static Dictionary<ChatType, (ChatSource, ChatSource)> MostlyPlayer =>
new()
{
// Special
[ChatType.Debug] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Urgent] = (ChatSourceExt.All, ChatSourceExt.All),
@@ -208,5 +220,5 @@ public static class TabsUtil
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.RandomNumber] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.MessageBook] = (ChatSourceExt.All, ChatSourceExt.All),
};
};
}
+10 -8
View File
@@ -19,12 +19,13 @@ public static class Tokenizer
UrlString,
StringValue,
Leftover,
SequenceTerminator
SequenceTerminator,
}
public class Token(TokenType tokenType, string value)
{
public Token(TokenType tokenType) : this(tokenType, string.Empty) { }
public Token(TokenType tokenType)
: this(tokenType, string.Empty) { }
public TokenType TokenType { get; } = tokenType;
public string Value { get; } = value;
@@ -49,7 +50,7 @@ public static class Tokenizer
new TokenDefinition(TokenType.OpenParenthesis, "\\(", 1),
new TokenDefinition(TokenType.UrlString, UrlRegex, 1),
new TokenDefinition(TokenType.StringValue, "\\p{IsBasicLatin}", 2),
new TokenDefinition(TokenType.Leftover, ".", 3)
new TokenDefinition(TokenType.Leftover, ".", 3),
];
}
@@ -57,7 +58,8 @@ public static class Tokenizer
{
var tokenMatches = FindTokenMatches(lqlText);
var groupedByIndex = tokenMatches.GroupBy(x => x.StartIndex)
var groupedByIndex = tokenMatches
.GroupBy(x => x.StartIndex)
.OrderBy(x => x.Key)
.ToList();
@@ -97,7 +99,7 @@ public static class Tokenizer
{
Type = returnsToken;
Precedence = precedence;
Regex = new Regex(regexPattern, RegexOptions.IgnoreCase|RegexOptions.Compiled);
Regex = new Regex(regexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
public TokenDefinition(TokenType returnsToken, Regex regex, int precedence)
@@ -110,7 +112,7 @@ public static class Tokenizer
public IEnumerable<TokenMatch> FindMatches(string inputString)
{
var matches = Regex.Matches(inputString);
for(var i = 0; i < matches.Count; i++)
for (var i = 0; i < matches.Count; i++)
{
yield return new TokenMatch
{
@@ -118,7 +120,7 @@ public static class Tokenizer
EndIndex = matches[i].Index + matches[i].Length,
TokenType = Type,
Value = matches[i].Value,
Precedence = Precedence
Precedence = Precedence,
};
}
}
@@ -149,4 +151,4 @@ public static class Tokenizer
@"(?<URL>((https?:\/\/|www\.)[a-z0-9-]+(\.[a-z0-9-]+)*|([a-z0-9-]+(\.[a-z0-9-]+)*\.(com|net|org|co|io|app)))(:[\d]{1,5})?(\/[^\s]*)?)",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
);
}
}
+11 -4
View File
@@ -1,5 +1,5 @@
using HellionChat.Resources;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification;
using HellionChat.Resources;
namespace HellionChat.Util;
@@ -7,7 +7,14 @@ public static class WrapperUtil
{
public static void AddNotification(string content, NotificationType type, bool minimized = true)
{
Plugin.Notification.AddNotification(new Notification { Content = content, Type = type, Minimized = minimized });
Plugin.Notification.AddNotification(
new Notification
{
Content = content,
Type = type,
Minimized = minimized,
}
);
}
public static void TryOpenUri(Uri uri)
@@ -23,4 +30,4 @@ public static class WrapperUtil
AddNotification(Language.Context_OpenInBrowserError, NotificationType.Error);
}
}
}
}
@@ -16,7 +16,11 @@ namespace HellionChat._Helpers;
// TEST-MIRROR: ../../../Hellion Build test/Ui/CompactInputHistoryNavigatorTests.cs
public static class CompactInputHistoryNavigator
{
public enum Direction { Up, Down }
public enum Direction
{
Up,
Down,
}
// replacement == null means: caller must NOT touch the buffer. This
// distinguishes "cursor unchanged, leave the user's typing alone"
@@ -27,7 +31,8 @@ public static class CompactInputHistoryNavigator
string currentBuffer,
Func<int> getCount,
Action<string> push,
Func<int, string?> getByCursor)
Func<int, string?> getByCursor
)
{
ArgumentNullException.ThrowIfNull(getCount);
ArgumentNullException.ThrowIfNull(push);
+107 -107
View File
@@ -1,110 +1,110 @@
{
"version": 1,
"dependencies": {
"net10.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[15.0.0, )",
"resolved": "15.0.0",
"contentHash": "411vwC8/X8Z/sQ2TI6v3SvOn66xFPeOjFn3Zn+h0d3Ox2t1kFm66AhDvmx/qcMwVrR+Hidxj0dadpQ2dgyXMBQ=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.2.39, )",
"resolved": "1.2.39",
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
},
"MessagePack": {
"type": "Direct",
"requested": "[3.1.4, 4.0.0)",
"resolved": "3.1.4",
"contentHash": "BH0wlHWmVoZpbAPyyt2Awbq30C+ZsS3eHSkYdnyUAbqVJ22fAJDzn2xTieBeoT5QlcBzp61vHcv878YJGfi3mg==",
"dependencies": {
"MessagePack.Annotations": "3.1.4",
"MessagePackAnalyzer": "3.1.4",
"Microsoft.NET.StringTools": "17.11.4"
"version": 1,
"dependencies": {
"net10.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[15.0.0, )",
"resolved": "15.0.0",
"contentHash": "411vwC8/X8Z/sQ2TI6v3SvOn66xFPeOjFn3Zn+h0d3Ox2t1kFm66AhDvmx/qcMwVrR+Hidxj0dadpQ2dgyXMBQ=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.2.39, )",
"resolved": "1.2.39",
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
},
"MessagePack": {
"type": "Direct",
"requested": "[3.1.4, 4.0.0)",
"resolved": "3.1.4",
"contentHash": "BH0wlHWmVoZpbAPyyt2Awbq30C+ZsS3eHSkYdnyUAbqVJ22fAJDzn2xTieBeoT5QlcBzp61vHcv878YJGfi3mg==",
"dependencies": {
"MessagePack.Annotations": "3.1.4",
"MessagePackAnalyzer": "3.1.4",
"Microsoft.NET.StringTools": "17.11.4"
}
},
"Microsoft.Data.Sqlite": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "DZ6G2QuyPrsh5VS+wfiZbNBtYT6p+CkxXjD0aZHF04xso7QsG/uk0JpG30hzYlK6u/wtTzta1Dqfgbc/Sl2sDA==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "10.0.7",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.11",
"SQLitePCLRaw.core": "2.1.11"
}
},
"morelinq": {
"type": "Direct",
"requested": "[4.4.0, )",
"resolved": "4.4.0",
"contentHash": "QX3bsK9oFeUXk8tFsc9NkI6NnCr8Ar/ex027p+ZZ/jdLCdX2RlryDtxUqZW5j45NVwn4E4Z4hzupsoMQd6Yxtg=="
},
"Pidgin": {
"type": "Direct",
"requested": "[3.5.1, 4.0.0)",
"resolved": "3.5.1",
"contentHash": "zU7tkXlF3D6d2GLTjJDomAL3nnl4AwfZvSgSz8D4b+Ry21/clqedYlxBnEAkAU/bkGfEv6uRR7QCdWZUpKrB/g=="
},
"SixLabors.ImageSharp": {
"type": "Direct",
"requested": "[3.1.12, 4.0.0)",
"resolved": "3.1.12",
"contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A=="
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Direct",
"requested": "[3.50.3, )",
"resolved": "3.50.3",
"contentHash": "tVyhqQ8wxgedWiiPFChyZhE8I3PkOM/AE1azsj1qsdYUws13ONBFyi3aDxju4tD2kzedB2q5+50WrTyY0h2gMQ=="
},
"MessagePack.Annotations": {
"type": "Transitive",
"resolved": "3.1.4",
"contentHash": "aVWrDAkCdqxwQsz/q0ldPh2EFn48M99YUzE9OvZjMq2RNLKz4o2z88iGFvSvbMqOWRweRvKPHBJZe22PRqzslQ=="
},
"MessagePackAnalyzer": {
"type": "Transitive",
"resolved": "3.1.4",
"contentHash": "CTaSsN/liJ7MhLCAB7Z4ZLBNuVGCq9lt2BT/cbrc9vzGv89yK3CqIA+z9T19a11eQYl9etZHL6MQJgCqECRVpg=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "10.0.7",
"contentHash": "xVrtBg3M1wJlBDkoT0dXEYB/wSc8bIHJPYtw/bu1AqpWgF79uPSs87DAhERR/Ilumre6TKZa1cjMg3VUUObVLA==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.11"
}
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
"resolved": "17.11.4",
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.11",
"contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.11",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.11"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.11",
"contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.11",
"contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.11"
}
}
}
},
"Microsoft.Data.Sqlite": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "DZ6G2QuyPrsh5VS+wfiZbNBtYT6p+CkxXjD0aZHF04xso7QsG/uk0JpG30hzYlK6u/wtTzta1Dqfgbc/Sl2sDA==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "10.0.7",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.11",
"SQLitePCLRaw.core": "2.1.11"
}
},
"morelinq": {
"type": "Direct",
"requested": "[4.4.0, )",
"resolved": "4.4.0",
"contentHash": "QX3bsK9oFeUXk8tFsc9NkI6NnCr8Ar/ex027p+ZZ/jdLCdX2RlryDtxUqZW5j45NVwn4E4Z4hzupsoMQd6Yxtg=="
},
"Pidgin": {
"type": "Direct",
"requested": "[3.5.1, 4.0.0)",
"resolved": "3.5.1",
"contentHash": "zU7tkXlF3D6d2GLTjJDomAL3nnl4AwfZvSgSz8D4b+Ry21/clqedYlxBnEAkAU/bkGfEv6uRR7QCdWZUpKrB/g=="
},
"SixLabors.ImageSharp": {
"type": "Direct",
"requested": "[3.1.12, 4.0.0)",
"resolved": "3.1.12",
"contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A=="
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Direct",
"requested": "[3.50.3, )",
"resolved": "3.50.3",
"contentHash": "tVyhqQ8wxgedWiiPFChyZhE8I3PkOM/AE1azsj1qsdYUws13ONBFyi3aDxju4tD2kzedB2q5+50WrTyY0h2gMQ=="
},
"MessagePack.Annotations": {
"type": "Transitive",
"resolved": "3.1.4",
"contentHash": "aVWrDAkCdqxwQsz/q0ldPh2EFn48M99YUzE9OvZjMq2RNLKz4o2z88iGFvSvbMqOWRweRvKPHBJZe22PRqzslQ=="
},
"MessagePackAnalyzer": {
"type": "Transitive",
"resolved": "3.1.4",
"contentHash": "CTaSsN/liJ7MhLCAB7Z4ZLBNuVGCq9lt2BT/cbrc9vzGv89yK3CqIA+z9T19a11eQYl9etZHL6MQJgCqECRVpg=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "10.0.7",
"contentHash": "xVrtBg3M1wJlBDkoT0dXEYB/wSc8bIHJPYtw/bu1AqpWgF79uPSs87DAhERR/Ilumre6TKZa1cjMg3VUUObVLA==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.11"
}
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
"resolved": "17.11.4",
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.11",
"contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.11",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.11"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.11",
"contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.11",
"contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.11"
}
}
}
}
}
}