feat: add talk

This commit is contained in:
Anna 2021-04-17 23:28:20 -04:00
parent f347e4e669
commit e2f66dd12b
3 changed files with 167 additions and 4 deletions

146
XivCommon/Functions/Talk.cs Executable file
View File

@ -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 {
/// <summary>
/// Class containing Talk events
/// </summary>
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<AddonTalkV45Delegate>? AddonTalkV45Hook { get; }
private delegate IntPtr SetAtkValueStringDelegate(IntPtr atkValue, IntPtr text);
private SetAtkValueStringDelegate SetAtkValueString { get; }
/// <summary>
/// The delegate for Talk events.
/// </summary>
public delegate void TalkEventDelegate(ref SeString name, ref SeString text, ref TalkStyle style);
/// <summary>
/// <para>
/// The event that is fired when NPCs talk.
/// </para>
/// <para>
/// Requires the <see cref="Hooks.Talk"/> hook to be enabled.
/// </para>
/// </summary>
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<SetAtkValueStringDelegate>(setAtkPtr);
if (!hooksEnabled) {
return;
}
var showMessageBoxPtr = scanner.ScanText("4C 8B DC 55 57 41 55 49 8D 6B 98");
this.AddonTalkV45Hook = new Hook<AddonTalkV45Delegate>(showMessageBoxPtr, new AddonTalkV45Delegate(this.AddonTalkV45Detour));
this.AddonTalkV45Hook.Enable();
}
/// <inheritdoc />
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);
}
}
}
}
/// <summary>
/// Talk window styles.
/// </summary>
public enum TalkStyle : byte {
/// <summary>
/// The normal style with a white background.
/// </summary>
Normal = 0,
/// <summary>
/// A style with lights on the top and bottom border.
/// </summary>
Lights = 2,
/// <summary>
/// A style used for when characters are shouting.
/// </summary>
Shout = 3,
/// <summary>
/// Like <see cref="Shout"/> but with flatter edges.
/// </summary>
FlatShout = 4,
/// <summary>
/// The style used when dragons (and some other NPCs) talk.
/// </summary>
Dragon = 5,
/// <summary>
/// The style used for Allagan machinery.
/// </summary>
Allagan = 6,
/// <summary>
/// The style used for system messages.
/// </summary>
System = 7,
/// <summary>
/// A mixture of the system message style and the dragon style.
/// </summary>
DragonSystem = 8,
/// <summary>
/// The system message style with a purple background.
/// </summary>
PurpleSystem = 9,
}
}

View File

@ -28,17 +28,27 @@ namespace XivCommon {
/// </summary>
public Examine Examine { get; }
/// <summary>
/// Talk events
/// </summary>
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));
}
/// <inheritdoc />
public void Dispose() {
this.Talk.Dispose();
this.BattleTalk.Dispose();
this.PartyFinder.Dispose();
}

View File

@ -26,6 +26,13 @@ namespace XivCommon {
/// This hook is used in order to enable all Party Finder functions.
/// </summary>
PartyFinder,
/// <summary>
/// The Talk hooks.
///
/// This hook is used in order to enable the Talk events.
/// </summary>
Talk,
}
internal static class HooksExt {