diff --git a/XIVChatPlugin/ILRepack.targets b/XIVChatPlugin/ILRepack.targets index 6a1e531..26dc630 100644 --- a/XIVChatPlugin/ILRepack.targets +++ b/XIVChatPlugin/ILRepack.targets @@ -6,6 +6,7 @@ diff --git a/XIVChatPlugin/NativeTools.cs b/XIVChatPlugin/NativeTools.cs new file mode 100644 index 0000000..17f450a --- /dev/null +++ b/XIVChatPlugin/NativeTools.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace XIVChatPlugin { + public static unsafe class NativeTools { + [StructLayout(LayoutKind.Sequential)] + private readonly struct RawVec { + public readonly byte** pointer; + public readonly uint length; + private readonly uint capacity; + } + + [DllImport("xivchat_native_tools.dll")] + private static extern RawVec* wrap(byte* input, uint width); + + [DllImport("xivchat_native_tools.dll")] + private static extern void wrap_free(RawVec* raw); + + public static IEnumerable Wrap(string input, uint width) { + RawVec* raw; + fixed (byte* ptr = Encoding.UTF8.GetBytes(input).Terminate()) { + raw = wrap(ptr, width); + } + + if (raw == null) { + return Array.Empty(); + } + + var strings = new List((int) raw->length); + for (var i = 0; i < raw->length; i++) { + var bytes = Util.ReadTerminated(raw->pointer[i]); + strings.Add(Encoding.UTF8.GetString(bytes)); + } + + wrap_free(raw); + + return strings; + } + } +} diff --git a/XIVChatPlugin/Resources/lib/libsodium.dll b/XIVChatPlugin/Resources/lib/libsodium.dll index 1e9df39..c5d2516 100644 Binary files a/XIVChatPlugin/Resources/lib/libsodium.dll and b/XIVChatPlugin/Resources/lib/libsodium.dll differ diff --git a/XIVChatPlugin/Resources/lib/xivchat_native_tools.dll b/XIVChatPlugin/Resources/lib/xivchat_native_tools.dll new file mode 100644 index 0000000..c44264d Binary files /dev/null and b/XIVChatPlugin/Resources/lib/xivchat_native_tools.dll differ diff --git a/XIVChatPlugin/Server.cs b/XIVChatPlugin/Server.cs index 790f675..e7fc3b5 100644 --- a/XIVChatPlugin/Server.cs +++ b/XIVChatPlugin/Server.cs @@ -6,6 +6,7 @@ using Sodium; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; @@ -23,8 +24,25 @@ using XIVChatCommon.Message.Server; namespace XIVChatPlugin { public class Server : IDisposable { + private const int MaxMessageLength = 500; + + private static readonly string[] PublicPrefixes = { + "/t ", + "/tell ", + "/reply ", + "/r ", + "/say ", + "/s ", + "/shout ", + "/sh ", + "/yell ", + "/y ", + }; + private readonly Plugin _plugin; + private readonly Stopwatch _sendWatch = new(); + private readonly CancellationTokenSource _tokenSource = new(); private readonly ConcurrentQueue _toGame = new(); @@ -50,11 +68,13 @@ namespace XIVChatPlugin { private const int MaxMessageSize = 128_000; public Server(Plugin plugin) { - this._plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null"); + this._plugin = plugin; if (this._plugin.Config.KeyPair == null) { this.RegenerateKeyPair(); } + this._sendWatch.Start(); + this._plugin.Functions.ReceiveFriendList += this.OnReceiveFriendList; } @@ -194,7 +214,7 @@ namespace XIVChatPlugin { if (sender.Payloads.Count > 0) { var format = this.FormatFor(chatCode.Type); - if (format != null && format.IsPresent) { + if (format is { IsPresent: true }) { chunks.Add(new TextChunk(format.Before) { FallbackColour = colour, }); @@ -251,10 +271,25 @@ namespace XIVChatPlugin { client.Queue.Writer.TryWrite(new Availability(available)); } + int time; + if (this._toGame.TryPeek(out var peek) && PublicPrefixes.Any(prefix => peek.StartsWith(prefix))) { + time = 1_000; + } else if (this._currentChannel is InputChannel.Tell or InputChannel.Say or InputChannel.Shout or InputChannel.Yell) { + time = 1_000; + } else { + time = 250; + } + + if (this._sendWatch.Elapsed < TimeSpan.FromMilliseconds(time)) { + return; + } + if (!this._toGame.TryDequeue(out var message)) { return; } + this._sendWatch.Restart(); + this._plugin.Functions.ProcessChatBox(message); } @@ -575,7 +610,7 @@ namespace XIVChatPlugin { chunks.Add(new TextChunk(text) { FallbackColour = defaultColour, Foreground = foreground.Count > 0 ? foreground.Peek() : (uint?) null, - Glow = glow.Count > 0 ? glow.Peek() : (uint?) null, + Glow = glow.Count > 0 ? glow.Peek() : null, Italic = italic, }); } @@ -643,9 +678,7 @@ namespace XIVChatPlugin { private IEnumerable MessagesAfter(DateTime time) => this._backlog.Where(msg => msg.Timestamp > time).ToArray(); private static IEnumerable Wrap(string input) { - const int limit = 500; - - if (input.Length <= limit) { + if (input.Length <= MaxMessageLength) { return new[] { input, }; @@ -656,59 +689,22 @@ namespace XIVChatPlugin { var space = input.IndexOf(' '); if (space != -1) { prefix = input.Substring(0, space); - input = input.Substring(space + 1); - } - } - - var parts = new List(); - - var builder = new StringBuilder(limit); - - foreach (var word in input.Split(' ')) { - if (word.Length > limit) { - var wordParts = (int) Math.Ceiling((float) word.Length / limit); - for (var i = 0; i < wordParts; i++) { - var start = i == 0 ? 0 : (i * limit); - var partLength = limit; - if (prefix.Length != 0) { - start = start == 0 ? 0 : (start - (prefix.Length + 1) * i); - partLength = partLength - prefix.Length - 1; + // handle wrapping tells + if (prefix is "/tell" or "/t") { + var tellSpace = input.IndexOfCount(' ', 3); + if (tellSpace != -1) { + prefix = input.Substring(0, tellSpace); + input = input.Substring(tellSpace + 1); } - - var part = word.Length - start < partLength ? word.Substring(start) : word.Substring(start, partLength); - if (part.Length == 0) { - continue; - } - - if (prefix.Length != 0) { - part = prefix + " " + part; - } - - parts.Add(part); + } else { + input = input.Substring(space + 1); } - - continue; } - - if (builder.Length + word.Length > limit) { - parts.Add(builder.ToString().TrimEnd(' ')); - builder.Clear(); - } - - if (builder.Length == 0 && prefix.Length != 0) { - builder.Append(prefix); - builder.Append(' '); - } - - builder.Append(word); - builder.Append(' '); } - if (builder.Length != 0) { - parts.Add(builder.ToString().TrimEnd(' ')); - } - - return parts.ToArray(); + return NativeTools.Wrap(input, MaxMessageLength) + .Select(text => $"{prefix} {text}") + .ToArray(); } private void BroadcastMessage(Encodable message) { diff --git a/XIVChatPlugin/Util.cs b/XIVChatPlugin/Util.cs new file mode 100644 index 0000000..08d06cc --- /dev/null +++ b/XIVChatPlugin/Util.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace XIVChatPlugin { + internal static class Util { + public static int IndexOfCount(this string source, char toFind, int position) { + var index = -1; + for (var i = 0; i < position; i++) { + index = source.IndexOf(toFind, index + 1); + + if (index == -1) { + return -1; + } + } + + return index; + } + + public static byte[] Terminate(this byte[] bytes) { + var terminated = new byte[bytes.Length + 1]; + Array.Copy(bytes, terminated, bytes.Length); + terminated[terminated.Length - 1] = 0; + return terminated; + } + + public static unsafe byte[] ReadTerminated(byte* mem) { + var bytes = new List(); + while (*mem != 0) { + bytes.Add(*mem); + mem += 1; + } + + return bytes.ToArray(); + } + } +} diff --git a/XIVChatPlugin/XIVChatPlugin.csproj b/XIVChatPlugin/XIVChatPlugin.csproj index f393c71..b253962 100644 --- a/XIVChatPlugin/XIVChatPlugin.csproj +++ b/XIVChatPlugin/XIVChatPlugin.csproj @@ -6,25 +6,26 @@ enable latest 1.5.1 + true - + $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll False - + $(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll False - + $(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll False - + $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll False - + $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll False @@ -40,7 +41,8 @@ - + +