Merge pull request #711 from pohky/aetherytes

This commit is contained in:
goaaats 2021-12-09 17:49:15 +01:00 committed by GitHub
commit ef16e84ae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 329 additions and 0 deletions

View file

@ -0,0 +1,72 @@
using Dalamud.Game.ClientState.Resolvers;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
namespace Dalamud.Game.ClientState.Aetherytes
{
/// <summary>
/// This class represents an entry in the Aetheryte list.
/// </summary>
public sealed class AetheryteEntry
{
private readonly TeleportInfo data;
/// <summary>
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
/// </summary>
/// <param name="data">Data read from the Aetheryte List.</param>
internal AetheryteEntry(TeleportInfo data)
{
this.data = data;
}
/// <summary>
/// Gets the Aetheryte ID.
/// </summary>
public uint AetheryteId => this.data.AetheryteId;
/// <summary>
/// Gets the Territory ID.
/// </summary>
public uint TerritoryId => this.data.TerritoryId;
/// <summary>
/// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.).
/// </summary>
public byte SubIndex => this.data.SubIndex;
/// <summary>
/// Gets the Ward. Zero if not a Shared Estate.
/// </summary>
public byte Ward => this.data.Ward;
/// <summary>
/// Gets the Plot. Zero if not a Shared Estate.
/// </summary>
public byte Plot => this.data.Plot;
/// <summary>
/// Gets the Cost in Gil to Teleport to this location.
/// </summary>
public uint GilCost => this.data.GilCost;
/// <summary>
/// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not.
/// </summary>
public bool IsFavourite => this.data.IsFavourite != 0;
/// <summary>
/// Gets a value indicating whether this Aetheryte is a Shared Estate or not.
/// </summary>
public bool IsSharedHouse => this.data.IsSharedHouse;
/// <summary>
/// Gets a value indicating whether this Aetheryte is an Appartment or not.
/// </summary>
public bool IsAppartment => this.data.IsAppartment;
/// <summary>
/// Gets the Aetheryte data related to this aetheryte.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData => new(this.AetheryteId);
}
}

View file

@ -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
{
/// <summary>
/// This collection represents the list of available Aetherytes in the Teleport window.
/// </summary>
[PluginInterface]
[InterfaceVersion("1.0")]
public sealed partial class AetheryteList
{
private readonly ClientStateAddressResolver address;
private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
/// <summary>
/// Initializes a new instance of the <see cref="AetheryteList"/> class.
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
internal AetheryteList(ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer<UpdateAetheryteListDelegate>(this.address.UpdateAetheryteList);
Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
}
private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1);
/// <summary>
/// Gets the amount of Aetherytes the local player has unlocked.
/// </summary>
public unsafe int Length
{
get
{
var clientState = Service<ClientState>.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;
/// <summary>
/// Gets a Aetheryte Entry at the specified index.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns>
public unsafe AetheryteEntry? this[int index]
{
get
{
if (index < 0 || index >= this.Length)
{
return null;
}
var clientState = Service<ClientState>.Get();
if (clientState.LocalPlayer == null)
return null;
return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
}
}
private void Update()
{
var clientState = Service<ClientState>.Get();
// this is very very important as otherwise it crashes
if (clientState.LocalPlayer == null)
return;
this.updateAetheryteListFunc(this.address.Telepo, 0);
}
}
/// <summary>
/// This collection represents the list of available Aetherytes in the Teleport window.
/// </summary>
public sealed partial class AetheryteList : IReadOnlyCollection<AetheryteEntry>
{
/// <inheritdoc/>
public int Count => this.Length;
/// <inheritdoc/>
public IEnumerator<AetheryteEntry> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}

View file

@ -10,6 +10,7 @@ using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Party; using Dalamud.Game.ClientState.Party;
using Dalamud.Game.ClientState.Aetherytes;
using Dalamud.Game.Network.Internal; using Dalamud.Game.Network.Internal;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.IoC; using Dalamud.IoC;
@ -61,6 +62,8 @@ namespace Dalamud.Game.ClientState
Service<TargetManager>.Set(this.address); Service<TargetManager>.Set(this.address);
Service<AetheryteList>.Set(this.address);
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);

View file

@ -62,6 +62,11 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public IntPtr ConditionFlags { get; private set; } public IntPtr ConditionFlags { get; private set; }
/// <summary>
/// Gets the address of the Telepo instance.
/// </summary>
public IntPtr Telepo { get; private set; }
// Functions // Functions
/// <summary> /// <summary>
@ -75,6 +80,11 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public IntPtr GamepadPoll { get; private set; } public IntPtr GamepadPoll { get; private set; }
/// <summary>
/// Gets the address of the method which updates the list of available teleport locations.
/// </summary>
public IntPtr UpdateAetheryteList { get; private set; }
/// <summary> /// <summary>
/// Scan for and setup any configured address pointers. /// Scan for and setup any configured address pointers.
/// </summary> /// </summary>
@ -109,6 +119,10 @@ namespace Dalamud.Game.ClientState
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB"); 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.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");
} }
} }
} }

View file

@ -23,6 +23,7 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Party; using Dalamud.Game.ClientState.Party;
using Dalamud.Game.ClientState.Aetherytes;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Game.Gui; using Dalamud.Game.Gui;
using Dalamud.Game.Gui.FlyText; using Dalamud.Game.Gui.FlyText;
@ -156,6 +157,7 @@ namespace Dalamud.Interface.Internal.Windows
Configuration, Configuration,
TaskSched, TaskSched,
Hook, Hook,
Aetherytes,
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -335,6 +337,9 @@ namespace Dalamud.Interface.Internal.Windows
case DataKind.Hook: case DataKind.Hook:
this.DrawHook(); this.DrawHook();
break; break;
case DataKind.Aetherytes:
this.DrawAetherytes();
break;
} }
} }
else 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<AetheryteList>.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() private async Task TestTaskInTaskDelay()
{ {
await Task.Delay(5000); await Task.Delay(5000);

View file

@ -0,0 +1,53 @@
using Dalamud.Game.ClientState.Aetherytes;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
{
/// <summary>
/// Test setup for the Aetheryte List.
/// </summary>
internal class AetheryteListAgingStep : IAgingStep
{
private int index = 0;
/// <inheritdoc/>
public string Name => "Test AetheryteList";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
var list = Service<AetheryteList>.Get();
ImGui.Text("Checking aetheryte list...");
if (this.index == list.Length - 1)
{
return SelfTestStepResult.Pass;
}
var aetheryte = list[this.index];
this.index++;
if (aetheryte == null)
{
return SelfTestStepResult.Waiting;
}
if (aetheryte.AetheryteId == 0)
{
return SelfTestStepResult.Fail;
}
Util.ShowObject(aetheryte);
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}
}

View file

@ -28,6 +28,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest
new EnterTerritoryAgingStep(148, "Central Shroud"), new EnterTerritoryAgingStep(148, "Central Shroud"),
new ActorTableAgingStep(), new ActorTableAgingStep(),
new FateTableAgingStep(), new FateTableAgingStep(),
new AetheryteListAgingStep(),
new ConditionAgingStep(), new ConditionAgingStep(),
new ToastAgingStep(), new ToastAgingStep(),
new TargetAgingStep(), new TargetAgingStep(),