diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs new file mode 100644 index 000000000..59e78831f --- /dev/null +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs @@ -0,0 +1,35 @@ +using Dalamud.Game.ClientState.Resolvers; +using FFXIVClientStructs.FFXIV.Client.Game.UI; + +namespace Dalamud.Game.ClientState.Aetherytes +{ + public class AetheryteEntry + { + private readonly TeleportInfo data; + + internal AetheryteEntry(TeleportInfo data) + { + this.data = data; + } + + public uint AetheryteId => this.data.AetheryteId; + + public uint TerritoryId => this.data.TerritoryId; + + public byte SubIndex => this.data.SubIndex; + + public byte Ward => this.data.Ward; + + public byte Plot => this.data.Plot; + + public uint GilCost => this.data.GilCost; + + public bool IsFavourite => this.data.IsFavourite != 0; + + public bool IsSharedHouse => this.data.IsSharedHouse; + + public bool IsAppartment => this.data.IsAppartment; + + public ExcelResolver AetheryteData => new(this.AetheryteId); + } +} diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs new file mode 100644 index 000000000..994b8371e --- /dev/null +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Dalamud.IoC; +using Dalamud.IoC.Internal; +using Serilog; + +namespace Dalamud.Game.ClientState.Aetherytes +{ + /// + /// This collection represents the list of available Aetherytes in the Teleport window. + /// + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed partial class AetheryteList + { + private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1); + + private readonly ClientStateAddressResolver address; + private readonly UpdateAetheryteListDelegate updateAetheryteListFunc; + + /// + /// Initializes a new instance of the class. + /// + /// Client state address resolver. + internal AetheryteList(ClientStateAddressResolver addressResolver) + { + this.address = addressResolver; + this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList); + + Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}"); + } + + /// + /// Gets the amount of Aetherytes the local player has unlocked. + /// + public unsafe int Length + { + get + { + var clientState = Service.Get(); + + if (clientState.LocalPlayer == null) + return 0; + + this.Update(); + + if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last) + return 0; + + return (int)TelepoStruct->TeleportList.Size(); + } + } + + private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo; + + /// + /// Gets a Aetheryte Entry at the specified index. + /// + /// Index. + /// A at the specified index. + public unsafe AetheryteEntry? this[int index] + { + get + { + if (index < 0 || index >= Length) + { + return null; + } + + var clientState = Service.Get(); + + if (clientState.LocalPlayer == null) + return null; + + return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index)); + } + } + + private void Update() + { + var clientState = Service.Get(); + + // this is very very important as otherwise it crashes + if (clientState.LocalPlayer == null) + return; + + this.updateAetheryteListFunc(this.address.Telepo, 0); + } + } + + /// + /// This collection represents the list of available Aetherytes in the Teleport window. + /// + public sealed partial class AetheryteList : IReadOnlyCollection + { + + /// + public int Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < this.Length; i++) + { + yield return this[i]; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 52b63ed9e..cda6d7c9b 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -10,6 +10,7 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Party; +using Dalamud.Game.ClientState.Aetherytes; using Dalamud.Game.Network.Internal; using Dalamud.Hooking; using Dalamud.IoC; @@ -61,6 +62,8 @@ namespace Dalamud.Game.ClientState Service.Set(this.address); + Service.Set(this.address); + Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 64b7f5d4c..4fa7feb79 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -62,6 +62,11 @@ namespace Dalamud.Game.ClientState /// public IntPtr ConditionFlags { get; private set; } + /// + /// Gets the address of the Telepo instance. + /// + public IntPtr Telepo { get; private set; } + // Functions /// @@ -75,6 +80,11 @@ namespace Dalamud.Game.ClientState /// public IntPtr GamepadPoll { get; private set; } + /// + /// Gets the address of the method which updates the list of available teleport locations. + /// + public IntPtr UpdateAetheryteList { get; private set; } + /// /// Scan for and setup any configured address pointers. /// @@ -109,6 +119,10 @@ namespace Dalamud.Game.ClientState this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB"); this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); + + this.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12"); + + this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50"); } } } diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index 48febaa5a..b6e9db296 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -23,6 +23,7 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Party; +using Dalamud.Game.ClientState.Aetherytes; using Dalamud.Game.Command; using Dalamud.Game.Gui; using Dalamud.Game.Gui.FlyText; @@ -156,6 +157,7 @@ namespace Dalamud.Interface.Internal.Windows Configuration, TaskSched, Hook, + Aetherytes, } /// @@ -335,6 +337,9 @@ namespace Dalamud.Interface.Internal.Windows case DataKind.Hook: this.DrawHook(); break; + case DataKind.Aetherytes: + this.DrawAetherytes(); + break; } } else @@ -1510,6 +1515,70 @@ namespace Dalamud.Interface.Internal.Windows } } + private void DrawAetherytes() + { + if (!ImGui.BeginTable("##aetheryteTable", 11, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) + return; + + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("Idx", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Zone", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Ward", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Plot", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Sub", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Gil", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Fav", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Shared", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Appartment", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableHeadersRow(); + + var tpList = Service.Get(); + + for (var i = 0; i < tpList.Length; i++) + { + var info = tpList[i]; + if (info == null) + continue; + + ImGui.TableNextColumn(); // Idx + ImGui.TextUnformatted($"{i}"); + + ImGui.TableNextColumn(); // Name + ImGui.TextUnformatted($"{info.AetheryteData.GameData.PlaceName.Value?.Name}"); + + ImGui.TableNextColumn(); // ID + ImGui.TextUnformatted($"{info.AetheryteId}"); + + ImGui.TableNextColumn(); // Zone + ImGui.TextUnformatted($"{info.TerritoryId}"); + + ImGui.TableNextColumn(); // Ward + ImGui.TextUnformatted($"{info.Ward}"); + + ImGui.TableNextColumn(); // Plot + ImGui.TextUnformatted($"{info.Plot}"); + + ImGui.TableNextColumn(); // Sub + ImGui.TextUnformatted($"{info.SubIndex}"); + + ImGui.TableNextColumn(); // Gil + ImGui.TextUnformatted($"{info.GilCost}"); + + ImGui.TableNextColumn(); // Favourite + ImGui.TextUnformatted($"{info.IsFavourite}"); + + ImGui.TableNextColumn(); // Shared + ImGui.TextUnformatted($"{info.IsSharedHouse}"); + + ImGui.TableNextColumn(); // Appartment + ImGui.TextUnformatted($"{info.IsAppartment}"); + } + + ImGui.EndTable(); + } + private async Task TestTaskInTaskDelay() { await Task.Delay(5000);