From abc0617d588b2bc7743db97c4ccb8cb9b970b5b0 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Wed, 27 May 2026 12:50:23 +0200 Subject: [PATCH] feat(payload-handler): wire DrawPlayerPopup + FindCharacterForPayload (E3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E3 fills the player-payload right-click menu. DrawPlayerPopup migrates 1:1 from v1.5.6 with all §4.2/§6.9 substitutions: - Tell-prefix builds via _inputBar.SetPendingMessage (single string build) + _inputBar.Activate = true (replaces v1.5.6's 3x LogWindow.Chat incremental writes + LogWindow.Activate flip) - Channel-switch routes through _mainWindow.ActiveTab?.CurrentChannel? .SetChannel(channel) (per §6.3, ActiveTab is public getter on MainWindow per v1.7.0 refactor) - SendFriendRequest / AddToBlacklist / AddToMuteList / AddToTermsList / SetEurekaTellChannel all route through injected _functions - Player-name renderings route through _chunkRenderer.DrawChunks FindCharacterForPayload migrates 1:1 (pure helper, Plugin.ObjectTable static stays as-is). Replaces E2's TODO(E3) marker in DrawPopups' PlayerPayload case with real DrawPlayerPopup(chunk, player) call. E4 will wire Item/EventItem/Status/Uri popup bodies; E5 the Hover/Click bodies; E6 MoveTooltip. --- HellionChat/PayloadHandler.cs | 197 +++++++++++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 1 deletion(-) diff --git a/HellionChat/PayloadHandler.cs b/HellionChat/PayloadHandler.cs index 72fe226..222d42c 100644 --- a/HellionChat/PayloadHandler.cs +++ b/HellionChat/PayloadHandler.cs @@ -1,5 +1,6 @@ using System.Linq; using Dalamud.Bindings.ImGui; +using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface.ImGuiNotification; @@ -85,7 +86,7 @@ internal sealed class PayloadHandler switch (payload) { case PlayerPayload player: - // TODO(E3): DrawPlayerPopup(chunk, player); + DrawPlayerPopup(chunk, player); drawn = true; break; case ItemPayload item: @@ -217,6 +218,200 @@ internal sealed class PayloadHandler .Aggregate(string.Concat); } + private void DrawPlayerPopup(Chunk chunk, PlayerPayload player) + { + // Possible that GMs return a null payload + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (player == null) + return; + + var world = player.World; + if (chunk.Message?.Code.Type == ChatType.FreeCompanyLoginLogout) + if (Plugin.PlayerState.HomeWorld.IsValid) + world = Plugin.PlayerState.HomeWorld; + + var name = new List { new TextChunk(ChunkSource.None, null, player.PlayerName) }; + if (world.Value.IsPublic) + { + name.AddRange([ + new IconChunk(ChunkSource.None, null, BitmapFontIcon.CrossWorld), + new TextChunk(ChunkSource.None, null, world.Value.Name.ExtractText()), + ]); + } + + _chunkRenderer.DrawChunks(name, false); + ImGui.Separator(); + + var validContentId = chunk.Message?.ContentId is not (null or 0); + if (ImGui.Selectable(Language.Context_SendTell)) + { + // Eureka, Bozja and Occult need special handling as tells work different + if (!Sheets.IsInForay()) + { + // §6.9: build as single string then hand off; replaces v1.5.6's + // incremental LogWindow.Chat += ... pattern + var builder = $"/tell {player.PlayerName}"; + if (world.Value.IsPublic) + builder += $"@{world.Value.Name}"; + + builder += " "; + _inputBar.SetPendingMessage(builder); + } + else if (validContentId) + { + _functions.Chat.SetEurekaTellChannel( + player.PlayerName, + world.Value.Name.ToString(), + (ushort)world.RowId, + 0, + chunk.Message!.ContentId, + 0, + false + ); + } + + _inputBar.Activate = true; + } + + if (world.Value.IsPublic) + { + var party = Plugin.PartyList; + var leader = party[(int)party.PartyLeaderIndex]?.ContentId; + var isLeader = party.Length == 0 || Plugin.PlayerState.ContentId == leader; + var member = party.FirstOrDefault(member => + member.Name.TextValue == player.PlayerName && member.World.RowId == world.RowId + ); + var isInParty = member != null; + var inInstance = GameFunctions.GameFunctions.IsInInstance(); + var inPartyInstance = + Sheets + .TerritorySheet.GetRow(Plugin.ClientState.TerritoryType) + .TerritoryIntendedUse.RowId + is (41 or 47 or 48 or 52 or 53 or 61); + if (isLeader) + { + if (!isInParty) + { + if (inInstance && inPartyInstance) + { + if (validContentId && ImGui.Selectable(Language.Context_InviteToParty)) + GameFunctions.Party.InviteInInstance(chunk.Message!.ContentId); + } + else if (!inInstance) + { + using var menu = ImRaii.Menu(Language.Context_InviteToParty); + if (menu.Success) + { + if (ImGui.Selectable(Language.Context_InviteToParty_SameWorld)) + GameFunctions.Party.InviteSameWorld( + player.PlayerName, + (ushort)world.RowId, + chunk.Message?.ContentId ?? 0 + ); + + if ( + validContentId + && ImGui.Selectable(Language.Context_InviteToParty_DifferentWorld) + ) + GameFunctions.Party.InviteOtherWorld( + chunk.Message!.ContentId, + (ushort)world.RowId + ); + } + } + } + + if (isInParty && member != null && (!inInstance || (inInstance && inPartyInstance))) + { + if (ImGui.Selectable(Language.Context_Promote)) + GameFunctions.Party.Promote(player.PlayerName, member.ContentId); + + if (ImGui.Selectable(Language.Context_KickFromParty)) + GameFunctions.Party.Kick(player.PlayerName, member.ContentId); + } + } + + var isFriend = GameFunctions + .GameFunctions.GetFriends() + .Any(friend => + friend.NameString == player.PlayerName && friend.HomeWorld == world.RowId + ); + if (!isFriend && ImGui.Selectable(Language.Context_SendFriendRequest)) + _functions.SendFriendRequest(player.PlayerName, (ushort)world.RowId); + + using (var menuBlockFunctions = ImRaii.Menu(Language.Context_BlockFunctions)) + { + if (menuBlockFunctions.Success) + { + if (ImGui.Selectable(Language.Context_AddToBlacklist)) + _functions.AddToBlacklist(player.PlayerName, (ushort)world.RowId); + + if (chunk.Message != null) + { + var message = chunk.Message; + + if ( + message.AccountId != 0 + && ImGui.Selectable(Language.Context_AddToMuteList) + ) + _functions.AddToMuteList( + message.AccountId, + message.ContentId, + player.PlayerName, + (short)world.RowId + ); + + if (ImGui.Selectable(Language.Context_AddToTermsFilter)) + _functions.AddToTermsList(message.ContentSource); + } + } + } + + if ( + GameFunctions.GameFunctions.IsMentor() + && ImGui.Selectable(Language.Context_InviteToNoviceNetwork) + ) + GameFunctions.Context.InviteToNoviceNetwork(player.PlayerName, (ushort)world.RowId); + } + + var inputChannel = chunk.Message?.Code.Type.ToInputChannel(); + if (inputChannel != null && ImGui.Selectable(Language.Context_ReplyInSelectedChatMode)) + { + // §6.3: route channel-switch through MainWindow's active tab + _mainWindow.ActiveTab?.CurrentChannel?.SetChannel(inputChannel.Value); + _inputBar.Activate = true; + } + + if (ImGui.Selectable(Language.Context_Target) && FindCharacterForPayload(player) is { } obj) + Plugin.TargetManager.Target = obj; + + if (validContentId && ImGui.Selectable(Language.Context_AdventurerPlate)) + if (!GameFunctions.GameFunctions.TryOpenAdventurerPlate(chunk.Message!.ContentId)) + WrapperUtil.AddNotification( + Language.Context_AdventurerPlateError, + NotificationType.Warning + ); + } + + private IPlayerCharacter? FindCharacterForPayload(PlayerPayload payload) + { + foreach (var obj in Plugin.ObjectTable) + { + if (obj is not IPlayerCharacter character) + continue; + + if (character.Name.TextValue != payload.PlayerName) + continue; + + if (payload.World.Value.IsPublic && character.HomeWorld.RowId != payload.World.RowId) + continue; + + return character; + } + + return null; + } + internal void Hover(Payload payload) { } internal unsafe void Click(Chunk chunk, Payload? payload, ImGuiMouseButton button) { }