using MareSynchronos.Interop.Ipc; using MareSynchronos.Services.Mediator; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; namespace MareSynchronos.Services; // Detect when players of interest are visible public class VisibilityService : DisposableMediatorSubscriberBase { private enum TrackedPlayerStatus { NotVisible, Visible, OtherSyncHandled }; private readonly DalamudUtilService _dalamudUtil; private readonly ConcurrentDictionary _trackedPlayerVisibility = new(StringComparer.Ordinal); private readonly List _makeVisibleNextFrame = new(); private readonly IpcCallerOtherSync _otherSync; private readonly HashSet cachedOtherSyncAddresses = new(); private uint _cachedAddressSum = 0; private uint _cachedAddressSumDebounce = 1; public VisibilityService(ILogger logger, MareMediator mediator, IpcCallerOtherSync otherSync, DalamudUtilService dalamudUtil) : base(logger, mediator) { _otherSync = otherSync; _dalamudUtil = dalamudUtil; Mediator.Subscribe(this, (_) => FrameworkUpdate()); } public void StartTracking(string ident) { _trackedPlayerVisibility.TryAdd(ident, TrackedPlayerStatus.NotVisible); } public void StopTracking(string ident) { // No PairVisibilityMessage is emitted if the player was visible when removed _trackedPlayerVisibility.TryRemove(ident, out _); } private void FrameworkUpdate() { var otherSyncHandledAddresses = _otherSync.GetHandledGameAddresses(); uint addressSum = 0; foreach (var addr in otherSyncHandledAddresses) addressSum ^= (uint)addr.GetHashCode(); if (addressSum != _cachedAddressSum) { if (addressSum == _cachedAddressSumDebounce) { cachedOtherSyncAddresses.Clear(); foreach (var addr in otherSyncHandledAddresses) cachedOtherSyncAddresses.Add(addr); _cachedAddressSum = addressSum; } else { _cachedAddressSumDebounce = addressSum; } } foreach (var player in _trackedPlayerVisibility) { string ident = player.Key; var findResult = _dalamudUtil.FindPlayerByNameHash(ident); var isOtherSyncHandled = cachedOtherSyncAddresses.Contains(findResult.Address); var isVisible = findResult.ObjectId != 0 && !isOtherSyncHandled; if (player.Value == TrackedPlayerStatus.OtherSyncHandled && !isOtherSyncHandled) _trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.NotVisible, comparisonValue: TrackedPlayerStatus.OtherSyncHandled); if (player.Value == TrackedPlayerStatus.NotVisible && isVisible) { if (_makeVisibleNextFrame.Contains(ident, StringComparer.Ordinal)) { if (_trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.Visible, comparisonValue: TrackedPlayerStatus.NotVisible)) Mediator.Publish(new(ident, IsVisible: true)); } else _makeVisibleNextFrame.Add(ident); } else if (player.Value == TrackedPlayerStatus.NotVisible && isOtherSyncHandled) { // Send a technically redundant visibility update with the added intent of triggering PairHandler to undo the application by name if (_trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.OtherSyncHandled, comparisonValue: TrackedPlayerStatus.NotVisible)) Mediator.Publish(new(ident, IsVisible: false, Invalidate: true)); } else if (player.Value == TrackedPlayerStatus.Visible && !isVisible) { var newTrackedStatus = isOtherSyncHandled ? TrackedPlayerStatus.OtherSyncHandled : TrackedPlayerStatus.NotVisible; if (_trackedPlayerVisibility.TryUpdate(ident, newValue: newTrackedStatus, comparisonValue: TrackedPlayerStatus.Visible)) Mediator.Publish(new(ident, IsVisible: false, Invalidate: isOtherSyncHandled)); } if (!isVisible) _makeVisibleNextFrame.Remove(ident); } } }