feat: add some item context menu options
This commit is contained in:
parent
a633b9541f
commit
c897aae0b3
@ -32,6 +32,12 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
internal const string FriendRequestBool = "40 53 48 83 EC 20 48 8B D9 48 8B 49 10 48 8B 01 FF 90 ?? ?? ?? ?? 48 8B 48 48";
|
internal const string FriendRequestBool = "40 53 48 83 EC 20 48 8B D9 48 8B 49 10 48 8B 01 FF 90 ?? ?? ?? ?? 48 8B 48 48";
|
||||||
internal const string AgentContextYesNo = "E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 84 C0 74 3A";
|
internal const string AgentContextYesNo = "E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 84 C0 74 3A";
|
||||||
internal const string InviteToNoviceNetwork = "E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9";
|
internal const string InviteToNoviceNetwork = "E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9";
|
||||||
|
|
||||||
|
internal const string TryOn = "E8 ?? ?? ?? ?? EB 35 BA";
|
||||||
|
internal const string LinkItem = "E8 ?? ?? ?? ?? EB 7B 49 8B 06";
|
||||||
|
|
||||||
|
internal const string ItemComparisonThing = "E8 ?? ?? ?? ?? 8B 6E 20";
|
||||||
|
internal const string ItemComparison = "E8 ?? ?? ?? ?? EB 3F 83 F8 FE";
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
||||||
@ -50,6 +56,14 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
internal delegate void ChatActivatedEventDelegate(string? input);
|
internal delegate void ChatActivatedEventDelegate(string? input);
|
||||||
|
|
||||||
|
private delegate byte TryOnDelegate(uint unknownCanEquip, uint itemBaseId, ulong stainColor, uint itemGlamourId, byte unknownByte);
|
||||||
|
|
||||||
|
private delegate IntPtr LinkItemDelegate(AgentInterface* agentChatLog, uint itemId);
|
||||||
|
|
||||||
|
private delegate IntPtr ItemComparisonDelegate(IntPtr a1, ushort a2, uint itemId, byte a4);
|
||||||
|
|
||||||
|
internal const int HqItemOffset = 1_000_000;
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
private Plugin Plugin { get; }
|
||||||
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; }
|
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; }
|
||||||
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; }
|
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; }
|
||||||
@ -66,6 +80,11 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
private readonly InviteToNoviceNetworkDelegate? _inviteToNoviceNetwork;
|
private readonly InviteToNoviceNetworkDelegate? _inviteToNoviceNetwork;
|
||||||
|
|
||||||
|
private readonly TryOnDelegate? _tryOn;
|
||||||
|
private readonly LinkItemDelegate? _linkItem;
|
||||||
|
private readonly delegate* unmanaged<AgentInterface*, uint, IntPtr> _itemComparisonThing;
|
||||||
|
private readonly ItemComparisonDelegate? _itemComparison;
|
||||||
|
|
||||||
internal event ChatActivatedEventDelegate? ChatActivated;
|
internal event ChatActivatedEventDelegate? ChatActivated;
|
||||||
|
|
||||||
internal (InputChannel channel, List<Chunk> name) ChatChannel { get; private set; }
|
internal (InputChannel channel, List<Chunk> name) ChatChannel { get; private set; }
|
||||||
@ -115,6 +134,22 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
this._inviteToNoviceNetwork = Marshal.GetDelegateForFunctionPointer<InviteToNoviceNetworkDelegate>(nnPtr);
|
this._inviteToNoviceNetwork = Marshal.GetDelegateForFunctionPointer<InviteToNoviceNetworkDelegate>(nnPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.Plugin.SigScanner.TryScanText(Signatures.TryOn, out var tryOnPtr)) {
|
||||||
|
this._tryOn = Marshal.GetDelegateForFunctionPointer<TryOnDelegate>(tryOnPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Plugin.SigScanner.TryScanText(Signatures.LinkItem, out var linkPtr)) {
|
||||||
|
this._linkItem = Marshal.GetDelegateForFunctionPointer<LinkItemDelegate>(linkPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Plugin.SigScanner.TryScanText(Signatures.ItemComparisonThing, out var comparisonThingPtr)) {
|
||||||
|
this._itemComparisonThing = (delegate* unmanaged<AgentInterface*, uint, IntPtr>) comparisonThingPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Plugin.SigScanner.TryScanText(Signatures.ItemComparison, out var comparisonPtr)) {
|
||||||
|
this._itemComparison = Marshal.GetDelegateForFunctionPointer<ItemComparisonDelegate>(comparisonPtr);
|
||||||
|
}
|
||||||
|
|
||||||
this.Plugin.ClientState.Login += this.Login;
|
this.Plugin.ClientState.Login += this.Login;
|
||||||
this.Login(null, null);
|
this.Login(null, null);
|
||||||
}
|
}
|
||||||
@ -378,4 +413,27 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void TryOn(uint itemId, byte stainId) {
|
||||||
|
this._tryOn?.Invoke(0xFF, itemId, stainId, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LinkItem(uint itemId) {
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
||||||
|
this._linkItem?.Invoke(agent, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OpenItemComparison(uint itemId) {
|
||||||
|
if (this._itemComparisonThing == null || this._itemComparison == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
||||||
|
var a1 = this._itemComparisonThing(agent, 152);
|
||||||
|
if (a1 == IntPtr.Zero) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._itemComparison(a1, 0x4D, itemId, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ internal sealed class PayloadHandler {
|
|||||||
private PluginUi Ui { get; }
|
private PluginUi Ui { get; }
|
||||||
private ChatLog Log { get; }
|
private ChatLog Log { get; }
|
||||||
|
|
||||||
private HashSet<PlayerPayload> Popups { get; set; } = new();
|
private HashSet<Payload> PopupPayloads { get; set; } = new();
|
||||||
|
|
||||||
private bool _handleTooltips;
|
private bool _handleTooltips;
|
||||||
private uint _hoveredItem;
|
private uint _hoveredItem;
|
||||||
@ -29,23 +29,7 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal void Draw() {
|
internal void Draw() {
|
||||||
var newPopups = new HashSet<PlayerPayload>();
|
this.DrawPopups();
|
||||||
foreach (var player in this.Popups) {
|
|
||||||
var id = PopupId(player);
|
|
||||||
if (!ImGui.BeginPopup(id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newPopups.Add(player);
|
|
||||||
ImGui.PushID(id);
|
|
||||||
|
|
||||||
this.DrawPlayerPopup(player);
|
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Popups = newPopups;
|
|
||||||
|
|
||||||
if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) {
|
if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) {
|
||||||
this.Ui.Plugin.Functions.CloseItemTooltip();
|
this.Ui.Plugin.Functions.CloseItemTooltip();
|
||||||
@ -55,6 +39,39 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawPopups() {
|
||||||
|
var newPopups = new HashSet<Payload>();
|
||||||
|
foreach (var payload in this.PopupPayloads) {
|
||||||
|
var id = PopupId(payload);
|
||||||
|
if (id == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ImGui.BeginPopup(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPopups.Add(payload);
|
||||||
|
ImGui.PushID(id);
|
||||||
|
|
||||||
|
switch (payload) {
|
||||||
|
case PlayerPayload player: {
|
||||||
|
this.DrawPlayerPopup(player);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ItemPayload item: {
|
||||||
|
this.DrawItemPopup(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.PopupPayloads = newPopups;
|
||||||
|
}
|
||||||
|
|
||||||
internal void Click(Chunk chunk, Payload payload, ImGuiMouseButton button) {
|
internal void Click(Chunk chunk, Payload payload, ImGuiMouseButton button) {
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case ImGuiMouseButton.Left:
|
case ImGuiMouseButton.Left:
|
||||||
@ -189,14 +206,50 @@ internal sealed class PayloadHandler {
|
|||||||
|
|
||||||
private void RightClickPayload(Payload payload) {
|
private void RightClickPayload(Payload payload) {
|
||||||
switch (payload) {
|
switch (payload) {
|
||||||
case PlayerPayload player: {
|
case PlayerPayload:
|
||||||
this.Popups.Add(player);
|
case ItemPayload: {
|
||||||
ImGui.OpenPopup(PopupId(player));
|
this.PopupPayloads.Add(payload);
|
||||||
|
ImGui.OpenPopup(PopupId(payload));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawItemPopup(ItemPayload item) {
|
||||||
|
if (this.Ui.Plugin.TextureCache.GetItem(item.Item) is { } icon) {
|
||||||
|
InlineIcon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = item.Item.Name.ToDalamudString();
|
||||||
|
if (item.IsHQ) {
|
||||||
|
// hq symbol
|
||||||
|
name.Payloads.Add(new TextPayload(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false);
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.HqItemOffset : 0));
|
||||||
|
|
||||||
|
if (item.Item.EquipSlotCategory.Row != 0) {
|
||||||
|
if (ImGui.Selectable("Try On")) {
|
||||||
|
this.Ui.Plugin.Functions.TryOn(realItemId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable("Item Comparison")) {
|
||||||
|
this.Ui.Plugin.Functions.OpenItemComparison(realItemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable("Link")) {
|
||||||
|
this.Ui.Plugin.Functions.LinkItem(realItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable("Copy Item Name")) {
|
||||||
|
ImGui.SetClipboardText(name.TextValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawPlayerPopup(PlayerPayload player) {
|
private void DrawPlayerPopup(PlayerPayload player) {
|
||||||
var name = player.PlayerName;
|
var name = player.PlayerName;
|
||||||
if (player.World.IsPublic) {
|
if (player.World.IsPublic) {
|
||||||
@ -206,27 +259,29 @@ internal sealed class PayloadHandler {
|
|||||||
ImGui.TextUnformatted(name);
|
ImGui.TextUnformatted(name);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (player.World.IsPublic && ImGui.Selectable("Send Tell")) {
|
if (player.World.IsPublic) {
|
||||||
|
if (ImGui.Selectable("Send Tell")) {
|
||||||
this.Log.Chat = $"/tell {player.PlayerName}@{player.World.Name} ";
|
this.Log.Chat = $"/tell {player.PlayerName}@{player.World.Name} ";
|
||||||
this.Log.Activate = true;
|
this.Log.Activate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.World.IsPublic && ImGui.Selectable("Invite to Party")) {
|
if (ImGui.Selectable("Invite to Party")) {
|
||||||
// FIXME: don't show if player is in your party or if you're in their party
|
// FIXME: don't show if player is in your party or if you're in their party
|
||||||
// FIXME: don't show if in party and not leader
|
// FIXME: don't show if in party and not leader
|
||||||
this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.World.IsPublic && ImGui.Selectable("Send Friend Request")) {
|
if (ImGui.Selectable("Send Friend Request")) {
|
||||||
// FIXME: this shows window, clicking yes doesn't work
|
// FIXME: this shows window, clicking yes doesn't work
|
||||||
// FIXME: only show if not already friend
|
// FIXME: only show if not already friend
|
||||||
this.Ui.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.World.IsPublic && ImGui.Selectable("Invite to Novice Network")) {
|
if (ImGui.Selectable("Invite to Novice Network")) {
|
||||||
// FIXME: only show if character is mentor and target is sprout/returner
|
// FIXME: only show if character is mentor and target is sprout/returner
|
||||||
this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Target") && this.FindCharacterForPayload(player) is { } obj) {
|
if (ImGui.Selectable("Target") && this.FindCharacterForPayload(player) is { } obj) {
|
||||||
this.Ui.Plugin.TargetManager.SetTarget(obj);
|
this.Ui.Plugin.TargetManager.SetTarget(obj);
|
||||||
@ -261,7 +316,9 @@ internal sealed class PayloadHandler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string PopupId(PlayerPayload player) {
|
private static string? PopupId(Payload payload) => payload switch {
|
||||||
return $"###player-{player.PlayerName}@{player.World}";
|
PlayerPayload player => $"###player-{player.PlayerName}@{player.World}",
|
||||||
}
|
ItemPayload item => $"###item-{item.ItemId}{item.IsHQ}",
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -191,11 +191,11 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.Sender.Count > 0) {
|
if (message.Sender.Count > 0) {
|
||||||
this.DrawChunks(message.Sender, this.PayloadHandler);
|
this.DrawChunks(message.Sender, true, this.PayloadHandler);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.DrawChunks(message.Content, this.PayloadHandler);
|
this.DrawChunks(message.Content, true, this.PayloadHandler);
|
||||||
|
|
||||||
// drawnHeight += ImGui.GetCursorPosY() - lastPos;
|
// drawnHeight += ImGui.GetCursorPosY() - lastPos;
|
||||||
// lastPos = ImGui.GetCursorPosY();
|
// lastPos = ImGui.GetCursorPosY();
|
||||||
@ -354,17 +354,22 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawChunks(IReadOnlyList<Chunk> chunks, PayloadHandler? handler = null) {
|
internal void DrawChunks(IReadOnlyList<Chunk> chunks, bool wrap = true, PayloadHandler? handler = null) {
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
try {
|
||||||
for (var i = 0; i < chunks.Count; i++) {
|
for (var i = 0; i < chunks.Count; i++) {
|
||||||
this.DrawChunk(chunks[i], handler);
|
this.DrawChunk(chunks[i], wrap, handler);
|
||||||
|
|
||||||
if (i < chunks.Count - 1) {
|
if (i < chunks.Count - 1) {
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawChunk(Chunk chunk, PayloadHandler? handler = null) {
|
private void DrawChunk(Chunk chunk, bool wrap = true, PayloadHandler? handler = null) {
|
||||||
if (chunk is IconChunk icon && this._fontIcon != null) {
|
if (chunk is IconChunk icon && this._fontIcon != null) {
|
||||||
var bounds = IconUtil.GetBounds((byte) icon.Icon);
|
var bounds = IconUtil.GetBounds((byte) icon.Icon);
|
||||||
if (bounds != null) {
|
if (bounds != null) {
|
||||||
@ -414,7 +419,12 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wrap) {
|
||||||
ImGuiUtil.WrapText(content, chunk, handler);
|
ImGuiUtil.WrapText(content, chunk, handler);
|
||||||
|
} else {
|
||||||
|
ImGui.TextUnformatted(content);
|
||||||
|
ImGuiUtil.PostPayload(chunk, handler);
|
||||||
|
}
|
||||||
|
|
||||||
if (text.Italic && this.Ui.ItalicFont.HasValue) {
|
if (text.Italic && this.Ui.ItalicFont.HasValue) {
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
Loading…
Reference in New Issue
Block a user