Merge pull request 'Added more checks in nameplate handler' (#82) from more-checks-nameplate into 1.12.4

Reviewed-on: #82
This commit was merged in pull request #82.
This commit is contained in:
2025-11-06 18:14:45 +01:00

View File

@@ -1,5 +1,6 @@
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
@@ -15,8 +16,9 @@ using LightlessSync.UtilsEnum.Enum;
// Created using https://github.com/PunishedPineapple/Distance as a reference, thank you!
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Text;
namespace LightlessSync.Services;
@@ -32,10 +34,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber
private readonly LightlessMediator _mediator;
public LightlessMediator Mediator => _mediator;
private bool mEnabled = false;
private bool _mEnabled = false;
private bool _needsLabelRefresh = false;
private AddonNamePlate* mpNameplateAddon = null;
private readonly AtkTextNode*[] mTextNodes = new AtkTextNode*[AddonNamePlate.NumNamePlateObjects];
private AddonNamePlate* _mpNameplateAddon = null;
private readonly AtkTextNode*[] _mTextNodes = new AtkTextNode*[AddonNamePlate.NumNamePlateObjects];
private readonly int[] _cachedNameplateTextWidths = new int[AddonNamePlate.NumNamePlateObjects];
private readonly int[] _cachedNameplateTextHeights = new int[AddonNamePlate.NumNamePlateObjects];
private readonly int[] _cachedNameplateContainerHeights = new int[AddonNamePlate.NumNamePlateObjects];
@@ -44,10 +46,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber
internal const uint mNameplateNodeIDBase = 0x7D99D500;
private const string DefaultLabelText = "LightFinder";
private const SeIconChar DefaultIcon = SeIconChar.Hyadelyn;
private const int ContainerOffsetX = 50;
private const int _containerOffsetX = 50;
private static readonly string DefaultIconGlyph = SeIconCharExtensions.ToIconString(DefaultIcon);
private volatile HashSet<string> _activeBroadcastingCids = [];
private ImmutableHashSet<string> _activeBroadcastingCids = [];
public NameplateHandler(ILogger<NameplateHandler> logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessConfigService configService, LightlessMediator mediator, IClientState clientState, PairManager pairManager)
{
@@ -74,17 +76,17 @@ public unsafe class NameplateHandler : IMediatorSubscriber
DisableNameplate();
DestroyNameplateNodes();
_mediator.Unsubscribe<PriorityFrameworkUpdateMessage>(this);
mpNameplateAddon = null;
_mpNameplateAddon = null;
}
internal void EnableNameplate()
{
if (!mEnabled)
if (!_mEnabled)
{
try
{
_addonLifecycle.RegisterListener(AddonEvent.PostDraw, "NamePlate", NameplateDrawDetour);
mEnabled = true;
_mEnabled = true;
}
catch (Exception e)
{
@@ -96,7 +98,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
internal void DisableNameplate()
{
if (mEnabled)
if (_mEnabled)
{
try
{
@@ -107,24 +109,30 @@ public unsafe class NameplateHandler : IMediatorSubscriber
_logger.LogError($"Unknown error while unregistering nameplate listener:\n{e}");
}
mEnabled = false;
_mEnabled = false;
HideAllNameplateNodes();
}
}
private void NameplateDrawDetour(AddonEvent type, AddonArgs args)
{
if (args.Addon.Address == nint.Zero)
{
_logger.LogWarning("Nameplate draw detour received a null addon address, skipping update.");
return;
}
var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
if (mpNameplateAddon != pNameplateAddon)
if (_mpNameplateAddon != pNameplateAddon)
{
for (int i = 0; i < mTextNodes.Length; ++i) mTextNodes[i] = null;
for (int i = 0; i < _mTextNodes.Length; ++i) _mTextNodes[i] = null;
System.Array.Clear(_cachedNameplateTextWidths, 0, _cachedNameplateTextWidths.Length);
System.Array.Clear(_cachedNameplateTextHeights, 0, _cachedNameplateTextHeights.Length);
System.Array.Clear(_cachedNameplateContainerHeights, 0, _cachedNameplateContainerHeights.Length);
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
mpNameplateAddon = pNameplateAddon;
if (mpNameplateAddon != null) CreateNameplateNodes();
_mpNameplateAddon = pNameplateAddon;
if (_mpNameplateAddon != null) CreateNameplateNodes();
}
UpdateNameplateNodes();
@@ -138,7 +146,16 @@ public unsafe class NameplateHandler : IMediatorSubscriber
if (nameplateObject == null)
continue;
var rootNode = nameplateObject.Value.RootComponentNode;
if (rootNode == null || rootNode->Component == null)
continue;
var pNameplateResNode = nameplateObject.Value.NameContainer;
if (pNameplateResNode == null)
continue;
if (pNameplateResNode->ChildNode == null)
continue;
var pNewNode = AtkNodeHelpers.CreateOrphanTextNode(mNameplateNodeIDBase + (uint)i, TextFlags.Edge | TextFlags.Glare);
if (pNewNode != null)
@@ -148,24 +165,43 @@ public unsafe class NameplateHandler : IMediatorSubscriber
pNewNode->AtkResNode.NextSiblingNode = pLastChild;
pNewNode->AtkResNode.ParentNode = pNameplateResNode;
pLastChild->PrevSiblingNode = (AtkResNode*)pNewNode;
nameplateObject.Value.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
rootNode->Component->UldManager.UpdateDrawNodeList();
pNewNode->AtkResNode.SetUseDepthBasedPriority(true);
mTextNodes[i] = pNewNode;
_mTextNodes[i] = pNewNode;
}
}
}
private void DestroyNameplateNodes()
{
var pCurrentNameplateAddon = (AddonNamePlate*)_gameGui.GetAddonByName("NamePlate", 1).Address;
if (mpNameplateAddon == null || mpNameplateAddon != pCurrentNameplateAddon)
var currentHandle = _gameGui.GetAddonByName("NamePlate", 1);
if (currentHandle.Address == nint.Zero)
{
_logger.LogWarning("Unable to destroy nameplate nodes because the NamePlate addon is not available.");
return;
}
var pCurrentNameplateAddon = (AddonNamePlate*)currentHandle.Address;
if (_mpNameplateAddon == null)
return;
if (_mpNameplateAddon != pCurrentNameplateAddon)
{
_logger.LogWarning("Skipping nameplate node destroy due to addon address mismatch (cached {Cached:X}, current {Current:X}).", (IntPtr)_mpNameplateAddon, (IntPtr)pCurrentNameplateAddon);
return;
}
for (int i = 0; i < AddonNamePlate.NumNamePlateObjects; ++i)
{
var pTextNode = mTextNodes[i];
var pTextNode = _mTextNodes[i];
var pNameplateNode = GetNameplateComponentNode(i);
if (pTextNode != null && pNameplateNode != null)
if (pTextNode != null && (pNameplateNode == null || pNameplateNode->Component == null))
{
_logger.LogDebug("Skipping destroy for nameplate {Index} because its component node is unavailable.", i);
continue;
}
if (pTextNode != null && pNameplateNode != null && pNameplateNode->Component != null)
{
try
{
@@ -175,7 +211,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
pTextNode->AtkResNode.NextSiblingNode->PrevSiblingNode = pTextNode->AtkResNode.PrevSiblingNode;
pNameplateNode->Component->UldManager.UpdateDrawNodeList();
pTextNode->AtkResNode.Destroy(true);
mTextNodes[i] = null;
_mTextNodes[i] = null;
}
catch (Exception e)
{
@@ -192,7 +228,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
private void HideAllNameplateNodes()
{
for (int i = 0; i < mTextNodes.Length; ++i)
for (int i = 0; i < _mTextNodes.Length; ++i)
{
HideNameplateTextNode(i);
}
@@ -200,22 +236,62 @@ public unsafe class NameplateHandler : IMediatorSubscriber
private void UpdateNameplateNodes()
{
var framework = Framework.Instance();
var ui3DModule = framework->GetUIModule()->GetUI3DModule();
var currentHandle = _gameGui.GetAddonByName("NamePlate");
if (currentHandle.Address == nint.Zero)
{
_logger.LogDebug("NamePlate addon unavailable during update, skipping label refresh.");
return;
}
var currentAddon = (AddonNamePlate*)currentHandle.Address;
if (_mpNameplateAddon == null || currentAddon == null || currentAddon != _mpNameplateAddon)
{
if (_mpNameplateAddon != null)
_logger.LogDebug("Cached NamePlate addon pointer differs from current: waiting for new hook (cached {Cached:X}, current {Current:X}).", (IntPtr)_mpNameplateAddon, (IntPtr)currentAddon);
return;
}
var framework = Framework.Instance();
if (framework == null)
{
_logger.LogDebug("Framework instance unavailable during nameplate update, skipping.");
return;
}
var uiModule = framework->GetUIModule();
if (uiModule == null)
{
_logger.LogDebug("UI module unavailable during nameplate update, skipping.");
return;
}
var ui3DModule = uiModule->GetUI3DModule();
if (ui3DModule == null)
{
_logger.LogDebug("UI3D module unavailable during nameplate update, skipping.");
return;
}
var vec = ui3DModule->NamePlateObjectInfoPointers;
if (vec.IsEmpty)
return;
for (int i = 0; i < ui3DModule->NamePlateObjectInfoCount; ++i)
var visibleUserIdsSnapshot = VisibleUserIds;
var safeCount = System.Math.Min(
ui3DModule->NamePlateObjectInfoCount,
vec.Length
);
for (int i = 0; i < safeCount; ++i)
{
if (ui3DModule->NamePlateObjectInfoPointers.IsEmpty) continue;
var config = _configService.Current;
var objectInfoPtr = ui3DModule->NamePlateObjectInfoPointers[i];
if (objectInfoPtr == null) continue;
var objectInfoPtr = vec[i];
if (objectInfoPtr == null)
continue;
var objectInfo = objectInfoPtr.Value;
if (objectInfo == null || objectInfo->GameObject == null)
continue;
@@ -223,62 +299,68 @@ public unsafe class NameplateHandler : IMediatorSubscriber
if (nameplateIndex < 0 || nameplateIndex >= AddonNamePlate.NumNamePlateObjects)
continue;
var pNode = mTextNodes[nameplateIndex];
var pNode = _mTextNodes[nameplateIndex];
if (pNode == null)
continue;
if (mpNameplateAddon == null)
var gameObject = objectInfo->GameObject;
if ((ObjectKind)gameObject->ObjectKind != ObjectKind.Player)
{
pNode->AtkResNode.ToggleVisibility(enable: false);
continue;
}
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)objectInfo->GameObject);
// CID gating
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)gameObject);
if (cid == null || !_activeBroadcastingCids.Contains(cid))
{
pNode->AtkResNode.ToggleVisibility(false);
pNode->AtkResNode.ToggleVisibility(enable: false);
continue;
}
if (!_configService.Current.LightfinderLabelShowOwn && (objectInfo->GameObject->GetGameObjectId() == _clientState.LocalPlayer.GameObjectId))
var local = _clientState.LocalPlayer;
if (!config.LightfinderLabelShowOwn && local != null &&
objectInfo->GameObject->GetGameObjectId() == local.GameObjectId)
{
pNode->AtkResNode.ToggleVisibility(false);
pNode->AtkResNode.ToggleVisibility(enable: false);
continue;
}
if (!_configService.Current.LightfinderLabelShowPaired && VisibleUserIds.Any(u => u == objectInfo->GameObject->GetGameObjectId()))
var hidePaired = !config.LightfinderLabelShowPaired;
var goId = (ulong)gameObject->GetGameObjectId();
if (hidePaired && visibleUserIdsSnapshot.Contains(goId))
{
pNode->AtkResNode.ToggleVisibility(false);
continue;
}
var nameplateObject = mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
nameplateObject.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
var pNameplateIconNode = nameplateObject.MarkerIcon;
var pNameplateResNode = nameplateObject.NameContainer;
var pNameplateTextNode = nameplateObject.NameText;
bool IsVisible = pNameplateIconNode->AtkResNode.IsVisible() || (pNameplateResNode->IsVisible() && pNameplateTextNode->AtkResNode.IsVisible()) || _configService.Current.LightfinderLabelShowHidden;
pNode->AtkResNode.ToggleVisibility(IsVisible);
if (nameplateObject.RootComponentNode == null ||
nameplateObject.NameContainer == null ||
nameplateObject.NameText == null)
{
pNode->AtkResNode.ToggleVisibility(false);
pNode->AtkResNode.ToggleVisibility(enable: false);
continue;
}
var nameplateObject = _mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
var root = nameplateObject.RootComponentNode;
var nameContainer = nameplateObject.NameContainer;
var nameText = nameplateObject.NameText;
var marker = nameplateObject.MarkerIcon;
if (nameContainer == null || nameText == null)
if (root == null || root->Component == null || nameContainer == null || nameText == null)
{
pNode->AtkResNode.ToggleVisibility(false);
_logger.LogDebug("Nameplate {Index} missing required nodes during update, skipping.", nameplateIndex);
pNode->AtkResNode.ToggleVisibility(enable: false);
continue;
}
root->Component->UldManager.UpdateDrawNodeList();
bool isVisible =
((marker != null) && marker->AtkResNode.IsVisible()) ||
(nameContainer->IsVisible() && nameText->AtkResNode.IsVisible()) ||
config.LightfinderLabelShowHidden;
pNode->AtkResNode.ToggleVisibility(isVisible);
if (!isVisible)
continue;
var labelColor = UIColors.Get("Lightfinder");
var edgeColor = UIColors.Get("LightfinderEdge");
var config = _configService.Current;
var scaleMultiplier = System.Math.Clamp(config.LightfinderLabelScale, 0.5f, 2.0f);
var baseScale = config.LightfinderLabelUseIcon ? 1.0f : 0.5f;
@@ -437,7 +519,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
positionY += config.LightfinderLabelOffsetY;
alignment = (AlignmentType)System.Math.Clamp((int)alignment, 0, 8);
pNode->AtkResNode.SetUseDepthBasedPriority(true);
pNode->AtkResNode.SetUseDepthBasedPriority(enable: true);
pNode->AtkResNode.Color.A = 255;
@@ -545,7 +627,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
}
private void HideNameplateTextNode(int i)
{
var pNode = mTextNodes[i];
var pNode = _mTextNodes[i];
if (pNode != null)
{
pNode->AtkResNode.ToggleVisibility(false);
@@ -555,10 +637,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber
private AddonNamePlate.NamePlateObject? GetNameplateObject(int i)
{
if (i < AddonNamePlate.NumNamePlateObjects &&
mpNameplateAddon != null &&
mpNameplateAddon->NamePlateObjectArray[i].RootComponentNode != null)
_mpNameplateAddon != null &&
_mpNameplateAddon->NamePlateObjectArray[i].RootComponentNode != null)
{
return mpNameplateAddon->NamePlateObjectArray[i];
return _mpNameplateAddon->NamePlateObjectArray[i];
}
else
{
@@ -571,10 +653,12 @@ public unsafe class NameplateHandler : IMediatorSubscriber
var nameplateObject = GetNameplateObject(i);
return nameplateObject != null ? nameplateObject.Value.RootComponentNode : null;
}
private HashSet<ulong> VisibleUserIds => [.. _pairManager.GetOnlineUserPairs()
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
.Select(u => (ulong)u.PlayerCharacterId)];
public void FlagRefresh()
{
_needsLabelRefresh = true;
@@ -591,18 +675,12 @@ public unsafe class NameplateHandler : IMediatorSubscriber
public void UpdateBroadcastingCids(IEnumerable<string> cids)
{
var newSet = cids.ToHashSet();
var changed = !_activeBroadcastingCids.SetEquals(newSet);
if (!changed)
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
if (ReferenceEquals(_activeBroadcastingCids, newSet) || _activeBroadcastingCids.SetEquals(newSet))
return;
_activeBroadcastingCids.Clear();
foreach (var cid in newSet)
_activeBroadcastingCids.Add(cid);
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(",", _activeBroadcastingCids));
_activeBroadcastingCids = newSet;
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids));
FlagRefresh();
}