mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
fix(ItemPayload): thorough clean up, collectibles, eventitems, aging
This commit is contained in:
parent
bb7128e6fe
commit
c232befd83
9 changed files with 244 additions and 27 deletions
|
|
@ -11,6 +11,7 @@ using Dalamud.Interface;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class GameGui : IDisposable
|
||||
public sealed unsafe class GameGui : IDisposable
|
||||
{
|
||||
private readonly GameGuiAddressResolver address;
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ namespace Dalamud.Game.Gui
|
|||
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||
private readonly Hook<HandleImmDelegate> handleImmHook;
|
||||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||
private readonly Hook<Utf8StringFromSequenceDelegate> utf8StringFromSequenceHook;
|
||||
|
||||
private GetUIMapObjectDelegate getUIMapObject;
|
||||
private OpenMapWithFlagDelegate openMapWithFlag;
|
||||
|
|
@ -79,6 +81,8 @@ namespace Dalamud.Game.Gui
|
|||
this.toggleUiHideHook = new Hook<ToggleUiHideDelegate>(this.address.ToggleUiHide, this.ToggleUiHideDetour);
|
||||
|
||||
this.getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(this.address.GetAgentModule);
|
||||
|
||||
this.utf8StringFromSequenceHook = new Hook<Utf8StringFromSequenceDelegate>(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
|
||||
}
|
||||
|
||||
// Marshaled delegates
|
||||
|
|
@ -93,6 +97,9 @@ namespace Dalamud.Game.Gui
|
|||
|
||||
// Hooked delegates
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject);
|
||||
|
||||
|
|
@ -439,6 +446,7 @@ namespace Dalamud.Game.Gui
|
|||
this.toggleUiHideHook.Enable();
|
||||
this.handleActionHoverHook.Enable();
|
||||
this.handleActionOutHook.Enable();
|
||||
this.utf8StringFromSequenceHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -457,6 +465,7 @@ namespace Dalamud.Game.Gui
|
|||
this.toggleUiHideHook.Dispose();
|
||||
this.handleActionHoverHook.Dispose();
|
||||
this.handleActionOutHook.Dispose();
|
||||
this.utf8StringFromSequenceHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -602,5 +611,15 @@ namespace Dalamud.Game.Gui
|
|||
? (char)0
|
||||
: result;
|
||||
}
|
||||
|
||||
private Utf8String* Utf8StringFromSequenceDetour(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen)
|
||||
{
|
||||
if (sourcePtr != null)
|
||||
this.utf8StringFromSequenceHook.Original(thisPtr, sourcePtr, sourceLen);
|
||||
else
|
||||
thisPtr->Ctor(); // this is in clientstructs but you could do it manually too
|
||||
|
||||
return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
public IntPtr GetAgentModule { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native Utf8StringFromSequence method.
|
||||
/// </summary>
|
||||
public IntPtr Utf8StringFromSequence { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
|
|
@ -88,6 +93,7 @@ namespace Dalamud.Game.Gui
|
|||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||
|
||||
var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28");
|
||||
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ namespace Dalamud.Game.Text
|
|||
/// </summary>
|
||||
HighQuality = 0xE03C,
|
||||
|
||||
/// <summary>
|
||||
/// The collectible icon unicode character.
|
||||
/// </summary>
|
||||
Collectible = 0xE03D,
|
||||
|
||||
/// <summary>
|
||||
/// The clock icon unicode character.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -5,6 +6,7 @@ using System.Text;
|
|||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
|
|
@ -22,7 +24,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
private string? displayName;
|
||||
|
||||
[JsonProperty]
|
||||
private uint itemId;
|
||||
private uint rawItemId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPayload"/> class.
|
||||
|
|
@ -35,7 +37,10 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// TextPayload that is a part of a full item link in chat.</param>
|
||||
public ItemPayload(uint itemId, bool isHq, string? displayNameOverride = null)
|
||||
{
|
||||
this.itemId = itemId;
|
||||
this.rawItemId = itemId;
|
||||
if (isHq)
|
||||
this.rawItemId += (uint)ItemKind.Hq;
|
||||
|
||||
this.Kind = isHq ? ItemKind.Hq : ItemKind.Normal;
|
||||
this.displayName = displayNameOverride;
|
||||
}
|
||||
|
|
@ -51,8 +56,11 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// TextPayload that is a part of a full item link in chat.</param>
|
||||
public ItemPayload(uint itemId, ItemKind kind = ItemKind.Normal, string? displayNameOverride = null)
|
||||
{
|
||||
this.itemId = itemId;
|
||||
this.Kind = kind;
|
||||
this.rawItemId = itemId;
|
||||
if (kind != ItemKind.EventItem)
|
||||
this.rawItemId += (uint)kind;
|
||||
|
||||
this.displayName = displayNameOverride;
|
||||
}
|
||||
|
||||
|
|
@ -112,10 +120,16 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw item ID of this payload.
|
||||
/// Gets the actual item ID of this payload.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ItemId => this.itemId;
|
||||
public uint ItemId => GetAdjustedId(this.rawItemId).ItemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw, unadjusted item ID of this payload.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RawItemId => this.rawItemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying Lumina Item represented by this payload.
|
||||
|
|
@ -124,7 +138,21 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Item? Item => this.item ??= this.DataResolver.GetExcelSheet<Item>()!.GetRow(this.itemId);
|
||||
public Item? Item
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO(goat): This should be revamped/removed on an API level change.
|
||||
if (this.Kind == ItemKind.EventItem)
|
||||
{
|
||||
Log.Warning("Event items cannot be fetched from the ItemPayload");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.item ??= this.DataResolver.GetExcelSheet<Item>()!.GetRow(this.ItemId);
|
||||
return this.item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this item link is for a high-quality version of the item.
|
||||
|
|
@ -156,15 +184,13 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - ItemId: {this.itemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}";
|
||||
return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var actualItemId = this.itemId - (uint)this.Kind;
|
||||
|
||||
var idBytes = MakeInteger(actualItemId);
|
||||
var idBytes = MakeInteger(this.rawItemId);
|
||||
var hasName = !string.IsNullOrEmpty(this.displayName);
|
||||
|
||||
var chunkLen = idBytes.Length + 4;
|
||||
|
|
@ -218,10 +244,8 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
var (id, kind) = GetAdjustedId(GetInteger(reader));
|
||||
|
||||
this.itemId = id;
|
||||
this.Kind = kind;
|
||||
this.rawItemId = GetInteger(reader);
|
||||
this.Kind = GetAdjustedId(this.rawItemId).Kind;
|
||||
|
||||
if (reader.BaseStream.Position + 3 < endOfStream)
|
||||
{
|
||||
|
|
@ -253,7 +277,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
{
|
||||
> 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible),
|
||||
> 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq),
|
||||
> 2_000_000 => (rawItemId - 2_000_000, ItemKind.EventItem),
|
||||
> 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted
|
||||
_ => (rawItemId, ItemKind.Normal),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
public class TextPayload : Payload, ITextProvider
|
||||
{
|
||||
[JsonProperty]
|
||||
private string text;
|
||||
private string? text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPayload"/> class.
|
||||
/// Creates a new TextPayload for the given text.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to include for this payload.</param>
|
||||
public TextPayload(string text)
|
||||
public TextPayload(string? text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
|
@ -41,12 +41,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// This may contain SE's special unicode characters.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Text
|
||||
public string? Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.text;
|
||||
}
|
||||
get => this.text;
|
||||
|
||||
set
|
||||
{
|
||||
|
|
|
|||
|
|
@ -156,22 +156,58 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
/// <param name="isHq">Whether to link the high-quality variant of the item.</param>
|
||||
/// <param name="displayNameOverride">An optional name override to display, instead of the actual item name.</param>
|
||||
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
|
||||
public static SeString CreateItemLink(uint itemId, bool isHq, string? displayNameOverride = null)
|
||||
public static SeString CreateItemLink(uint itemId, bool isHq, string? displayNameOverride = null) =>
|
||||
CreateItemLink(itemId, isHq ? ItemPayload.ItemKind.Hq : ItemPayload.ItemKind.Normal, displayNameOverride);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The id of the item to link.</param>
|
||||
/// <param name="kind">The kind of item to link.</param>
|
||||
/// <param name="displayNameOverride">An optional name override to display, instead of the actual item name.</param>
|
||||
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
|
||||
public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null)
|
||||
{
|
||||
var data = Service<DataManager>.Get();
|
||||
|
||||
var displayName = displayNameOverride ?? data.GetExcelSheet<Item>()?.GetRow(itemId)?.Name;
|
||||
if (isHq)
|
||||
var displayName = displayNameOverride;
|
||||
if (displayName == null)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case ItemPayload.ItemKind.Normal:
|
||||
case ItemPayload.ItemKind.Collectible:
|
||||
case ItemPayload.ItemKind.Hq:
|
||||
displayName = data.GetExcelSheet<Item>()?.GetRow(itemId)?.Name;
|
||||
break;
|
||||
case ItemPayload.ItemKind.EventItem:
|
||||
displayName = data.GetExcelSheet<EventItem>()?.GetRow(itemId)?.Name;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (displayName == null)
|
||||
{
|
||||
throw new Exception("Invalid item ID specified, could not determine item name.");
|
||||
}
|
||||
|
||||
if (kind == ItemPayload.ItemKind.Hq)
|
||||
{
|
||||
displayName += $" {(char)SeIconChar.HighQuality}";
|
||||
}
|
||||
else if (kind == ItemPayload.ItemKind.Collectible)
|
||||
{
|
||||
displayName += $" {(char)SeIconChar.Collectible}";
|
||||
}
|
||||
|
||||
// TODO: probably a cleaner way to build these than doing the bulk+insert
|
||||
var payloads = new List<Payload>(new Payload[]
|
||||
{
|
||||
new UIForegroundPayload(0x0225),
|
||||
new UIGlowPayload(0x0226),
|
||||
new ItemPayload(itemId, isHq),
|
||||
new ItemPayload(itemId, kind),
|
||||
// arrow goes here
|
||||
new TextPayload(displayName),
|
||||
RawPayload.LinkTerminator,
|
||||
|
|
|
|||
|
|
@ -116,6 +116,14 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
public SeStringBuilder AddItemLink(uint itemId, ItemPayload.ItemKind kind, string? itemNameOverride = null) =>
|
||||
this.Add(new ItemPayload(itemId, kind, itemNameOverride));
|
||||
|
||||
/// <summary>
|
||||
/// Add an item link to the builder.
|
||||
/// </summary>
|
||||
/// <param name="rawItemId">The raw item ID.</param>
|
||||
/// <returns>The current builder.</returns>
|
||||
public SeStringBuilder AddItemLinkRaw(uint rawItemId) =>
|
||||
this.Add(ItemPayload.FromRaw(rawItemId));
|
||||
|
||||
/// <summary>
|
||||
/// Add italicized raw text to the builder.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
|
||||
{
|
||||
/// <summary>
|
||||
/// Test setup for item payloads.
|
||||
/// </summary>
|
||||
internal class ItemPayloadAgingStep : IAgingStep
|
||||
{
|
||||
private SubStep currentSubStep;
|
||||
|
||||
private enum SubStep
|
||||
{
|
||||
PrintNormalItem,
|
||||
HoverNormalItem,
|
||||
PrintHqItem,
|
||||
HoverHqItem,
|
||||
PrintCollectable,
|
||||
HoverCollectable,
|
||||
PrintEventItem,
|
||||
HoverEventItem,
|
||||
PrintNormalWithText,
|
||||
HoverNormalWithText,
|
||||
Done,
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "Item Payloads";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
|
||||
const uint normalItemId = 24002; // Capybara pup
|
||||
const uint hqItemId = 31861; // Exarchic circlets of healing
|
||||
const uint collectableItemId = 36299; // Rarefied Annite
|
||||
const uint eventItemId = 2003363; // Speude bradeos figurine
|
||||
|
||||
SeString? toPrint = null;
|
||||
|
||||
ImGui.Text(this.currentSubStep.ToString());
|
||||
|
||||
switch (this.currentSubStep)
|
||||
{
|
||||
case SubStep.PrintNormalItem:
|
||||
toPrint = SeString.CreateItemLink(normalItemId);
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.HoverNormalItem:
|
||||
ImGui.Text("Hover the item.");
|
||||
if (gameGui.HoveredItem != normalItemId)
|
||||
return SelfTestStepResult.Waiting;
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.PrintHqItem:
|
||||
toPrint = SeString.CreateItemLink(hqItemId, ItemPayload.ItemKind.Hq);
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.HoverHqItem:
|
||||
ImGui.Text("Hover the item.");
|
||||
if (gameGui.HoveredItem != 1_000_000 + hqItemId)
|
||||
return SelfTestStepResult.Waiting;
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.PrintCollectable:
|
||||
toPrint = SeString.CreateItemLink(collectableItemId, ItemPayload.ItemKind.Collectible);
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.HoverCollectable:
|
||||
ImGui.Text("Hover the item.");
|
||||
if (gameGui.HoveredItem != 500_000 + collectableItemId)
|
||||
return SelfTestStepResult.Waiting;
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.PrintEventItem:
|
||||
toPrint = SeString.CreateItemLink(eventItemId, ItemPayload.ItemKind.EventItem);
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.HoverEventItem:
|
||||
ImGui.Text("Hover the item.");
|
||||
if (gameGui.HoveredItem != eventItemId)
|
||||
return SelfTestStepResult.Waiting;
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.PrintNormalWithText:
|
||||
toPrint = SeString.CreateItemLink(normalItemId, displayNameOverride: "Gort");
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.HoverNormalWithText:
|
||||
ImGui.Text("Hover the item.");
|
||||
if (gameGui.HoveredItem != normalItemId)
|
||||
return SelfTestStepResult.Waiting;
|
||||
this.currentSubStep++;
|
||||
break;
|
||||
case SubStep.Done:
|
||||
return SelfTestStepResult.Pass;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (toPrint != null)
|
||||
chatGui.Print(toPrint);
|
||||
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CleanUp()
|
||||
{
|
||||
this.currentSubStep = SubStep.PrintNormalItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest
|
|||
new LoginEventAgingStep(),
|
||||
new WaitFramesAgingStep(1000),
|
||||
new EnterTerritoryAgingStep(148, "Central Shroud"),
|
||||
new ItemPayloadAgingStep(),
|
||||
new ActorTableAgingStep(),
|
||||
new FateTableAgingStep(),
|
||||
new AetheryteListAgingStep(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue