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
This commit is contained in:
Infi 2024-11-19 03:05:28 +01:00 committed by GitHub
parent bf7ef00ec0
commit 3a3d6b6e6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 45 deletions

View file

@ -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
/// <inheritdoc/>
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
/// <inheritdoc/>
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
}
/// <inheritdoc/>
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
}
#endregion
/// <summary>
/// Process a chat queue.
/// </summary>
@ -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<byte> 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);
/// <inheritdoc/>
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.Print(message, messageTag, tagColor);
/// <inheritdoc/>
public void PrintError(ReadOnlySpan<byte> 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);

View file

@ -20,12 +20,30 @@ public sealed class XivChatEntry
/// <summary>
/// Gets or sets the sender name.
/// </summary>
public SeString Name { get; set; } = string.Empty;
public SeString Name
{
get => SeString.Parse(this.NameBytes);
set => this.NameBytes = value.Encode();
}
/// <summary>
/// Gets or sets the message.
/// </summary>
public SeString Message { get; set; } = string.Empty;
public SeString Message
{
get => SeString.Parse(this.MessageBytes);
set => this.MessageBytes = value.Encode();
}
/// <summary>
/// Gets or Sets the name payloads
/// </summary>
public byte[] NameBytes { get; set; } = [];
/// <summary>
/// Gets or Sets the message payloads.
/// </summary>
public byte[] MessageBytes { get; set; } = [];
/// <summary>
/// Gets or sets a value indicating whether new message sounds should be silenced or not.

View file

@ -48,7 +48,7 @@ public interface IChatGui
/// <param name="sender">The sender name.</param>
/// <param name="message">The message sent.</param>
public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message);
/// <summary>
/// Event that will be fired when a chat message is sent to chat by the game.
/// </summary>
@ -68,17 +68,17 @@ public interface IChatGui
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
/// </summary>
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
/// <summary>
/// Gets the ID of the last linked item.
/// </summary>
public uint LastLinkedItemId { get; }
/// <summary>
/// Gets the flags of the last linked item.
/// </summary>
public byte LastLinkedItemFlags { get; }
/// <summary>
/// Gets the dictionary of Dalamud Link Handlers.
/// </summary>
@ -121,4 +121,20 @@ public interface IChatGui
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
/// <param name="tagColor">Color to display the message tag with.</param>
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null);
/// <summary>
/// Queue a chat message. Dalamud will send queued messages on the next framework event.
/// </summary>
/// <param name="message">A message to send.</param>
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
/// <param name="tagColor">Color to display the message tag with.</param>
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null);
/// <summary>
/// Queue a chat message. Dalamud will send queued messages on the next framework event.
/// </summary>
/// <param name="message">A message to send.</param>
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
/// <param name="tagColor">Color to display the message tag with.</param>
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null);
}