Pushed Imgui plate handler for lightfinder. need to redo options of it.
This commit is contained in:
249
LightlessSync/Services/LightFinder/LightFinderPlateHandler.cs
Normal file
249
LightlessSync/Services/LightFinder/LightFinderPlateHandler.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.UI;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
|
||||
namespace LightlessSync.Services.LightFinder
|
||||
{
|
||||
public class LightFinderPlateHandler : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
private readonly ILogger<LightFinderPlateHandler> _logger;
|
||||
private readonly LightlessConfigService _configService;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly IObjectTable _gameObjects;
|
||||
private readonly IGameGui _gameGui;
|
||||
|
||||
private const float _defaultNameplateDistance = 15.0f;
|
||||
private ImmutableHashSet<string> _activeBroadcastingCids = [];
|
||||
private readonly Dictionary<IGameObject, Vector3> _smoothed = [];
|
||||
private readonly float _defaultHeightOffset = 0f;
|
||||
|
||||
public LightlessMediator Mediator { get; }
|
||||
|
||||
public LightFinderPlateHandler(
|
||||
ILogger<LightFinderPlateHandler> logger,
|
||||
LightlessMediator mediator,
|
||||
IDalamudPluginInterface dalamudPluginInterface,
|
||||
LightlessConfigService configService,
|
||||
IObjectTable gameObjects,
|
||||
IGameGui gameGui)
|
||||
{
|
||||
_logger = logger;
|
||||
Mediator = mediator;
|
||||
_pluginInterface = dalamudPluginInterface;
|
||||
_configService = configService;
|
||||
_gameObjects = gameObjects;
|
||||
_gameGui = gameGui;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Starting LightFinderPlateHandler...");
|
||||
|
||||
_pluginInterface.UiBuilder.Draw += OnDraw;
|
||||
|
||||
_logger.LogInformation("LightFinderPlateHandler started.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Stopping LightFinderPlateHandler...");
|
||||
|
||||
_pluginInterface.UiBuilder.Draw -= OnDraw;
|
||||
|
||||
_logger.LogInformation("LightFinderPlateHandler stopped.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private unsafe void OnDraw()
|
||||
{
|
||||
if (!_configService.Current.BroadcastEnabled)
|
||||
return;
|
||||
|
||||
if (_activeBroadcastingCids.Count == 0)
|
||||
return;
|
||||
|
||||
var drawList = ImGui.GetForegroundDrawList();
|
||||
|
||||
foreach (var obj in _gameObjects.PlayerObjects.OfType<IPlayerCharacter>())
|
||||
{
|
||||
//Double check to be sure, should always be true due to OfType filter above
|
||||
if (obj is not IPlayerCharacter player)
|
||||
continue;
|
||||
|
||||
if (player.Address == IntPtr.Zero)
|
||||
continue;
|
||||
|
||||
var hashedCID = DalamudUtilService.GetHashedCIDFromPlayerPointer(player.Address);
|
||||
if (!_activeBroadcastingCids.Contains(hashedCID))
|
||||
continue;
|
||||
|
||||
//Approximate check if nameplate should be visible (at short distances)
|
||||
if (!ShouldApproximateNameplateVisible(player))
|
||||
continue;
|
||||
|
||||
if (!TryGetApproxNameplateScreenPos(player, out var rawScreenPos))
|
||||
continue;
|
||||
|
||||
var rawVector3 = new Vector3(rawScreenPos.X, rawScreenPos.Y, 0f);
|
||||
|
||||
if (rawVector3 == Vector3.Zero)
|
||||
{
|
||||
_smoothed.Remove(obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Possible have to rework this. Currently just a simple distance check to avoid jitter.
|
||||
Vector3 smoothedVector3;
|
||||
|
||||
if (_smoothed.TryGetValue(obj, out var lastVector3))
|
||||
{
|
||||
var deltaVector2 = new Vector2(rawVector3.X - lastVector3.X, rawVector3.Y - lastVector3.Y);
|
||||
if (deltaVector2.Length() < 1f)
|
||||
smoothedVector3 = lastVector3;
|
||||
else
|
||||
smoothedVector3 = rawVector3;
|
||||
}
|
||||
else
|
||||
{
|
||||
smoothedVector3 = rawVector3;
|
||||
}
|
||||
|
||||
_smoothed[obj] = smoothedVector3;
|
||||
|
||||
var screenPos = new Vector2(smoothedVector3.X, smoothedVector3.Y);
|
||||
|
||||
var radiusWorld = Math.Max(player.HitboxRadius, 0.5f);
|
||||
var radiusPx = radiusWorld * 8.0f;
|
||||
var offsetPx = GetScreenOffset(player);
|
||||
var drawPos = new Vector2(screenPos.X, screenPos.Y - offsetPx);
|
||||
|
||||
var fillColor = ImGui.GetColorU32(UiSharedService.Color(UIColors.Get("Lightfinder")));
|
||||
var outlineColor = ImGui.GetColorU32(UiSharedService.Color(UIColors.Get("LightfinderEdge")));
|
||||
|
||||
drawList.AddCircleFilled(drawPos, radiusPx, fillColor);
|
||||
drawList.AddCircle(drawPos, radiusPx, outlineColor, 0, 2.0f);
|
||||
|
||||
var label = "LightFinder";
|
||||
var icon = FontAwesomeIcon.Bullseye.ToIconString();
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var iconSize = ImGui.CalcTextSize(icon);
|
||||
var iconPos = new Vector2(drawPos.X - iconSize.X / 2f, drawPos.Y - radiusPx - iconSize.Y - 2f);
|
||||
drawList.AddText(iconPos, fillColor, icon);
|
||||
ImGui.PopFont();
|
||||
|
||||
/* var scale = 1.4f;
|
||||
var font = ImGui.GetFont();
|
||||
var baseFontSize = ImGui.GetFontSize();
|
||||
var fontSize = baseFontSize * scale;
|
||||
|
||||
var baseTextSize = ImGui.CalcTextSize(label);
|
||||
var textSize = baseTextSize * scale;
|
||||
|
||||
var textPos = new Vector2(
|
||||
drawPos.X - textSize.X / 2f,
|
||||
drawPos.Y - radiusPx - textSize.Y - 2f
|
||||
);
|
||||
|
||||
drawList.AddText(font, fontSize, textPos, fillColor, label); */
|
||||
}
|
||||
}
|
||||
|
||||
// Get screen offset based on distance to local player (to scale size appropriately)
|
||||
// I need to fine tune these values still
|
||||
private float GetScreenOffset(IPlayerCharacter player)
|
||||
{
|
||||
var local = _gameObjects.LocalPlayer;
|
||||
if (local == null)
|
||||
return 32.1f;
|
||||
|
||||
var delta = player.Position - local.Position;
|
||||
var dist = MathF.Sqrt(delta.X * delta.X + delta.Z * delta.Z);
|
||||
|
||||
const float minDist = 2.1f;
|
||||
const float maxDist = 30.4f;
|
||||
dist = Math.Clamp(dist, minDist, maxDist);
|
||||
|
||||
var t = 1f - (dist - minDist) / (maxDist - minDist);
|
||||
|
||||
const float minOffset = 24.4f;
|
||||
const float maxOffset = 56.4f;
|
||||
return minOffset + (maxOffset - minOffset) * t;
|
||||
}
|
||||
|
||||
private bool TryGetApproxNameplateScreenPos(IPlayerCharacter player, out Vector2 screenPos)
|
||||
{
|
||||
screenPos = default;
|
||||
|
||||
var worldPos = player.Position;
|
||||
|
||||
var visualHeight = GetVisualHeight(player);
|
||||
|
||||
worldPos.Y += (visualHeight + 1.2f) + _defaultHeightOffset;
|
||||
|
||||
if (!_gameGui.WorldToScreen(worldPos, out var raw))
|
||||
return false;
|
||||
|
||||
screenPos = raw;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Approximate check to see if nameplate would be visible based on distance and screen position
|
||||
// Also has to be fine tuned still
|
||||
private bool ShouldApproximateNameplateVisible(IPlayerCharacter player)
|
||||
{
|
||||
var local = _gameObjects.LocalPlayer;
|
||||
if (local == null)
|
||||
return false;
|
||||
|
||||
var delta = player.Position - local.Position;
|
||||
var distance2D = MathF.Sqrt(delta.X * delta.X + delta.Z * delta.Z);
|
||||
if (distance2D > _defaultNameplateDistance)
|
||||
return false;
|
||||
|
||||
var verticalDelta = MathF.Abs(delta.Y);
|
||||
if (verticalDelta > 3.4f)
|
||||
return false;
|
||||
|
||||
return TryGetApproxNameplateScreenPos(player, out _);
|
||||
}
|
||||
|
||||
private static unsafe float GetVisualHeight(IPlayerCharacter player)
|
||||
{
|
||||
var gameObject = (GameObject*)player.Address;
|
||||
if (gameObject == null)
|
||||
return Math.Max(player.HitboxRadius * 2.0f, 1.7f); // fallback
|
||||
|
||||
// This should account for transformations (sitting, crouching, etc.)
|
||||
var radius = gameObject->GetRadius(adjustByTransformation: true);
|
||||
if (radius <= 0)
|
||||
radius = Math.Max(player.HitboxRadius * 2.0f, 1.7f);
|
||||
|
||||
return radius;
|
||||
}
|
||||
|
||||
// Update the set of active broadcasting CIDs (Same uses as in NameplateHnadler before)
|
||||
public void UpdateBroadcastingCids(IEnumerable<string> cids)
|
||||
{
|
||||
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
|
||||
if (ReferenceEquals(_activeBroadcastingCids, newSet) || _activeBroadcastingCids.SetEquals(newSet))
|
||||
return;
|
||||
|
||||
_activeBroadcastingCids = newSet;
|
||||
if (_logger.IsEnabled(LogLevel.Information))
|
||||
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class LightFinderScannerService : DisposableMediatorSubscriberBase
|
||||
private readonly IFramework _framework;
|
||||
|
||||
private readonly LightFinderService _broadcastService;
|
||||
private readonly NameplateHandler _nameplateHandler;
|
||||
private readonly LightFinderPlateHandler _lightFinderPlateHandler;
|
||||
|
||||
private readonly ConcurrentDictionary<string, BroadcastEntry> _broadcastCache = new(StringComparer.Ordinal);
|
||||
private readonly Queue<string> _lookupQueue = new();
|
||||
@@ -41,22 +41,21 @@ public class LightFinderScannerService : DisposableMediatorSubscriberBase
|
||||
IFramework framework,
|
||||
LightFinderService broadcastService,
|
||||
LightlessMediator mediator,
|
||||
NameplateHandler nameplateHandler,
|
||||
LightFinderPlateHandler lightFinderPlateHandler,
|
||||
ActorObjectService actorTracker) : base(logger, mediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_actorTracker = actorTracker;
|
||||
_broadcastService = broadcastService;
|
||||
_nameplateHandler = nameplateHandler;
|
||||
_lightFinderPlateHandler = lightFinderPlateHandler;
|
||||
|
||||
_logger = logger;
|
||||
_framework = framework;
|
||||
_framework.Update += OnFrameworkUpdate;
|
||||
|
||||
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, OnBroadcastStatusChanged);
|
||||
_cleanupTask = Task.Run(ExpiredBroadcastCleanupLoop);
|
||||
_cleanupTask = Task.Run(ExpiredBroadcastCleanupLoop, _cleanupCts.Token);
|
||||
|
||||
_nameplateHandler.Init();
|
||||
_actorTracker = actorTracker;
|
||||
}
|
||||
|
||||
@@ -129,7 +128,7 @@ public class LightFinderScannerService : DisposableMediatorSubscriberBase
|
||||
.Select(e => e.Key)
|
||||
.ToList();
|
||||
|
||||
_nameplateHandler.UpdateBroadcastingCids(activeCids);
|
||||
_lightFinderPlateHandler.UpdateBroadcastingCids(activeCids);
|
||||
UpdateSyncshellBroadcasts();
|
||||
}
|
||||
|
||||
@@ -142,7 +141,7 @@ public class LightFinderScannerService : DisposableMediatorSubscriberBase
|
||||
_lookupQueuedCids.Clear();
|
||||
_syncshellCids.Clear();
|
||||
|
||||
_nameplateHandler.UpdateBroadcastingCids(Enumerable.Empty<string>());
|
||||
_lightFinderPlateHandler.UpdateBroadcastingCids([]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +242,5 @@ public class LightFinderScannerService : DisposableMediatorSubscriberBase
|
||||
|
||||
_cleanupTask?.Wait(100);
|
||||
_cleanupCts.Dispose();
|
||||
_nameplateHandler.Uninit();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user