feat: add some basic chat bubbles support
This commit is contained in:
parent
57be9cd299
commit
2dea5ce9cc
162
XivCommon/Functions/ChatBubbles.cs
Executable file
162
XivCommon/Functions/ChatBubbles.cs
Executable file
@ -0,0 +1,162 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
namespace XivCommon.Functions {
|
||||||
|
/// <summary>
|
||||||
|
/// Class containing chat bubble events and functions
|
||||||
|
/// </summary>
|
||||||
|
public class ChatBubbles : IDisposable {
|
||||||
|
private Dalamud.Dalamud Dalamud { get; }
|
||||||
|
private SeStringManager SeStringManager { get; }
|
||||||
|
|
||||||
|
private delegate void OpenChatBubbleDelegate(IntPtr manager, IntPtr actor, IntPtr text, byte a4);
|
||||||
|
|
||||||
|
private unsafe delegate void UpdateChatBubbleDelegate(ChatBubble* bubble, IntPtr actor, IntPtr a3);
|
||||||
|
|
||||||
|
private Hook<OpenChatBubbleDelegate>? OpenChatBubbleHook { get; }
|
||||||
|
|
||||||
|
private Hook<UpdateChatBubbleDelegate>? UpdateChatBubbleHook { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate for chat bubble events.
|
||||||
|
/// </summary>
|
||||||
|
public delegate void OnChatBubbleDelegate(ref Actor actor, ref SeString text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate for chat bubble update events.
|
||||||
|
/// </summary>
|
||||||
|
public delegate void OnUpdateChatBubbleDelegate(ref Actor actor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// The event that is fired when a chat bubble is shown.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Requires the <see cref="Hooks.ChatBubbles"/> hook to be enabled.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public event OnChatBubbleDelegate? OnChatBubble;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// The event that is fired when a chat bubble is updated.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Requires the <see cref="Hooks.ChatBubbles"/> hook to be enabled.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public event OnUpdateChatBubbleDelegate? OnUpdateBubble;
|
||||||
|
|
||||||
|
internal ChatBubbles(Dalamud.Dalamud dalamud, SigScanner scanner, SeStringManager manager, bool hookEnabled) {
|
||||||
|
this.Dalamud = dalamud;
|
||||||
|
this.SeStringManager = manager;
|
||||||
|
|
||||||
|
if (!hookEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var openPtr = scanner.ScanText("E8 ?? ?? ?? ?? 80 BF ?? ?? ?? ?? ?? C7 07 ?? ?? ?? ??");
|
||||||
|
this.OpenChatBubbleHook = new Hook<OpenChatBubbleDelegate>(openPtr, new OpenChatBubbleDelegate(this.OpenChatBubbleDetour));
|
||||||
|
this.OpenChatBubbleHook.Enable();
|
||||||
|
|
||||||
|
var updatePtr = scanner.ScanText("48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C");
|
||||||
|
unsafe {
|
||||||
|
this.UpdateChatBubbleHook = new Hook<UpdateChatBubbleDelegate>(updatePtr + 9, new UpdateChatBubbleDelegate(this.UpdateChatBubbleDetour));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UpdateChatBubbleHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose() {
|
||||||
|
this.OpenChatBubbleHook?.Dispose();
|
||||||
|
this.UpdateChatBubbleHook?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenChatBubbleDetour(IntPtr manager, IntPtr actor, IntPtr text, byte a4) {
|
||||||
|
try {
|
||||||
|
this.OpenChatBubbleDetourInner(manager, actor, text, a4);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Exception in chat bubble detour");
|
||||||
|
this.OpenChatBubbleHook!.Original(manager, actor, text, a4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenChatBubbleDetourInner(IntPtr manager, IntPtr actorPtr, IntPtr textPtr, byte a4) {
|
||||||
|
var actorStruct = Marshal.PtrToStructure<Dalamud.Game.ClientState.Structs.Actor>(actorPtr);
|
||||||
|
var actor = new Actor(actorPtr, actorStruct, this.Dalamud);
|
||||||
|
|
||||||
|
var rawText = Util.ReadTerminated(textPtr);
|
||||||
|
var text = this.SeStringManager.Parse(rawText);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.OnChatBubble?.Invoke(ref actor, ref text);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Exception in chat bubble event");
|
||||||
|
}
|
||||||
|
|
||||||
|
var newText = text.Encode().Terminate();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
fixed (byte* newTextPtr = newText) {
|
||||||
|
this.OpenChatBubbleHook!.Original(manager, actor.Address, (IntPtr) newTextPtr, a4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UpdateChatBubbleDetour(ChatBubble* bubble, IntPtr actor, IntPtr a3) {
|
||||||
|
try {
|
||||||
|
this.UpdateChatBubbleDetourInner(bubble, actor, a3);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Exception in update chat bubble detour");
|
||||||
|
this.UpdateChatBubbleHook!.Original(bubble, actor, a3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UpdateChatBubbleDetourInner(ChatBubble* bubble, IntPtr actorPtr, IntPtr a3) {
|
||||||
|
var actorStruct = Marshal.PtrToStructure<Dalamud.Game.ClientState.Structs.Actor>(actorPtr);
|
||||||
|
var actor = new Actor(actorPtr, actorStruct, this.Dalamud);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.OnUpdateBubble?.Invoke(ref actor);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Exception in chat bubble update event");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UpdateChatBubbleHook!.Original(bubble, actor.Address, a3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
|
||||||
|
internal unsafe struct ChatBubble {
|
||||||
|
[FieldOffset(0x0)]
|
||||||
|
internal readonly uint Id;
|
||||||
|
|
||||||
|
[FieldOffset(0x4)]
|
||||||
|
internal float Timer;
|
||||||
|
|
||||||
|
[FieldOffset(0x8)]
|
||||||
|
internal readonly uint Unk_8; // enum probably
|
||||||
|
|
||||||
|
[FieldOffset(0xC)]
|
||||||
|
internal ChatBubbleStatus Status; // state of the bubble
|
||||||
|
|
||||||
|
[FieldOffset(0x10)]
|
||||||
|
internal readonly byte* Text;
|
||||||
|
|
||||||
|
[FieldOffset(0x78)]
|
||||||
|
internal readonly ulong Unk_78; // check whats in memory here
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ChatBubbleStatus : uint {
|
||||||
|
GetData = 0,
|
||||||
|
On = 1,
|
||||||
|
Init = 2,
|
||||||
|
Off = 3,
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using XivCommon.Functions;
|
using XivCommon.Functions;
|
||||||
|
|
||||||
@ -33,21 +34,31 @@ namespace XivCommon {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Talk Talk { get; }
|
public Talk Talk { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat bubble functions and events
|
||||||
|
/// </summary>
|
||||||
|
public ChatBubbles ChatBubbles { get; }
|
||||||
|
|
||||||
internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) {
|
internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) {
|
||||||
this.Interface = @interface;
|
this.Interface = @interface;
|
||||||
|
|
||||||
var scanner = @interface.TargetModuleScanner;
|
var scanner = @interface.TargetModuleScanner;
|
||||||
var seStringManager = @interface.SeStringManager;
|
var seStringManager = @interface.SeStringManager;
|
||||||
|
|
||||||
|
var dalamudField = @interface.GetType().GetField("dalamud", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
var dalamud = (Dalamud.Dalamud) dalamudField!.GetValue(@interface);
|
||||||
|
|
||||||
this.Chat = new Chat(this, scanner);
|
this.Chat = new Chat(this, scanner);
|
||||||
this.PartyFinder = new PartyFinder(scanner, hooks.HasFlag(Hooks.PartyFinder));
|
this.PartyFinder = new PartyFinder(scanner, hooks.HasFlag(Hooks.PartyFinder));
|
||||||
this.BattleTalk = new BattleTalk(this, scanner, seStringManager, hooks.HasFlag(Hooks.BattleTalk));
|
this.BattleTalk = new BattleTalk(this, scanner, seStringManager, hooks.HasFlag(Hooks.BattleTalk));
|
||||||
this.Examine = new Examine(this, scanner);
|
this.Examine = new Examine(this, scanner);
|
||||||
this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk));
|
this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk));
|
||||||
|
this.ChatBubbles = new ChatBubbles(dalamud, scanner, seStringManager, hooks.HasFlag(Hooks.ChatBubbles));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
|
this.ChatBubbles.Dispose();
|
||||||
this.Talk.Dispose();
|
this.Talk.Dispose();
|
||||||
this.BattleTalk.Dispose();
|
this.BattleTalk.Dispose();
|
||||||
this.PartyFinder.Dispose();
|
this.PartyFinder.Dispose();
|
||||||
|
@ -33,6 +33,13 @@ namespace XivCommon {
|
|||||||
/// This hook is used in order to enable the Talk events.
|
/// This hook is used in order to enable the Talk events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Talk,
|
Talk,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The chat bubbles hooks.
|
||||||
|
///
|
||||||
|
/// This hook is used in order to enable the chat bubbles events.
|
||||||
|
/// </summary>
|
||||||
|
ChatBubbles,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class HooksExt {
|
internal static class HooksExt {
|
||||||
|
Loading…
Reference in New Issue
Block a user