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);
}