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 @@
-
+
+