nearby lightfinder users window, wiht pair func

This commit is contained in:
choco
2025-12-27 02:38:56 +01:00
parent 1ab4e2f94b
commit 5eed65149a
4 changed files with 366 additions and 36 deletions

View File

@@ -462,7 +462,8 @@ public sealed class Plugin : IDalamudPlugin
sp.GetRequiredService<LightFinderScannerService>(), sp.GetRequiredService<LightFinderScannerService>(),
sp.GetRequiredService<PairUiService>(), sp.GetRequiredService<PairUiService>(),
sp.GetRequiredService<DalamudUtilService>(), sp.GetRequiredService<DalamudUtilService>(),
sp.GetRequiredService<LightlessProfileManager>())); sp.GetRequiredService<LightlessProfileManager>(),
sp.GetRequiredService<ActorObjectService>()));
services.AddScoped<IPopupHandler, BanUserPopupHandler>(); services.AddScoped<IPopupHandler, BanUserPopupHandler>();
services.AddScoped<IPopupHandler, CensusPopupHandler>(); services.AddScoped<IPopupHandler, CensusPopupHandler>();

View File

@@ -776,6 +776,28 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null; 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) private unsafe void CheckCharacterForDrawing(nint address, string characterName)
{ {
var gameObj = (GameObject*)address; var gameObj = (GameObject*)address;

View File

@@ -519,7 +519,7 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.InvisibleButton("BroadcastIcon", buttonSize); ImGui.InvisibleButton("BroadcastIcon", buttonSize);
var iconPos = ImGui.GetItemRectMin() + new Vector2(0f, iconYOffset); var iconPos = ImGui.GetItemRectMin() + new Vector2(0f, iconYOffset);
using (_uiSharedService.IconFont.Push()) 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; nextIconX = ImGui.GetItemRectMax().X + 6f;
@@ -640,6 +640,7 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
} }
if (uidTextHovered) if (uidTextHovered)
{ {
var padding = new Vector2(35f * ImGuiHelpers.GlobalScale); var padding = new Vector2(35f * ImGuiHelpers.GlobalScale);

View File

@@ -11,7 +11,9 @@ using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto; using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.LightFinder; using LightlessSync.Services.LightFinder;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services; using LightlessSync.UI.Services;
@@ -28,6 +30,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
{ {
#region Services #region Services
private readonly ActorObjectService _actorObjectService;
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private readonly LightFinderScannerService _broadcastScannerService; private readonly LightFinderScannerService _broadcastScannerService;
@@ -63,10 +66,10 @@ public class LightFinderUI : WindowMediatorSubscriberBase
private LightfinderTab _selectedTab = LightfinderTab.NearbySyncshells; private LightfinderTab _selectedTab = LightfinderTab.NearbySyncshells;
private string _userUid = string.Empty; private string _userUid = string.Empty;
private enum LightfinderTab { NearbySyncshells, BroadcastSettings, Help } private enum LightfinderTab { NearbySyncshells, NearbyPlayers, BroadcastSettings, Help }
#if DEBUG #if DEBUG
private enum LightfinderTabDebug { NearbySyncshells, BroadcastSettings, Help, Debug } private enum LightfinderTabDebug { NearbySyncshells, NearbyPlayers, BroadcastSettings, Help, Debug }
private LightfinderTabDebug _selectedTabDebug = LightfinderTabDebug.NearbySyncshells; private LightfinderTabDebug _selectedTabDebug = LightfinderTabDebug.NearbySyncshells;
#endif #endif
@@ -89,7 +92,8 @@ public class LightFinderUI : WindowMediatorSubscriberBase
LightFinderScannerService broadcastScannerService, LightFinderScannerService broadcastScannerService,
PairUiService pairUiService, PairUiService pairUiService,
DalamudUtilService dalamudUtilService, DalamudUtilService dalamudUtilService,
LightlessProfileManager lightlessProfileManager LightlessProfileManager lightlessProfileManager,
ActorObjectService actorObjectService
) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService) ) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService)
{ {
_broadcastService = broadcastService; _broadcastService = broadcastService;
@@ -100,6 +104,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
_pairUiService = pairUiService; _pairUiService = pairUiService;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_lightlessProfileManager = lightlessProfileManager; _lightlessProfileManager = lightlessProfileManager;
_actorObjectService = actorObjectService;
_animatedHeader.Height = 100f; _animatedHeader.Height = 100f;
_animatedHeader.EnableBottomGradient = true; _animatedHeader.EnableBottomGradient = true;
@@ -158,6 +163,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
var debugTabOptions = new List<UiSharedService.TabOption<LightfinderTabDebug>> var debugTabOptions = new List<UiSharedService.TabOption<LightfinderTabDebug>>
{ {
new("Nearby Syncshells", LightfinderTabDebug.NearbySyncshells), new("Nearby Syncshells", LightfinderTabDebug.NearbySyncshells),
new("Nearby Players", LightfinderTabDebug.NearbyPlayers),
new("Broadcast", LightfinderTabDebug.BroadcastSettings), new("Broadcast", LightfinderTabDebug.BroadcastSettings),
new("Help", LightfinderTabDebug.Help), new("Help", LightfinderTabDebug.Help),
new("Debug", LightfinderTabDebug.Debug) new("Debug", LightfinderTabDebug.Debug)
@@ -171,6 +177,9 @@ public class LightFinderUI : WindowMediatorSubscriberBase
case LightfinderTabDebug.NearbySyncshells: case LightfinderTabDebug.NearbySyncshells:
DrawNearbySyncshellsTab(); DrawNearbySyncshellsTab();
break; break;
case LightfinderTabDebug.NearbyPlayers:
DrawNearbyPlayersTab();
break;
case LightfinderTabDebug.BroadcastSettings: case LightfinderTabDebug.BroadcastSettings:
DrawBroadcastSettingsTab(); DrawBroadcastSettingsTab();
break; break;
@@ -185,6 +194,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
var tabOptions = new List<UiSharedService.TabOption<LightfinderTab>> var tabOptions = new List<UiSharedService.TabOption<LightfinderTab>>
{ {
new("Nearby Syncshells", LightfinderTab.NearbySyncshells), new("Nearby Syncshells", LightfinderTab.NearbySyncshells),
new("Nearby Players", LightfinderTab.NearbyPlayers),
new("Broadcast", LightfinderTab.BroadcastSettings), new("Broadcast", LightfinderTab.BroadcastSettings),
new("Help", LightfinderTab.Help) new("Help", LightfinderTab.Help)
}; };
@@ -197,6 +207,9 @@ public class LightFinderUI : WindowMediatorSubscriberBase
case LightfinderTab.NearbySyncshells: case LightfinderTab.NearbySyncshells:
DrawNearbySyncshellsTab(); DrawNearbySyncshellsTab();
break; break;
case LightfinderTab.NearbyPlayers:
DrawNearbyPlayersTab();
break;
case LightfinderTab.BroadcastSettings: case LightfinderTab.BroadcastSettings:
DrawBroadcastSettingsTab(); DrawBroadcastSettingsTab();
break; break;
@@ -241,8 +254,8 @@ public class LightFinderUI : WindowMediatorSubscriberBase
{ {
ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("Time", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("Time", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("NearbyPlayers", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("NearbySyncshells", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("NearbySyncshells", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("NearbyPlayers", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("Broadcasting", ImGuiTableColumnFlags.WidthStretch, 1f); ImGui.TableSetupColumn("Broadcasting", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed, buttonWidth + 16f * scale); ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed, buttonWidth + 16f * scale);
@@ -278,16 +291,18 @@ public class LightFinderUI : WindowMediatorSubscriberBase
} }
DrawStatusCell(FontAwesomeIcon.Clock, timeColor, timeValue, timeSub, infoColor, scale); 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 // Nearby syncshells cell
var nearbySyncshellCount = _nearbySyncshells.Count; var nearbySyncshellCount = _nearbySyncshells.Count;
var nearbySyncshellColor = nearbySyncshellCount > 0 ? UIColors.Get("LightlessPurple") : infoColor; var nearbySyncshellColor = nearbySyncshellCount > 0 ? UIColors.Get("LightlessPurple") : infoColor;
DrawStatusCell(FontAwesomeIcon.Compass, nearbySyncshellColor, nearbySyncshellCount.ToString(), "Syncshells", infoColor, scale); 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 // Broadcasting syncshell cell
var isBroadcastingSyncshell = _configService.Current.SyncshellFinderEnabled && isBroadcasting; var isBroadcastingSyncshell = _configService.Current.SyncshellFinderEnabled && isBroadcasting;
var broadcastSyncshellColor = isBroadcastingSyncshell ? UIColors.Get("LightlessGreen") : infoColor; 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; string? myHashedCid = null;
try try
@@ -419,11 +434,9 @@ public class LightFinderUI : WindowMediatorSubscriberBase
} }
catch { } catch { }
var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts() var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts().ToList();
.Where(b => !string.Equals(b.HashedCID, myHashedCid, StringComparison.Ordinal))
.ToList();
var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>(); var cardData = new List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)>();
foreach (var shell in _nearbySyncshells) foreach (var shell in _nearbySyncshells)
{ {
@@ -434,7 +447,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
if (_useTestSyncshells) if (_useTestSyncshells)
{ {
var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID; 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; continue;
} }
#endif #endif
@@ -443,29 +456,39 @@ public class LightFinderUI : WindowMediatorSubscriberBase
if (broadcast == null) if (broadcast == null)
continue; continue;
var (name, address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); var isOwnBroadcast = !string.IsNullOrEmpty(myHashedCid) && string.Equals(broadcast.HashedCID, myHashedCid, StringComparison.Ordinal);
if (string.IsNullOrEmpty(name))
continue;
var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(address); string broadcasterName;
var broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{name} ({worldName})" : name; 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; 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.FrameRounding, 8f);
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f); ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
if (ImGui.BeginChild("SyncshellListScroll", new Vector2(-1, -1), border: false)) 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)); ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));
} }
} }
@@ -474,7 +497,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
ImGui.PopStyleVar(2); ImGui.PopStyleVar(2);
} }
private void DrawSyncshellListItem(GroupJoinDto shell, string broadcasterName) private void DrawSyncshellListItem(GroupJoinDto shell, string broadcasterName, bool isOwnBroadcast)
{ {
ImGui.PushID(shell.Group.GID); ImGui.PushID(shell.Group.GID);
float rowHeight = 74f * ImGuiHelpers.GlobalScale; float rowHeight = 74f * ImGuiHelpers.GlobalScale;
@@ -496,9 +519,12 @@ public class LightFinderUI : WindowMediatorSubscriberBase
float rightX = startX + regionW - rightTxtW - style.ItemSpacing.X; float rightX = startX + regionW - rightTxtW - style.ItemSpacing.X;
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetCursorPosX(rightX); ImGui.SetCursorPosX(rightX);
ImGui.TextUnformatted(broadcasterName); if (isOwnBroadcast)
ImGui.TextColored(UIColors.Get("LightlessGreen"), broadcasterName);
else
ImGui.TextUnformatted(broadcasterName);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Broadcaster"); ImGui.SetTooltip(isOwnBroadcast ? "Your broadcast" : "Broadcaster");
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
@@ -530,21 +556,21 @@ public class LightFinderUI : WindowMediatorSubscriberBase
ImGui.PopID(); 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.FrameRounding, 8f);
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f); 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.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));
} }
ImGui.PopStyleVar(2); ImGui.PopStyleVar(2);
} }
private void DrawSyncshellCompactItem(GroupJoinDto shell) private void DrawSyncshellCompactItem(GroupJoinDto shell, bool isOwnBroadcast)
{ {
ImGui.PushID(shell.Group.GID); ImGui.PushID(shell.Group.GID);
float rowHeight = 36f * ImGuiHelpers.GlobalScale; 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); 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; var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID;
if (isOwnBroadcast)
displayName += " (You)";
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
float availW = ImGui.GetContentRegionAvail().X; float availW = ImGui.GetContentRegionAvail().X;
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
_uiSharedService.MediumText(displayName, UIColors.Get("LightlessPurple")); _uiSharedService.MediumText(displayName, isOwnBroadcast ? UIColors.Get("LightlessGreen") : UIColors.Get("LightlessPurple"));
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Click to open profile."); ImGui.SetTooltip("Click to open profile.");
if (ImGui.IsItemClicked()) if (ImGui.IsItemClicked())
@@ -605,7 +633,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
using (ImRaii.Disabled()) using (ImRaii.Disabled())
ImGui.Button(label, buttonSize); 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) else if (!isAlreadyMember && !isRecentlyJoined)
{ {
@@ -718,6 +746,284 @@ public class LightFinderUI : WindowMediatorSubscriberBase
#endregion #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<NearbyPlayerData> BuildNearbyPlayerData(List<KeyValuePair<string, LightFinderScannerService.BroadcastEntry>> activeBroadcasts)
{
var snapshot = _pairUiService.GetSnapshot();
var playerData = new List<NearbyPlayerData>();
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<string> 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 #region Broadcast Settings Tab
private void DrawBroadcastSettingsTab() private void DrawBroadcastSettingsTab()