From 5eed65149ae3cf35c684efd746ccee492e783f93 Mon Sep 17 00:00:00 2001 From: choco Date: Sat, 27 Dec 2025 02:38:56 +0100 Subject: [PATCH] nearby lightfinder users window, wiht pair func --- LightlessSync/Plugin.cs | 3 +- LightlessSync/Services/DalamudUtilService.cs | 22 ++ LightlessSync/UI/CompactUI.cs | 3 +- LightlessSync/UI/LightFinderUI.cs | 374 +++++++++++++++++-- 4 files changed, 366 insertions(+), 36 deletions(-) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 606a23d..d4e13b3 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -462,7 +462,8 @@ public sealed class Plugin : IDalamudPlugin sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService())); + sp.GetRequiredService(), + sp.GetRequiredService())); services.AddScoped(); services.AddScoped(); diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 106adf2..5908624 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -776,6 +776,28 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null; } + public void TargetPlayerByAddress(nint address) + { + if (address == nint.Zero) return; + if (_clientState.IsPvP) return; + + _ = RunOnFrameworkThread(() => + { + var gameObject = CreateGameObject(address); + if (gameObject is null) return; + + var useFocusTarget = _configService.Current.UseFocusTarget; + if (useFocusTarget) + { + _targetManager.FocusTarget = gameObject; + } + else + { + _targetManager.Target = gameObject; + } + }); + } + private unsafe void CheckCharacterForDrawing(nint address, string characterName) { var gameObj = (GameObject*)address; diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index be56434..79fbc88 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -519,7 +519,7 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.InvisibleButton("BroadcastIcon", buttonSize); var iconPos = ImGui.GetItemRectMin() + new Vector2(0f, iconYOffset); using (_uiSharedService.IconFont.Push()) - ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(UIColors.Get("LightlessGreen")), FontAwesomeIcon.PersonCirclePlus.ToIconString()); + ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(UIColors.Get("LightlessGreen")), FontAwesomeIcon.Wifi.ToIconString()); nextIconX = ImGui.GetItemRectMax().X + 6f; @@ -640,6 +640,7 @@ public class CompactUi : WindowMediatorSubscriberBase } } + if (uidTextHovered) { var padding = new Vector2(35f * ImGuiHelpers.GlobalScale); diff --git a/LightlessSync/UI/LightFinderUI.cs b/LightlessSync/UI/LightFinderUI.cs index b6bfc73..ed55d4f 100644 --- a/LightlessSync/UI/LightFinderUI.cs +++ b/LightlessSync/UI/LightFinderUI.cs @@ -11,7 +11,9 @@ using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto; using LightlessSync.API.Dto.Group; using LightlessSync.LightlessConfiguration; +using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; +using LightlessSync.Services.ActorTracking; using LightlessSync.Services.LightFinder; using LightlessSync.Services.Mediator; using LightlessSync.UI.Services; @@ -28,6 +30,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase { #region Services + private readonly ActorObjectService _actorObjectService; private readonly ApiController _apiController; private readonly DalamudUtilService _dalamudUtilService; private readonly LightFinderScannerService _broadcastScannerService; @@ -63,10 +66,10 @@ public class LightFinderUI : WindowMediatorSubscriberBase private LightfinderTab _selectedTab = LightfinderTab.NearbySyncshells; private string _userUid = string.Empty; - private enum LightfinderTab { NearbySyncshells, BroadcastSettings, Help } + private enum LightfinderTab { NearbySyncshells, NearbyPlayers, BroadcastSettings, Help } #if DEBUG - private enum LightfinderTabDebug { NearbySyncshells, BroadcastSettings, Help, Debug } + private enum LightfinderTabDebug { NearbySyncshells, NearbyPlayers, BroadcastSettings, Help, Debug } private LightfinderTabDebug _selectedTabDebug = LightfinderTabDebug.NearbySyncshells; #endif @@ -89,7 +92,8 @@ public class LightFinderUI : WindowMediatorSubscriberBase LightFinderScannerService broadcastScannerService, PairUiService pairUiService, DalamudUtilService dalamudUtilService, - LightlessProfileManager lightlessProfileManager + LightlessProfileManager lightlessProfileManager, + ActorObjectService actorObjectService ) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService) { _broadcastService = broadcastService; @@ -100,6 +104,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase _pairUiService = pairUiService; _dalamudUtilService = dalamudUtilService; _lightlessProfileManager = lightlessProfileManager; + _actorObjectService = actorObjectService; _animatedHeader.Height = 100f; _animatedHeader.EnableBottomGradient = true; @@ -158,6 +163,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase var debugTabOptions = new List> { new("Nearby Syncshells", LightfinderTabDebug.NearbySyncshells), + new("Nearby Players", LightfinderTabDebug.NearbyPlayers), new("Broadcast", LightfinderTabDebug.BroadcastSettings), new("Help", LightfinderTabDebug.Help), new("Debug", LightfinderTabDebug.Debug) @@ -171,6 +177,9 @@ public class LightFinderUI : WindowMediatorSubscriberBase case LightfinderTabDebug.NearbySyncshells: DrawNearbySyncshellsTab(); break; + case LightfinderTabDebug.NearbyPlayers: + DrawNearbyPlayersTab(); + break; case LightfinderTabDebug.BroadcastSettings: DrawBroadcastSettingsTab(); break; @@ -185,6 +194,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase var tabOptions = new List> { new("Nearby Syncshells", LightfinderTab.NearbySyncshells), + new("Nearby Players", LightfinderTab.NearbyPlayers), new("Broadcast", LightfinderTab.BroadcastSettings), new("Help", LightfinderTab.Help) }; @@ -197,6 +207,9 @@ public class LightFinderUI : WindowMediatorSubscriberBase case LightfinderTab.NearbySyncshells: DrawNearbySyncshellsTab(); break; + case LightfinderTab.NearbyPlayers: + DrawNearbyPlayersTab(); + break; case LightfinderTab.BroadcastSettings: DrawBroadcastSettingsTab(); break; @@ -241,8 +254,8 @@ public class LightFinderUI : WindowMediatorSubscriberBase { ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("Time", ImGuiTableColumnFlags.WidthStretch, 1f); - ImGui.TableSetupColumn("NearbyPlayers", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("NearbySyncshells", ImGuiTableColumnFlags.WidthStretch, 1f); + ImGui.TableSetupColumn("NearbyPlayers", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("Broadcasting", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed, buttonWidth + 16f * scale); @@ -278,16 +291,18 @@ public class LightFinderUI : WindowMediatorSubscriberBase } DrawStatusCell(FontAwesomeIcon.Clock, timeColor, timeValue, timeSub, infoColor, scale); - // Nearby players cell - var nearbyPlayerCount = _broadcastScannerService.CountActiveBroadcasts(); - var nearbyPlayerColor = nearbyPlayerCount > 0 ? UIColors.Get("LightlessBlue") : infoColor; - DrawStatusCell(FontAwesomeIcon.Users, nearbyPlayerColor, nearbyPlayerCount.ToString(), "Players", infoColor, scale); - // Nearby syncshells cell var nearbySyncshellCount = _nearbySyncshells.Count; var nearbySyncshellColor = nearbySyncshellCount > 0 ? UIColors.Get("LightlessPurple") : infoColor; DrawStatusCell(FontAwesomeIcon.Compass, nearbySyncshellColor, nearbySyncshellCount.ToString(), "Syncshells", infoColor, scale); - + + // Nearby players cell (exclude self) + string? myHashedCidForCount = null; + try { myHashedCidForCount = _dalamudUtilService.GetCID().ToString().GetHash256(); } catch { } + var nearbyPlayerCount = _broadcastScannerService.CountActiveBroadcasts(myHashedCidForCount); + var nearbyPlayerColor = nearbyPlayerCount > 0 ? UIColors.Get("LightlessBlue") : infoColor; + DrawStatusCell(FontAwesomeIcon.Users, nearbyPlayerColor, nearbyPlayerCount.ToString(), "Players", infoColor, scale); + // Broadcasting syncshell cell var isBroadcastingSyncshell = _configService.Current.SyncshellFinderEnabled && isBroadcasting; var broadcastSyncshellColor = isBroadcastingSyncshell ? UIColors.Get("LightlessGreen") : infoColor; @@ -409,7 +424,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase } } - private List<(GroupJoinDto Shell, string BroadcasterName)> BuildSyncshellCardData() + private List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)> BuildSyncshellCardData() { string? myHashedCid = null; try @@ -419,11 +434,9 @@ public class LightFinderUI : WindowMediatorSubscriberBase } catch { } - var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts() - .Where(b => !string.Equals(b.HashedCID, myHashedCid, StringComparison.Ordinal)) - .ToList(); + var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts().ToList(); - var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>(); + var cardData = new List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)>(); foreach (var shell in _nearbySyncshells) { @@ -434,7 +447,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase if (_useTestSyncshells) { var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID; - cardData.Add((shell, $"{displayName} (Test World)")); + cardData.Add((shell, $"{displayName} (Test World)", false)); continue; } #endif @@ -443,29 +456,39 @@ public class LightFinderUI : WindowMediatorSubscriberBase if (broadcast == null) continue; - var (name, address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); - if (string.IsNullOrEmpty(name)) - continue; + var isOwnBroadcast = !string.IsNullOrEmpty(myHashedCid) && string.Equals(broadcast.HashedCID, myHashedCid, StringComparison.Ordinal); - var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(address); - var broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{name} ({worldName})" : name; + string broadcasterName; + if (isOwnBroadcast) + { + broadcasterName = "You"; + } + else + { + var (name, address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); + if (string.IsNullOrEmpty(name)) + continue; - cardData.Add((shell, broadcasterName)); + var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(address); + broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{name} ({worldName})" : name; + } + + cardData.Add((shell, broadcasterName, isOwnBroadcast)); } return cardData; } - private void DrawSyncshellList(List<(GroupJoinDto Shell, string BroadcasterName)> listData) + private void DrawSyncshellList(List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)> listData) { ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f); ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f); if (ImGui.BeginChild("SyncshellListScroll", new Vector2(-1, -1), border: false)) { - foreach (var (shell, broadcasterName) in listData) + foreach (var (shell, broadcasterName, isOwnBroadcast) in listData) { - DrawSyncshellListItem(shell, broadcasterName); + DrawSyncshellListItem(shell, broadcasterName, isOwnBroadcast); ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale)); } } @@ -474,7 +497,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase ImGui.PopStyleVar(2); } - private void DrawSyncshellListItem(GroupJoinDto shell, string broadcasterName) + private void DrawSyncshellListItem(GroupJoinDto shell, string broadcasterName, bool isOwnBroadcast) { ImGui.PushID(shell.Group.GID); float rowHeight = 74f * ImGuiHelpers.GlobalScale; @@ -496,9 +519,12 @@ public class LightFinderUI : WindowMediatorSubscriberBase float rightX = startX + regionW - rightTxtW - style.ItemSpacing.X; ImGui.SameLine(); ImGui.SetCursorPosX(rightX); - ImGui.TextUnformatted(broadcasterName); + if (isOwnBroadcast) + ImGui.TextColored(UIColors.Get("LightlessGreen"), broadcasterName); + else + ImGui.TextUnformatted(broadcasterName); if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Broadcaster"); + ImGui.SetTooltip(isOwnBroadcast ? "Your broadcast" : "Broadcaster"); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); @@ -530,21 +556,21 @@ public class LightFinderUI : WindowMediatorSubscriberBase ImGui.PopID(); } - private void DrawSyncshellGrid(List<(GroupJoinDto Shell, string BroadcasterName)> cardData) + private void DrawSyncshellGrid(List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)> cardData) { ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f); ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f); - foreach (var (shell, _) in cardData) + foreach (var (shell, _, isOwnBroadcast) in cardData) { - DrawSyncshellCompactItem(shell); + DrawSyncshellCompactItem(shell, isOwnBroadcast); ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale)); } ImGui.PopStyleVar(2); } - private void DrawSyncshellCompactItem(GroupJoinDto shell) + private void DrawSyncshellCompactItem(GroupJoinDto shell, bool isOwnBroadcast) { ImGui.PushID(shell.Group.GID); float rowHeight = 36f * ImGuiHelpers.GlobalScale; @@ -552,11 +578,13 @@ public class LightFinderUI : WindowMediatorSubscriberBase ImGui.BeginChild($"ShellCompact##{shell.Group.GID}", new Vector2(-1, rowHeight), border: true); var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID; + if (isOwnBroadcast) + displayName += " (You)"; var style = ImGui.GetStyle(); float availW = ImGui.GetContentRegionAvail().X; ImGui.AlignTextToFramePadding(); - _uiSharedService.MediumText(displayName, UIColors.Get("LightlessPurple")); + _uiSharedService.MediumText(displayName, isOwnBroadcast ? UIColors.Get("LightlessGreen") : UIColors.Get("LightlessPurple")); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Click to open profile."); if (ImGui.IsItemClicked()) @@ -605,7 +633,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase using (ImRaii.Disabled()) ImGui.Button(label, buttonSize); - UiSharedService.AttachToolTip("You can't join your own Syncshell, silly! That's like trying to high-five yourself."); + UiSharedService.AttachToolTip("You can't join your own Syncshell..."); } else if (!isAlreadyMember && !isRecentlyJoined) { @@ -718,6 +746,284 @@ public class LightFinderUI : WindowMediatorSubscriberBase #endregion + #region Nearby Players Tab + + private void DrawNearbyPlayersTab() + { + ImGui.BeginGroup(); + string checkboxLabel = "Compact"; + float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight() + 8f; + float availWidth = ImGui.GetContentRegionAvail().X; + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + availWidth - checkboxWidth); + ImGui.Checkbox(checkboxLabel, ref _compactView); + ImGui.EndGroup(); + + ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale)); + + if (!_broadcastService.IsBroadcasting) + { + ImGui.TextColored(UIColors.Get("LightlessYellow"), "Lightfinder must be active to see nearby players."); + return; + } + + string? myHashedCid = null; + try + { + var cid = _dalamudUtilService.GetCID(); + myHashedCid = cid.ToString().GetHash256(); + } + catch { } + + var activeBroadcasts = _broadcastScannerService.GetActiveBroadcasts(myHashedCid); + + if (activeBroadcasts.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby Lightfinder players found."); + return; + } + + var playerData = BuildNearbyPlayerData(activeBroadcasts); + if (playerData.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby Lightfinder players found."); + return; + } + + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f); + ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f); + + if (ImGui.BeginChild("NearbyPlayersScroll", new Vector2(-1, -1), border: false)) + { + foreach (var data in playerData) + { + if (_compactView) + DrawNearbyPlayerCompactRow(data); + else + DrawNearbyPlayerRow(data); + ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale)); + } + } + ImGui.EndChild(); + + ImGui.PopStyleVar(2); + } + + private List BuildNearbyPlayerData(List> activeBroadcasts) + { + var snapshot = _pairUiService.GetSnapshot(); + var playerData = new List(); + + foreach (var broadcast in activeBroadcasts) + { + var (name, address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.Key); + if (string.IsNullOrEmpty(name) || address == nint.Zero) + continue; + + var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(address); + var pair = snapshot.PairsByUid.Values.FirstOrDefault(p => + p.IsVisible && + !string.IsNullOrEmpty(p.GetPlayerNameHash()) && + string.Equals(p.GetPlayerNameHash(), broadcast.Key, StringComparison.Ordinal)); + + var isDirectlyPaired = pair?.IsDirectlyPaired ?? false; + var sharedGroups = pair?.UserPair?.Groups ?? []; + var sharedGroupNames = sharedGroups + .Select(gid => snapshot.GroupsByGid.TryGetValue(gid, out var g) ? g.GroupAliasOrGID : gid) + .ToList(); + + playerData.Add(new NearbyPlayerData(broadcast.Key, name, worldName, address, pair, isDirectlyPaired, sharedGroupNames)); + } + + return playerData; + } + + private readonly record struct NearbyPlayerData( + string HashedCid, + string Name, + string? World, + nint Address, + Pair? Pair, + bool IsDirectlyPaired, + List SharedSyncshells); + + private void DrawNearbyPlayerRow(NearbyPlayerData data) + { + ImGui.PushID(data.HashedCid); + float rowHeight = 74f * ImGuiHelpers.GlobalScale; + + ImGui.BeginChild($"PlayerRow##{data.HashedCid}", new Vector2(-1, rowHeight), border: true); + + var serverName = !string.IsNullOrEmpty(data.World) ? data.World : "Unknown"; + var style = ImGui.GetStyle(); + float startX = ImGui.GetCursorPosX(); + float regionW = ImGui.GetContentRegionAvail().X; + float rightTxtW = ImGui.CalcTextSize(serverName).X; + + _uiSharedService.MediumText(data.Name, UIColors.Get("LightlessPurple")); + if (data.Pair != null) + { + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Click to open profile."); + if (ImGui.IsItemClicked()) + Mediator.Publish(new ProfileOpenStandaloneMessage(data.Pair)); + } + + float rightX = startX + regionW - rightTxtW - style.ItemSpacing.X; + ImGui.SameLine(); + ImGui.SetCursorPosX(rightX); + ImGui.TextUnformatted(serverName); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Home World"); + + UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); + + Vector2 rowStartLocal = ImGui.GetCursorPos(); + + if (data.IsDirectlyPaired) + { + ImGui.SetCursorPosX(startX); + _uiSharedService.IconText(FontAwesomeIcon.UserCheck, UIColors.Get("LightlessGreen")); + ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale); + ImGui.TextColored(UIColors.Get("LightlessGreen"), "Direct Pair"); + } + else if (data.SharedSyncshells.Count > 0) + { + ImGui.SetCursorPosX(startX); + _uiSharedService.IconText(FontAwesomeIcon.Users, UIColors.Get("LightlessPurple")); + ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale); + var shellText = data.SharedSyncshells.Count == 1 + ? data.SharedSyncshells[0] + : $"{data.SharedSyncshells.Count} shared shells"; + ImGui.TextColored(UIColors.Get("LightlessPurple"), shellText); + if (data.SharedSyncshells.Count > 1 && ImGui.IsItemHovered()) + ImGui.SetTooltip(string.Join("\n", data.SharedSyncshells)); + } + else + { + ImGui.SetCursorPosX(startX); + _uiSharedService.IconText(FontAwesomeIcon.Wifi, UIColors.Get("LightlessBlue")); + ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale); + ImGui.TextColored(ImGuiColors.DalamudGrey, "Lightfinder User"); + } + + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + + DrawPlayerActionButtons(data, startX, regionW, rowStartLocal.Y, style); + + ImGui.EndChild(); + ImGui.PopID(); + } + + private void DrawNearbyPlayerCompactRow(NearbyPlayerData data) + { + ImGui.PushID(data.HashedCid); + float rowHeight = 36f * ImGuiHelpers.GlobalScale; + + ImGui.BeginChild($"PlayerCompact##{data.HashedCid}", new Vector2(-1, rowHeight), border: true); + + ImGui.AlignTextToFramePadding(); + + if (data.IsDirectlyPaired) + { + _uiSharedService.IconText(FontAwesomeIcon.UserCheck, UIColors.Get("LightlessGreen")); + ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale); + } + else if (data.SharedSyncshells.Count > 0) + { + _uiSharedService.IconText(FontAwesomeIcon.Users, UIColors.Get("LightlessPurple")); + ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale); + } + else + { + _uiSharedService.IconText(FontAwesomeIcon.Wifi, UIColors.Get("LightlessBlue")); + ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale); + } + + var displayText = !string.IsNullOrEmpty(data.World) ? $"{data.Name} ({data.World})" : data.Name; + _uiSharedService.MediumText(displayText, UIColors.Get("LightlessPurple")); + if (data.Pair != null) + { + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Click to open profile."); + if (ImGui.IsItemClicked()) + Mediator.Publish(new ProfileOpenStandaloneMessage(data.Pair)); + } + + ImGui.SameLine(); + DrawPlayerActionButtons(data, 0, ImGui.GetContentRegionAvail().X, ImGui.GetCursorPosY(), ImGui.GetStyle(), compact: true); + + ImGui.EndChild(); + ImGui.PopID(); + } + + private void DrawPlayerActionButtons(NearbyPlayerData data, float startX, float regionW, float rowY, ImGuiStylePtr style, bool compact = false) + { + float buttonWidth = compact ? 60f * ImGuiHelpers.GlobalScale : 80f * ImGuiHelpers.GlobalScale; + float buttonHeight = compact ? 0 : 30f; + float totalButtonsWidth = buttonWidth * 2 + style.ItemSpacing.X; + + if (compact) + { + float availX = ImGui.GetContentRegionAvail().X; + float curX = ImGui.GetCursorPosX(); + ImGui.SetCursorPosX(curX + availX - totalButtonsWidth); + } + else + { + ImGui.SetCursorPos(new Vector2(startX + regionW - totalButtonsWidth - style.ItemSpacing.X, rowY)); + } + + using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessGreen"))) + using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f))) + using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f))) + using (ImRaii.Disabled(data.IsDirectlyPaired)) + { + if (ImGui.Button($"Pair##{data.HashedCid}", new Vector2(buttonWidth, buttonHeight))) + { + _ = SendPairRequestAsync(data.HashedCid); + } + } + if (data.IsDirectlyPaired) + UiSharedService.AttachToolTip("Already directly paired with this player."); + + ImGui.SameLine(); + + using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessPurple"))) + using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple").WithAlpha(0.85f))) + using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurple").WithAlpha(0.75f))) + { + if (ImGui.Button($"Target##{data.HashedCid}", new Vector2(buttonWidth, buttonHeight))) + { + TargetPlayerByAddress(data.Address); + } + } + } + + private async Task SendPairRequestAsync(string hashedCid) + { + if (string.IsNullOrWhiteSpace(hashedCid)) + return; + + try + { + await _apiController.TryPairWithContentId(hashedCid).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send pair request to {HashedCid}", hashedCid); + } + } + + private void TargetPlayerByAddress(nint address) + { + if (address == nint.Zero) + return; + + _dalamudUtilService.TargetPlayerByAddress(address); + } + + #endregion + #region Broadcast Settings Tab private void DrawBroadcastSettingsTab()