using Dalamud.Logging; using MareSynchronos.Factories; using MareSynchronos.Models; using MareSynchronos.Utils; using MareSynchronos.WebAPI; using Newtonsoft.Json; using System; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MareSynchronos.Managers { public class PlayerManager : IDisposable { private readonly ApiController _apiController; private readonly CachedPlayersManager _cachedPlayersManager; private readonly CharacterDataFactory _characterDataFactory; private readonly DalamudUtil _dalamudUtil; private readonly IpcManager _ipcManager; private string _lastSentHash = string.Empty; private CancellationTokenSource? _playerChangedCts; public PlayerManager(ApiController apiController, IpcManager ipcManager, CharacterDataFactory characterDataFactory, CachedPlayersManager cachedPlayersManager, DalamudUtil dalamudUtil) { Logger.Debug("Creating " + nameof(PlayerManager)); _apiController = apiController; _ipcManager = ipcManager; _characterDataFactory = characterDataFactory; _cachedPlayersManager = cachedPlayersManager; _dalamudUtil = dalamudUtil; _dalamudUtil.AddPlayerToWatch(_dalamudUtil.PlayerName); _apiController.Connected += ApiController_Connected; _apiController.Disconnected += ApiController_Disconnected; Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected); if (_apiController.IsConnected) { ApiController_Connected(null, EventArgs.Empty); } } public void Dispose() { Logger.Debug("Disposing " + nameof(PlayerManager)); _dalamudUtil.RemovePlayerFromWatch(_dalamudUtil.PlayerName); _apiController.Connected -= ApiController_Connected; _apiController.Disconnected -= ApiController_Disconnected; _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _dalamudUtil.PlayerChanged -= Watcher_PlayerChanged; } private void ApiController_Connected(object? sender, EventArgs args) { Logger.Debug("ApiController Connected"); var apiTask = _apiController.GetOnlineCharacters(); _lastSentHash = string.Empty; Task.WaitAll(apiTask); _cachedPlayersManager.AddInitialPairs(apiTask.Result); _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; _dalamudUtil.PlayerChanged += Watcher_PlayerChanged; PlayerChanged(_dalamudUtil.PlayerName); } private void ApiController_Disconnected(object? sender, EventArgs args) { Logger.Debug(nameof(ApiController_Disconnected)); _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _dalamudUtil.PlayerChanged -= Watcher_PlayerChanged; } private async Task CreateFullCharacterCache(CancellationToken token) { var cache = _characterDataFactory.BuildCharacterData(); await Task.Run(async () => { while (!cache.IsReady && !token.IsCancellationRequested) { await Task.Delay(50, token); } if (token.IsCancellationRequested) return; var json = JsonConvert.SerializeObject(cache, Formatting.Indented); cache.CacheHash = Crypto.GetHash(json); }, token); return cache; } private void IpcManager_PenumbraRedrawEvent(object? objectTableIndex, EventArgs e) { var player = _dalamudUtil.GetPlayerCharacterFromObjectTableIndex((int)objectTableIndex!); if (player != null && player.Name.ToString() != _dalamudUtil.PlayerName) return; Logger.Debug("Penumbra Redraw Event for " + _dalamudUtil.PlayerName); PlayerChanged(_dalamudUtil.PlayerName); } private void PlayerChanged(string name) { Logger.Debug("Player changed: " + name); _playerChangedCts?.Cancel(); _playerChangedCts = new CancellationTokenSource(); var token = _playerChangedCts.Token; if (!_ipcManager.Initialized) { Logger.Warn("Penumbra not active, doing nothing."); return; } Task.Run(async () => { int attempts = 0; while (!_apiController.IsConnected && attempts < 10 && !token.IsCancellationRequested) { Logger.Warn("No connection to the API"); await Task.Delay(TimeSpan.FromSeconds(1), token); attempts++; } if (attempts == 10 || token.IsCancellationRequested) return; Stopwatch st = Stopwatch.StartNew(); _dalamudUtil.WaitWhileSelfIsDrawing(token); var characterCacheTask = await CreateFullCharacterCache(token); if (token.IsCancellationRequested) return; var cacheDto = characterCacheTask.ToCharacterCacheDto(); st.Stop(); if (token.IsCancellationRequested) { return; } Logger.Debug("Elapsed time PlayerChangedTask: " + st.Elapsed); if (cacheDto.Hash == _lastSentHash) { Logger.Debug("Not sending data, already sent"); return; } _ = _apiController.SendCharacterData(cacheDto, _dalamudUtil.GetLocalPlayers().Select(d => d.Key).ToList()); _lastSentHash = cacheDto.Hash; }, token); } private void Watcher_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor) { Task.Run(() => { // fix for redraw from anamnesis while (!_dalamudUtil.IsPlayerPresent) { Logger.Debug("Waiting Until Player is Present"); Thread.Sleep(100); } if (actor.Name.ToString() == _dalamudUtil.PlayerName) { Logger.Debug("Watcher: PlayerChanged"); PlayerChanged(actor.Name.ToString()); } }); } } }