diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index b5cb97f..c20d631 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -78,7 +78,9 @@ public class LightlessConfig : ILightlessConfiguration public short LightfinderLabelOffsetX { get; set; } = 0; public short LightfinderLabelOffsetY { get; set; } = 0; public bool LightfinderLabelUseIcon { get; set; } = false; - public string LightfinderLabelIconGlyph { get; set; } = SeIconCharExtensions.ToIconString(SeIconChar.LinkMarker); + public bool LightfinderLabelShowOwn { get; set; } = true; + public bool LightfinderLabelShowPaired { get; set; } = true; + public string LightfinderLabelIconGlyph { get; set; } = SeIconCharExtensions.ToIconString(SeIconChar.Hyadelyn); public float LightfinderLabelScale { get; set; } = 1.0f; public bool LightfinderAutoAlign { get; set; } = true; public LabelAlignment LabelAlignment { get; set; } = LabelAlignment.Left; diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 90ed4ae..c1648ca 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -208,7 +208,6 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); collection.AddSingleton(s => new BroadcastScannerService( s.GetRequiredService>(), clientState, objectTable, framework, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); @@ -253,6 +252,8 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService())); collection.AddScoped((s) => new NameplateService(s.GetRequiredService>(), s.GetRequiredService(), namePlateGui, clientState, s.GetRequiredService(), s.GetRequiredService())); + collection.AddScoped((s) => new NameplateHandler(s.GetRequiredService>(), addonLifecycle, gameGui, s.GetRequiredService(), + s.GetRequiredService(), s.GetRequiredService(), clientState, s.GetRequiredService())); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); diff --git a/LightlessSync/Services/ContextMenuService.cs b/LightlessSync/Services/ContextMenuService.cs index 12dae60..ad00514 100644 --- a/LightlessSync/Services/ContextMenuService.cs +++ b/LightlessSync/Services/ContextMenuService.cs @@ -158,6 +158,7 @@ internal class ContextMenuService : IHostedService _logger.LogError(ex, "Error sending pair request."); } } + private HashSet VisibleUserIds => [.. _pairManager.GetOnlineUserPairs() .Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue) .Select(u => (ulong)u.PlayerCharacterId)]; diff --git a/LightlessSync/Services/NameplateHandler.cs b/LightlessSync/Services/NameplateHandler.cs index abe6266..74edabc 100644 --- a/LightlessSync/Services/NameplateHandler.cs +++ b/LightlessSync/Services/NameplateHandler.cs @@ -6,7 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; using LightlessSync.LightlessConfiguration; -using LightlessSync.LightlessConfiguration.Configurations; +using LightlessSync.PlayerData.Pairs; using LightlessSync.Services.Mediator; using LightlessSync.UI; using LightlessSync.Utils; @@ -25,8 +25,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber private readonly ILogger _logger; private readonly IAddonLifecycle _addonLifecycle; private readonly IGameGui _gameGui; + private readonly IClientState _clientState; private readonly DalamudUtilService _dalamudUtil; private readonly LightlessConfigService _configService; + private readonly PairManager _pairManager; private readonly LightlessMediator _mediator; public LightlessMediator Mediator => _mediator; @@ -40,13 +42,14 @@ public unsafe class NameplateHandler : IMediatorSubscriber private readonly int[] _cachedNameplateTextOffsets = new int[AddonNamePlate.NumNamePlateObjects]; internal const uint mNameplateNodeIDBase = 0x7D99D500; - private const string DefaultLabelText = "Lightfinder"; - private const SeIconChar DefaultIcon = SeIconChar.LinkMarker; + private const string DefaultLabelText = "LightFinder"; + private const SeIconChar DefaultIcon = SeIconChar.Hyadelyn; + private const int ContainerOffsetX = 50; private static readonly string DefaultIconGlyph = SeIconCharExtensions.ToIconString(DefaultIcon); - private volatile HashSet _activeBroadcastingCids = new(); + private volatile HashSet _activeBroadcastingCids = []; - public NameplateHandler(ILogger logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessConfigService configService, LightlessMediator mediator) + public NameplateHandler(ILogger logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessConfigService configService, LightlessMediator mediator, IClientState clientState, PairManager pairManager) { _logger = logger; _addonLifecycle = addonLifecycle; @@ -54,6 +57,8 @@ public unsafe class NameplateHandler : IMediatorSubscriber _dalamudUtil = dalamudUtil; _configService = configService; _mediator = mediator; + _clientState = clientState; + _pairManager = pairManager; System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue); } @@ -218,14 +223,24 @@ public unsafe class NameplateHandler : IMediatorSubscriber var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)objectInfo->GameObject); - //_logger.LogInformation($"checking cid: {cid}", cid); - if (cid == null || !_activeBroadcastingCids.Contains(cid)) { pNode->AtkResNode.ToggleVisibility(false); continue; } + if (!_configService.Current.LightfinderLabelShowOwn && (objectInfo->GameObject->GetGameObjectId() == _clientState.LocalPlayer.GameObjectId)) + { + pNode->AtkResNode.ToggleVisibility(false); + continue; + } + + if (!_configService.Current.LightfinderLabelShowPaired && VisibleUserIds.Any(u => u == objectInfo->GameObject->GetGameObjectId())) + { + pNode->AtkResNode.ToggleVisibility(false); + continue; + } + var nameplateObject = mpNameplateAddon->NamePlateObjectArray[nameplateIndex]; nameplateObject.RootComponentNode->Component->UldManager.UpdateDrawNodeList(); @@ -251,11 +266,20 @@ public unsafe class NameplateHandler : IMediatorSubscriber var scaleMultiplier = System.Math.Clamp(config.LightfinderLabelScale, 0.5f, 2.0f); var baseScale = config.LightfinderLabelUseIcon ? 1.0f : 0.5f; var effectiveScale = baseScale * scaleMultiplier; - var nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale); - var nodeHeight = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale); + var labelContent = config.LightfinderLabelUseIcon + ? NormalizeIconGlyph(config.LightfinderLabelIconGlyph) + : DefaultLabelText; - int positionX = 58; - AlignmentType alignment = AlignmentType.Bottom; + pNode->FontType = config.LightfinderLabelUseIcon ? FontType.Axis : FontType.MiedingerMed; + pNode->AtkResNode.SetScale(effectiveScale, effectiveScale); + var nodeWidth = (int)pNode->AtkResNode.GetWidth(); + if (nodeWidth <= 0) + nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale); + var nodeHeight = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale); + var baseFontSize = config.LightfinderLabelUseIcon ? 36f : 24f; + var computedFontSize = (int)System.Math.Round(baseFontSize * scaleMultiplier); + pNode->FontSize = (byte)System.Math.Clamp(computedFontSize, 1, 255); + AlignmentType alignment; var textScaleY = nameText->AtkResNode.ScaleY; if (textScaleY <= 0f) @@ -307,21 +331,15 @@ public unsafe class NameplateHandler : IMediatorSubscriber var positionY = blockTop - verticalPadding - nodeHeight; var textWidth = System.Math.Abs((int)nameplateObject.TextW); - if (textWidth > 0) - { - _cachedNameplateTextWidths[nameplateIndex] = textWidth; - } - else - { - textWidth = _cachedNameplateTextWidths[nameplateIndex]; - } - if (textWidth <= 0) { textWidth = GetScaledTextWidth(nameText); if (textWidth <= 0) textWidth = nodeWidth; + } + if (textWidth > 0) + { _cachedNameplateTextWidths[nameplateIndex] = textWidth; } @@ -340,37 +358,67 @@ public unsafe class NameplateHandler : IMediatorSubscriber { hasValidOffset = false; } + int positionX; if (config.LightfinderAutoAlign && nameContainer != null && hasValidOffset) { + var nameplateWidth = (int)nameContainer->Width; + + if (!config.LightfinderLabelUseIcon) + { + pNode->TextFlags &= ~TextFlags.AutoAdjustNodeSize; + pNode->AtkResNode.Width = 0; + pNode->SetText(labelContent); + + nodeWidth = (int)pNode->AtkResNode.GetWidth(); + if (nodeWidth <= 0) + nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale); + + if (nodeWidth > nameplateWidth) + nodeWidth = nameplateWidth; + + pNode->AtkResNode.Width = (ushort)nodeWidth; + } + else + { + pNode->TextFlags |= TextFlags.AutoAdjustNodeSize; + pNode->AtkResNode.Width = 0; + pNode->SetText(labelContent); + nodeWidth = (int)pNode->AtkResNode.GetWidth(); + } + + int leftPos = nameplateWidth / 8; + int rightPos = nameplateWidth - nodeWidth - (nameplateWidth / 8); + int centrePos = (nameplateWidth - nodeWidth) / 2; + int staticMargin = 24; + int calcMargin = (int)(nameplateWidth * 0.08f); + switch (config.LabelAlignment) { case LabelAlignment.Left: - positionX = textOffset; + positionX = config.LightfinderLabelUseIcon ? leftPos + staticMargin : leftPos; alignment = AlignmentType.BottomLeft; break; case LabelAlignment.Right: - positionX = textOffset + textWidth - nodeWidth; + positionX = config.LightfinderLabelUseIcon ? rightPos - staticMargin : nameplateWidth - nodeWidth + calcMargin; alignment = AlignmentType.BottomRight; break; default: - positionX = textOffset + textWidth / 2 - nodeWidth / 2; + positionX = config.LightfinderLabelUseIcon ? centrePos : centrePos + calcMargin; alignment = AlignmentType.Bottom; break; } } else { + positionX = 58 + config.LightfinderLabelOffsetX; alignment = AlignmentType.Bottom; } - positionX += config.LightfinderLabelOffsetX; positionY += config.LightfinderLabelOffsetY; alignment = (AlignmentType)System.Math.Clamp((int)alignment, 0, 8); - pNode->AtkResNode.SetPositionShort((short)System.Math.Clamp(positionX, short.MinValue, short.MaxValue), (short)System.Math.Clamp(positionY, short.MinValue, short.MaxValue)); pNode->AtkResNode.SetUseDepthBasedPriority(true); - pNode->AtkResNode.SetScale(effectiveScale, effectiveScale); pNode->AtkResNode.Color.A = 255; @@ -384,24 +432,25 @@ public unsafe class NameplateHandler : IMediatorSubscriber pNode->EdgeColor.B = (byte)(edgeColor.Z * 255); pNode->EdgeColor.A = (byte)(edgeColor.W * 255); - var baseFontSize = config.LightfinderLabelUseIcon ? 36f : 24f; - var computedFontSize = (int)System.Math.Round(baseFontSize * scaleMultiplier); - pNode->FontSize = (byte)System.Math.Clamp(computedFontSize, 1, 255); - pNode->AlignmentType = alignment; + + if(!config.LightfinderLabelUseIcon) + { + pNode->AlignmentType = AlignmentType.Bottom; + } + else + { + pNode->AlignmentType = alignment; + } + pNode->AtkResNode.SetPositionShort( + (short)System.Math.Clamp(positionX, short.MinValue, short.MaxValue), + (short)System.Math.Clamp(positionY, short.MinValue, short.MaxValue) + ); var computedLineSpacing = (int)System.Math.Round(24 * scaleMultiplier); pNode->LineSpacing = (byte)System.Math.Clamp(computedLineSpacing, 0, byte.MaxValue); pNode->CharSpacing = 1; - pNode->TextFlags = config.LightfinderLabelUseIcon ? TextFlags.Edge | TextFlags.Glare | TextFlags.AutoAdjustNodeSize - : TextFlags.Edge | TextFlags.Glare; - - var labelContent = config.LightfinderLabelUseIcon - ? NormalizeIconGlyph(config.LightfinderLabelIconGlyph) - : DefaultLabelText; - - pNode->FontType = config.LightfinderLabelUseIcon ? FontType.Axis : FontType.MiedingerMed; - pNode->SetText(labelContent); + : TextFlags.Edge | TextFlags.Glare; } } @@ -503,6 +552,9 @@ public unsafe class NameplateHandler : IMediatorSubscriber var nameplateObject = GetNameplateObject(i); return nameplateObject != null ? nameplateObject.Value.RootComponentNode : null; } + private HashSet VisibleUserIds => [.. _pairManager.GetOnlineUserPairs() + .Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue) + .Select(u => (ulong)u.PlayerCharacterId)]; public void FlagRefresh() { @@ -534,4 +586,12 @@ public unsafe class NameplateHandler : IMediatorSubscriber FlagRefresh(); } + + public void ClearNameplateCaches() + { + 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); + } } diff --git a/LightlessSync/Services/NameplateService.cs b/LightlessSync/Services/NameplateService.cs index f441a60..8ccc362 100644 --- a/LightlessSync/Services/NameplateService.cs +++ b/LightlessSync/Services/NameplateService.cs @@ -35,7 +35,6 @@ public class NameplateService : DisposableMediatorSubscriberBase _namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate; _namePlateGui.RequestRedraw(); Mediator.Subscribe(this, (_) => _namePlateGui.RequestRedraw()); - } private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList handlers) diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 58187a6..84e4e94 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -1066,41 +1066,82 @@ public class SettingsUi : WindowMediatorSubscriberBase if (_uiShared.MediumTreeNode("Lightfinder", UIColors.Get("LightlessPurple"))) { + var autoAlign = _configService.Current.LightfinderAutoAlign; var offsetX = (int)_configService.Current.LightfinderLabelOffsetX; + var offsetY = (int)_configService.Current.LightfinderLabelOffsetY; + var labelScale = _configService.Current.LightfinderLabelScale; + + ImGui.TextUnformatted("Alignment"); + ImGui.BeginDisabled(autoAlign); if (ImGui.SliderInt("Label Offset X", ref offsetX, -200, 200)) { _configService.Current.LightfinderLabelOffsetX = (short)offsetX; _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } - _uiShared.DrawHelpText("Moves the Lightfinder label horizontally on player nameplates."); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.LightfinderLabelOffsetX = 0; + _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); + _nameplateHandler.FlagRefresh(); + _nameplateService.RequestRedraw(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default."); + ImGui.EndDisabled(); + _uiShared.DrawHelpText("Moves the Lightfinder label horizontally on player nameplates.\nUnavailable when automatic alignment is enabled."); + - var offsetY = (int)_configService.Current.LightfinderLabelOffsetY; if (ImGui.SliderInt("Label Offset Y", ref offsetY, -200, 200)) { _configService.Current.LightfinderLabelOffsetY = (short)offsetY; _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.LightfinderLabelOffsetY = 0; + _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); + _nameplateHandler.FlagRefresh(); + _nameplateService.RequestRedraw(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default."); _uiShared.DrawHelpText("Moves the Lightfinder label vertically on player nameplates."); - var labelScale = _configService.Current.LightfinderLabelScale; if (ImGui.SliderFloat("Label Size", ref labelScale, 0.5f, 2.0f, "%.2fx")) { _configService.Current.LightfinderLabelScale = labelScale; _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.LightfinderLabelScale = 1.0f; + _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); + _nameplateHandler.FlagRefresh(); + _nameplateService.RequestRedraw(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default."); _uiShared.DrawHelpText("Adjusts the Lightfinder label size for both text and icon modes."); - var autoAlign = _configService.Current.LightfinderAutoAlign; + ImGui.Dummy(new Vector2(8)); + if (ImGui.Checkbox("Automatically align with nameplate", ref autoAlign)) { _configService.Current.LightfinderAutoAlign = autoAlign; _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } @@ -1144,11 +1185,40 @@ public class SettingsUi : WindowMediatorSubscriberBase } + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + + ImGui.TextUnformatted("Visibility"); + var showOwn = _configService.Current.LightfinderLabelShowOwn; + if (ImGui.Checkbox("Show your own Lightfinder label", ref showOwn)) + { + _configService.Current.LightfinderLabelShowOwn = showOwn; + _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); + _nameplateHandler.FlagRefresh(); + _nameplateService.RequestRedraw(); + } + _uiShared.DrawHelpText("Toggles your own Lightfinder label."); + + var showPaired = _configService.Current.LightfinderLabelShowPaired; + if (ImGui.Checkbox("Show paired player(s) Lightfinder label", ref showPaired)) + { + _configService.Current.LightfinderLabelShowPaired = showPaired; + _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); + _nameplateHandler.FlagRefresh(); + _nameplateService.RequestRedraw(); + } + _uiShared.DrawHelpText("Toggles paired player(s) Lightfinder label."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + + ImGui.TextUnformatted("Label"); var useIcon = _configService.Current.LightfinderLabelUseIcon; if (ImGui.Checkbox("Show icon instead of text", ref useIcon)) { _configService.Current.LightfinderLabelUseIcon = useIcon; _configService.Save(); + _nameplateHandler.ClearNameplateCaches(); _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw();