diff --git a/ChatTwo/GameFunctions.cs b/ChatTwo/GameFunctions.cs index bc22efb..82a9420 100755 --- a/ChatTwo/GameFunctions.cs +++ b/ChatTwo/GameFunctions.cs @@ -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 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 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); @@ -50,6 +56,14 @@ internal unsafe class GameFunctions : IDisposable { 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 Hook? ChatLogRefreshHook { get; } private Hook? ChangeChannelNameHook { get; } @@ -66,6 +80,11 @@ internal unsafe class GameFunctions : IDisposable { private readonly InviteToNoviceNetworkDelegate? _inviteToNoviceNetwork; + private readonly TryOnDelegate? _tryOn; + private readonly LinkItemDelegate? _linkItem; + private readonly delegate* unmanaged _itemComparisonThing; + private readonly ItemComparisonDelegate? _itemComparison; + internal event ChatActivatedEventDelegate? ChatActivated; internal (InputChannel channel, List name) ChatChannel { get; private set; } @@ -115,6 +134,22 @@ internal unsafe class GameFunctions : IDisposable { this._inviteToNoviceNetwork = Marshal.GetDelegateForFunctionPointer(nnPtr); } + if (this.Plugin.SigScanner.TryScanText(Signatures.TryOn, out var tryOnPtr)) { + this._tryOn = Marshal.GetDelegateForFunctionPointer(tryOnPtr); + } + + if (this.Plugin.SigScanner.TryScanText(Signatures.LinkItem, out var linkPtr)) { + this._linkItem = Marshal.GetDelegateForFunctionPointer(linkPtr); + } + + if (this.Plugin.SigScanner.TryScanText(Signatures.ItemComparisonThing, out var comparisonThingPtr)) { + this._itemComparisonThing = (delegate* unmanaged) comparisonThingPtr; + } + + if (this.Plugin.SigScanner.TryScanText(Signatures.ItemComparison, out var comparisonPtr)) { + this._itemComparison = Marshal.GetDelegateForFunctionPointer(comparisonPtr); + } + this.Plugin.ClientState.Login += this.Login; this.Login(null, null); } @@ -378,4 +413,27 @@ internal unsafe class GameFunctions : IDisposable { 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); + } } diff --git a/ChatTwo/PayloadHandler.cs b/ChatTwo/PayloadHandler.cs index 60aa1d0..9dc9b7b 100755 --- a/ChatTwo/PayloadHandler.cs +++ b/ChatTwo/PayloadHandler.cs @@ -16,7 +16,7 @@ internal sealed class PayloadHandler { private PluginUi Ui { get; } private ChatLog Log { get; } - private HashSet Popups { get; set; } = new(); + private HashSet PopupPayloads { get; set; } = new(); private bool _handleTooltips; private uint _hoveredItem; @@ -29,23 +29,7 @@ internal sealed class PayloadHandler { } internal void Draw() { - var newPopups = new HashSet(); - 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; + this.DrawPopups(); if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) { this.Ui.Plugin.Functions.CloseItemTooltip(); @@ -55,6 +39,39 @@ internal sealed class PayloadHandler { } } + private void DrawPopups() { + var newPopups = new HashSet(); + 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) { switch (button) { case ImGuiMouseButton.Left: @@ -189,14 +206,50 @@ internal sealed class PayloadHandler { private void RightClickPayload(Payload payload) { switch (payload) { - case PlayerPayload player: { - this.Popups.Add(player); - ImGui.OpenPopup(PopupId(player)); + case PlayerPayload: + case ItemPayload: { + this.PopupPayloads.Add(payload); + ImGui.OpenPopup(PopupId(payload)); 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) { var name = player.PlayerName; if (player.World.IsPublic) { @@ -206,26 +259,28 @@ internal sealed class PayloadHandler { ImGui.TextUnformatted(name); ImGui.Separator(); - if (player.World.IsPublic && ImGui.Selectable("Send Tell")) { - this.Log.Chat = $"/tell {player.PlayerName}@{player.World.Name} "; - this.Log.Activate = true; - } + if (player.World.IsPublic) { + if (ImGui.Selectable("Send Tell")) { + this.Log.Chat = $"/tell {player.PlayerName}@{player.World.Name} "; + this.Log.Activate = true; + } - if (player.World.IsPublic && 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 in party and not leader - this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId); - } + 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 in party and not leader + this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId); + } - if (player.World.IsPublic && ImGui.Selectable("Send Friend Request")) { - // FIXME: this shows window, clicking yes doesn't work - // FIXME: only show if not already friend - this.Ui.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) player.World.RowId); - } + if (ImGui.Selectable("Send Friend Request")) { + // FIXME: this shows window, clicking yes doesn't work + // FIXME: only show if not already friend + this.Ui.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) player.World.RowId); + } - if (player.World.IsPublic && ImGui.Selectable("Invite to Novice Network")) { - // FIXME: only show if character is mentor and target is sprout/returner - this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId); + if (ImGui.Selectable("Invite to Novice Network")) { + // FIXME: only show if character is mentor and target is sprout/returner + this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId); + } } if (ImGui.Selectable("Target") && this.FindCharacterForPayload(player) is { } obj) { @@ -261,7 +316,9 @@ internal sealed class PayloadHandler { return null; } - private static string PopupId(PlayerPayload player) { - return $"###player-{player.PlayerName}@{player.World}"; - } + private static string? PopupId(Payload payload) => payload switch { + PlayerPayload player => $"###player-{player.PlayerName}@{player.World}", + ItemPayload item => $"###item-{item.ItemId}{item.IsHQ}", + _ => null, + }; } diff --git a/ChatTwo/Ui/ChatLog.cs b/ChatTwo/Ui/ChatLog.cs index 0f5fdc5..78caf80 100755 --- a/ChatTwo/Ui/ChatLog.cs +++ b/ChatTwo/Ui/ChatLog.cs @@ -191,11 +191,11 @@ internal sealed class ChatLog : IUiComponent { } if (message.Sender.Count > 0) { - this.DrawChunks(message.Sender, this.PayloadHandler); + this.DrawChunks(message.Sender, true, this.PayloadHandler); ImGui.SameLine(); } - this.DrawChunks(message.Content, this.PayloadHandler); + this.DrawChunks(message.Content, true, this.PayloadHandler); // drawnHeight += ImGui.GetCursorPosY() - lastPos; // lastPos = ImGui.GetCursorPosY(); @@ -354,17 +354,22 @@ internal sealed class ChatLog : IUiComponent { return 0; } - internal void DrawChunks(IReadOnlyList chunks, PayloadHandler? handler = null) { - for (var i = 0; i < chunks.Count; i++) { - this.DrawChunk(chunks[i], handler); + internal void DrawChunks(IReadOnlyList chunks, bool wrap = true, PayloadHandler? handler = null) { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + try { + for (var i = 0; i < chunks.Count; i++) { + this.DrawChunk(chunks[i], wrap, handler); - if (i < chunks.Count - 1) { - ImGui.SameLine(); + if (i < chunks.Count - 1) { + 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) { var bounds = IconUtil.GetBounds((byte) icon.Icon); if (bounds != null) { @@ -414,7 +419,12 @@ internal sealed class ChatLog : IUiComponent { } } - ImGuiUtil.WrapText(content, chunk, handler); + if (wrap) { + ImGuiUtil.WrapText(content, chunk, handler); + } else { + ImGui.TextUnformatted(content); + ImGuiUtil.PostPayload(chunk, handler); + } if (text.Italic && this.Ui.ItalicFont.HasValue) { ImGui.PopFont();