diff --git a/Peeping Tom/Configuration.cs b/Peeping Tom/Configuration.cs index 6e16c2e..82f3dd0 100644 --- a/Peeping Tom/Configuration.cs +++ b/Peeping Tom/Configuration.cs @@ -34,6 +34,8 @@ namespace PeepingTom { public bool LogSelf { get; set; } = false; public bool FocusTargetOnHover { get; set; } = true; + public bool OpenExamine { get; set; } = false; + public bool PlaySoundOnTarget { get; set; } = false; public string SoundPath { get; set; } = null; public float SoundVolume { get; set; } = 1f; diff --git a/Peeping Tom/GameFunctions.cs b/Peeping Tom/GameFunctions.cs new file mode 100644 index 0000000..c9e1fee --- /dev/null +++ b/Peeping Tom/GameFunctions.cs @@ -0,0 +1,59 @@ +using Dalamud.Game.ClientState.Actors.Types; +using Dalamud.Plugin; +using System; +using System.Linq; +using System.Runtime.InteropServices; + +namespace PeepingTom { + public class GameFunctions { + private delegate IntPtr GetListDelegate(IntPtr basePtr); + private delegate long RequestCharInfoDelegate(IntPtr ptr); + + private readonly PeepingTomPlugin plugin; + + private readonly RequestCharInfoDelegate _requestCharInfo = null; + + public GameFunctions(PeepingTomPlugin plugin) { + this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null"); + + IntPtr rciPtr = this.plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 40 BA 1B 01 00 00"); + if (rciPtr == IntPtr.Zero) { + PluginLog.Log("Could not find the signature for the examine window function - will not be able to open examine window."); + return; + } + + this._requestCharInfo = Marshal.GetDelegateForFunctionPointer(rciPtr); + } + + private static IntPtr FollowPtrChain(IntPtr start, int[] offsets) { + foreach (int offset in offsets) { + start = Marshal.ReadIntPtr(start, offset); + if (start == IntPtr.Zero) { + break; + } + } + + return start; + } + + public void OpenExamineWindow(Actor actor) { + if (this._requestCharInfo == null) { + return; + } + + IntPtr framework = this.plugin.Interface.Framework.Address.BaseAddress; + + IntPtr getListPtr = FollowPtrChain(framework, new int[] { 0x29f8, 0, 0x110 }); + var getList = Marshal.GetDelegateForFunctionPointer(getListPtr); + IntPtr list = getList(Marshal.ReadIntPtr(framework + 0x29f8)); + IntPtr rciData = Marshal.ReadIntPtr(list + 0x188); + + Marshal.WriteInt32(rciData + 0x28, actor.ActorId); + Marshal.WriteInt32(rciData + 0x2c, actor.ActorId); + Marshal.WriteInt32(rciData + 0x30, actor.ActorId); + + this._requestCharInfo(rciData); + } + + } +} diff --git a/Peeping Tom/Peeping Tom.csproj b/Peeping Tom/Peeping Tom.csproj index 89a2427..45e7251 100644 --- a/Peeping Tom/Peeping Tom.csproj +++ b/Peeping Tom/Peeping Tom.csproj @@ -64,6 +64,7 @@ + diff --git a/Peeping Tom/Plugin.cs b/Peeping Tom/Plugin.cs index 0cd7d21..7acb859 100644 --- a/Peeping Tom/Plugin.cs +++ b/Peeping Tom/Plugin.cs @@ -10,12 +10,14 @@ namespace PeepingTom { internal Configuration Config { get; private set; } internal PluginUI Ui { get; private set; } internal TargetWatcher Watcher { get; private set; } + internal GameFunctions GameFunctions { get; private set; } public void Initialize(DalamudPluginInterface pluginInterface) { this.Interface = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface argument was null"); this.Config = this.Interface.GetPluginConfig() as Configuration ?? new Configuration(); this.Config.Initialize(this.Interface); this.Watcher = new TargetWatcher(this); + this.GameFunctions = new GameFunctions(this); this.Ui = new PluginUI(this); this.Interface.CommandManager.AddHandler("/ppeepingtom", new CommandInfo(this.OnCommand) { diff --git a/Peeping Tom/PluginUI.cs b/Peeping Tom/PluginUI.cs index 7aa536e..7ccf040 100644 --- a/Peeping Tom/PluginUI.cs +++ b/Peeping Tom/PluginUI.cs @@ -3,6 +3,7 @@ using Dalamud.Game.Chat.SeStringHandling; using Dalamud.Game.Chat.SeStringHandling.Payloads; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Actors.Types; +using Dalamud.Plugin; using ImGuiNET; using NAudio.Wave; using System; @@ -89,8 +90,7 @@ namespace PeepingTom { } private void ShowSettings() { - // 700x250 if setting a size - ImGui.SetNextWindowSize(new Vector2(700, 275)); + ImGui.SetNextWindowSize(new Vector2(700, 250)); if (ImGui.Begin($"{this.plugin.Name} settings", ref this._settingsOpen, ImGuiWindowFlags.NoResize)) { if (ImGui.BeginTabBar("##settings-tabs")) { if (ImGui.BeginTabItem("Markers")) { @@ -172,6 +172,16 @@ namespace PeepingTom { this.plugin.Config.Save(); } + bool openExamine = this.plugin.Config.OpenExamine; + if (ImGui.Checkbox("Open examine window on Alt-click", ref openExamine)) { + this.plugin.Config.OpenExamine = openExamine; + this.plugin.Config.Save(); + } + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Sound")) { bool playSound = this.plugin.Config.PlaySoundOnTarget; if (ImGui.Checkbox("Play sound when targeted", ref playSound)) { this.plugin.Config.PlaySoundOnTarget = playSound; @@ -211,7 +221,7 @@ namespace PeepingTom { ImGui.Separator(); - for (int deviceNum = -1; deviceNum < WaveOut.DeviceCount; deviceNum++) { + for (int deviceNum = 0; deviceNum < WaveOut.DeviceCount; deviceNum++) { var caps = WaveOut.GetCapabilities(deviceNum); if (ImGui.Selectable(caps.ProductName)) { this.plugin.Config.SoundDevice = deviceNum; @@ -404,10 +414,16 @@ namespace PeepingTom { ImGui.SetNextWindowSize(new Vector2(290, 195), ImGuiCond.FirstUseEver); if (ImGui.Begin(this.plugin.Name, ref this._wantsOpen, flags)) { ImGui.Text("Targeting you"); + ImGui.SameLine(); + if (this.plugin.Config.OpenExamine) { + HelpMarker("Click to link, Alt-click to examine, or right click to target."); + } else { + HelpMarker("Click to link or right click to target."); + } float height = ImGui.GetContentRegionAvail().Y; + height -= ImGui.GetStyle().ItemSpacing.Y; - height -= ImGui.CalcTextSize(string.Empty).Y + ImGui.GetStyle().ItemSpacing.Y; bool anyHovered = false; if (ImGui.ListBoxHeader("##targeting", new Vector2(-1, height))) { // add the two first players for testing @@ -447,11 +463,21 @@ namespace PeepingTom { } this.previousFocus = new Optional(); } - ImGui.Text("Click to link or right click to target."); ImGui.End(); } } + private static void HelpMarker(string text) { + ImGui.TextDisabled("(?)"); + if (ImGui.IsItemHovered()) { + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f); + ImGui.TextUnformatted(text); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); + } + } + private void AddEntry(Targeter targeter, Actor actor, ref bool anyHovered, ImGuiSelectableFlags flags = ImGuiSelectableFlags.None) { ImGui.Selectable(targeter.Name, false, flags); bool hover = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled); @@ -477,11 +503,26 @@ namespace PeepingTom { } if (left) { - PlayerPayload payload = new PlayerPayload(this.plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id); - Payload[] payloads = { payload }; - this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry { - MessageBytes = new SeString(payloads).Encode() - }); + if (this.plugin.Config.OpenExamine && ImGui.GetIO().KeyAlt) { + if (actor != null) { + this.plugin.GameFunctions.OpenExamineWindow(actor); + } else { + Payload[] payloads = { + new TextPayload($"[{this.plugin.Name}] "), + new PlayerPayload(this.plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id), + new TextPayload(" is not close enough to examine."), + }; + this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry { + MessageBytes = new SeString(payloads).Encode(), + }); + } + } else { + PlayerPayload payload = new PlayerPayload(this.plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id); + Payload[] payloads = { payload }; + this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry { + MessageBytes = new SeString(payloads).Encode(), + }); + } } else if (right && actor != null) { this.plugin.Interface.ClientState.Targets.SetCurrentTarget(actor); }