diff --git a/XivCommon/Functions/Talk.cs b/XivCommon/Functions/Talk.cs
new file mode 100755
index 0000000..a3a97b6
--- /dev/null
+++ b/XivCommon/Functions/Talk.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Runtime.InteropServices;
+using Dalamud.Game;
+using Dalamud.Game.Text.SeStringHandling;
+using Dalamud.Hooking;
+using Dalamud.Plugin;
+
+namespace XivCommon.Functions {
+ ///
+ /// Class containing Talk events
+ ///
+ public class Talk : IDisposable {
+ // Updated: 5.5
+ private const int TextOffset = 0;
+ private const int NameOffset = 0x10;
+ private const int StyleOffset = 0x38;
+
+ private SeStringManager SeStringManager { get; }
+
+ private delegate void AddonTalkV45Delegate(IntPtr addon, IntPtr a2, IntPtr data);
+
+ private Hook? AddonTalkV45Hook { get; }
+
+ private delegate IntPtr SetAtkValueStringDelegate(IntPtr atkValue, IntPtr text);
+
+ private SetAtkValueStringDelegate SetAtkValueString { get; }
+
+ ///
+ /// The delegate for Talk events.
+ ///
+ public delegate void TalkEventDelegate(ref SeString name, ref SeString text, ref TalkStyle style);
+
+ ///
+ ///
+ /// The event that is fired when NPCs talk.
+ ///
+ ///
+ /// Requires the hook to be enabled.
+ ///
+ ///
+ public event TalkEventDelegate? OnTalk;
+
+ internal Talk(SigScanner scanner, SeStringManager manager, bool hooksEnabled) {
+ this.SeStringManager = manager;
+
+ var setAtkPtr = scanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
+ this.SetAtkValueString = Marshal.GetDelegateForFunctionPointer(setAtkPtr);
+
+ if (!hooksEnabled) {
+ return;
+ }
+
+ var showMessageBoxPtr = scanner.ScanText("4C 8B DC 55 57 41 55 49 8D 6B 98");
+ this.AddonTalkV45Hook = new Hook(showMessageBoxPtr, new AddonTalkV45Delegate(this.AddonTalkV45Detour));
+ this.AddonTalkV45Hook.Enable();
+ }
+
+ ///
+ public void Dispose() {
+ this.AddonTalkV45Hook?.Dispose();
+ }
+
+ private void AddonTalkV45Detour(IntPtr addon, IntPtr a2, IntPtr data) {
+ if (this.OnTalk == null) {
+ this.AddonTalkV45Hook!.Original(addon, a2, data);
+ return;
+ }
+
+ var rawName = Util.ReadTerminated(Marshal.ReadIntPtr(data + NameOffset + 8));
+ var rawText = Util.ReadTerminated(Marshal.ReadIntPtr(data + TextOffset + 8));
+ var style = (TalkStyle) Marshal.ReadByte(data + StyleOffset);
+
+ var name = this.SeStringManager.Parse(rawName);
+ var text = this.SeStringManager.Parse(rawText);
+
+ try {
+ this.OnTalk?.Invoke(ref name, ref text, ref style);
+ } catch (Exception ex) {
+ PluginLog.LogError(ex, "Exception in Talk detour");
+ }
+
+ var newName = name.Encode().Terminate();
+ var newText = text.Encode().Terminate();
+
+ Marshal.WriteByte(data + StyleOffset, (byte) style);
+
+ unsafe {
+ fixed (byte* namePtr = newName, textPtr = newText) {
+ this.SetAtkValueString(data + NameOffset, (IntPtr) namePtr);
+ this.SetAtkValueString(data + TextOffset, (IntPtr) textPtr);
+ this.AddonTalkV45Hook!.Original(addon, a2, data);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Talk window styles.
+ ///
+ public enum TalkStyle : byte {
+ ///
+ /// The normal style with a white background.
+ ///
+ Normal = 0,
+
+ ///
+ /// A style with lights on the top and bottom border.
+ ///
+ Lights = 2,
+
+ ///
+ /// A style used for when characters are shouting.
+ ///
+ Shout = 3,
+
+ ///
+ /// Like but with flatter edges.
+ ///
+ FlatShout = 4,
+
+ ///
+ /// The style used when dragons (and some other NPCs) talk.
+ ///
+ Dragon = 5,
+
+ ///
+ /// The style used for Allagan machinery.
+ ///
+ Allagan = 6,
+
+ ///
+ /// The style used for system messages.
+ ///
+ System = 7,
+
+ ///
+ /// A mixture of the system message style and the dragon style.
+ ///
+ DragonSystem = 8,
+
+ ///
+ /// The system message style with a purple background.
+ ///
+ PurpleSystem = 9,
+ }
+}
diff --git a/XivCommon/GameFunctions.cs b/XivCommon/GameFunctions.cs
index b0d39ed..92c2cdc 100755
--- a/XivCommon/GameFunctions.cs
+++ b/XivCommon/GameFunctions.cs
@@ -28,17 +28,27 @@ namespace XivCommon {
///
public Examine Examine { get; }
+ ///
+ /// Talk events
+ ///
+ public Talk Talk { get; }
+
internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) {
this.Interface = @interface;
- this.Chat = new Chat(this, @interface.TargetModuleScanner);
- this.PartyFinder = new PartyFinder(@interface.TargetModuleScanner, hooks.HasFlag(Hooks.PartyFinder));
- this.BattleTalk = new BattleTalk(this, @interface.TargetModuleScanner, @interface.SeStringManager, hooks.HasFlag(Hooks.BattleTalk));
- this.Examine = new Examine(this, @interface.TargetModuleScanner);
+ var scanner = @interface.TargetModuleScanner;
+ var seStringManager = @interface.SeStringManager;
+
+ this.Chat = new Chat(this, scanner);
+ this.PartyFinder = new PartyFinder(scanner, hooks.HasFlag(Hooks.PartyFinder));
+ this.BattleTalk = new BattleTalk(this, scanner, seStringManager, hooks.HasFlag(Hooks.BattleTalk));
+ this.Examine = new Examine(this, scanner);
+ this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk));
}
///
public void Dispose() {
+ this.Talk.Dispose();
this.BattleTalk.Dispose();
this.PartyFinder.Dispose();
}
diff --git a/XivCommon/Hooks.cs b/XivCommon/Hooks.cs
index 8711b01..4701a94 100755
--- a/XivCommon/Hooks.cs
+++ b/XivCommon/Hooks.cs
@@ -26,6 +26,13 @@ namespace XivCommon {
/// This hook is used in order to enable all Party Finder functions.
///
PartyFinder,
+
+ ///
+ /// The Talk hooks.
+ ///
+ /// This hook is used in order to enable the Talk events.
+ ///
+ Talk,
}
internal static class HooksExt {