From 3a3d6b6e6ad33d135615f8f11673e233657922e5 Mon Sep 17 00:00:00 2001 From: Infi Date: Tue, 19 Nov 2024 03:05:28 +0100 Subject: [PATCH] Implement Print methods that work with Lumina SeString/ReadOnlySeString (#2106) * Implement Print methods that work with Lumina SeString/ReadOnlySeString * null terminate before passing to Utf8String * Rename XivChatEntryReadOnly to XivChatEntryRaw * Fix error from wrong conversion method * Follow Rider suggestion * Switch from AppendMacroString to BeginMacro for optimization * More optimization suggested by kizer * More kizer suggested optimizations * Fix small mistake * Use XivChatEntry and read/write to Byte fields accordingly --- Dalamud/Game/Gui/ChatGui.cs | 117 ++++++++++++++++++---------- Dalamud/Game/Text/XivChatEntry.cs | 22 +++++- Dalamud/Plugin/Services/IChatGui.cs | 24 +++++- 3 files changed, 118 insertions(+), 45 deletions(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index fefc82790..84622c5e8 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -8,9 +8,11 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Memory; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -20,6 +22,8 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text.Payloads; + using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType; using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType; @@ -107,6 +111,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.handleLinkClickHook.Dispose(); } + #region DalamudSeString + /// public void Print(XivChatEntry chat) { @@ -137,6 +143,24 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); } + #endregion + + #region LuminaSeString + + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor); + } + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); + } + + #endregion + /// /// Process a chat queue. /// @@ -145,30 +169,35 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui while (this.chatQueue.Count > 0) { var chat = this.chatQueue.Dequeue(); - var replacedMessage = new SeStringBuilder(); + var sb = LuminaSeStringBuilder.SharedPool.Get(); + var rosss = (ReadOnlySeStringSpan)chat.MessageBytes; - // Normalize Unicode NBSP to the built-in one, as the former won't renderl - foreach (var payload in chat.Message.Payloads) + foreach (var payload in rosss) { - if (payload is TextPayload { Text: not null } textPayload) + if (payload.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (payload.Type != ReadOnlySePayloadType.Text) { - var split = textPayload.Text.Split("\u202f"); // NARROW NO-BREAK SPACE - for (var i = 0; i < split.Length; i++) - { - replacedMessage.AddText(split[i]); - if (i + 1 < split.Length) - replacedMessage.Add(new RawPayload([0x02, (byte)Lumina.Text.Payloads.PayloadType.Indent, 0x01, 0x03])); - } + sb.Append(payload); + continue; } - else + + foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default)) { - replacedMessage.Add(payload); + if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); + else + sb.Append(c.EffectiveChar); } } - var sender = Utf8String.FromSequence(chat.Name.Encode()); - var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode()); - + var output = sb.ToArray(); + LuminaSeStringBuilder.SharedPool.Return(sb); + + var sender = Utf8String.FromSequence(chat.NameBytes.NullTerminate()); + var message = Utf8String.FromSequence(output.NullTerminate()); + var targetChannel = chat.Type ?? this.configuration.GeneralChatType; this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); @@ -228,29 +257,6 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } } - private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color) - { - var builder = new SeStringBuilder(); - - if (!tag.IsNullOrEmpty()) - { - if (color is not null) - { - builder.AddUiForeground($"[{tag}] ", color.Value); - } - else - { - builder.AddText($"[{tag}] "); - } - } - - this.Print(new XivChatEntry - { - Message = builder.AddText(message).Build(), - Type = channel, - }); - } - private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color) { var builder = new SeStringBuilder(); @@ -274,6 +280,31 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui }); } + private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) + { + var builder = new LuminaSeStringBuilder(); + + if (!tag.IsNullOrEmpty()) + { + if (color is not null) + { + builder.PushColorType(color.Value); + builder.Append($"[{tag}] "); + builder.PopColorType(); + } + else + { + builder.Append($"[{tag}] "); + } + } + + this.Print(new XivChatEntry + { + MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(), + Type = channel, + }); + } + private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) { this.inventoryItemCopyHook.Original(thisPtr, otherPtr); @@ -505,6 +536,14 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null) => this.chatGuiService.PrintError(message, messageTag, tagColor); + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.Print(message, messageTag, tagColor); + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.PrintError(message, messageTag, tagColor); + private void OnMessageForward(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) => this.ChatMessage?.Invoke(type, timestamp, ref sender, ref message, ref isHandled); diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index eb40d6636..7932ead72 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -20,12 +20,30 @@ public sealed class XivChatEntry /// /// Gets or sets the sender name. /// - public SeString Name { get; set; } = string.Empty; + public SeString Name + { + get => SeString.Parse(this.NameBytes); + set => this.NameBytes = value.Encode(); + } /// /// Gets or sets the message. /// - public SeString Message { get; set; } = string.Empty; + public SeString Message + { + get => SeString.Parse(this.MessageBytes); + set => this.MessageBytes = value.Encode(); + } + + /// + /// Gets or Sets the name payloads + /// + public byte[] NameBytes { get; set; } = []; + + /// + /// Gets or Sets the message payloads. + /// + public byte[] MessageBytes { get; set; } = []; /// /// Gets or sets a value indicating whether new message sounds should be silenced or not. diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 42bbd6b06..3f221b3bb 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -48,7 +48,7 @@ public interface IChatGui /// The sender name. /// The message sent. public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message); - + /// /// Event that will be fired when a chat message is sent to chat by the game. /// @@ -68,17 +68,17 @@ public interface IChatGui /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. /// public event OnMessageUnhandledDelegate ChatMessageUnhandled; - + /// /// Gets the ID of the last linked item. /// public uint LastLinkedItemId { get; } - + /// /// Gets the flags of the last linked item. /// public byte LastLinkedItemFlags { get; } - + /// /// Gets the dictionary of Dalamud Link Handlers. /// @@ -121,4 +121,20 @@ public interface IChatGui /// String to prepend message with "[messageTag] ". /// Color to display the message tag with. public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); }