diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 6032c59..417290c 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -78,7 +78,7 @@ namespace MareSynchronos.Factories var cache = new CharacterData { JobId = _dalamudUtil.PlayerJobId, - GlamourerString = _ipcManager.GlamourerGetCharacterCustomization(_dalamudUtil.PlayerName), + GlamourerString = _ipcManager.GlamourerGetCharacterCustomization(_dalamudUtil.PlayerCharacter), ManipulationString = _ipcManager.PenumbraGetMetaManipulations(_dalamudUtil.PlayerName) }; var model = (CharacterBase*)((Character*)_dalamudUtil.PlayerPointer)->GameObject.GetDrawObject(); diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 7c9a382..0463ab9 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -60,6 +60,26 @@ public class CachedPlayer private CharacterEquipment? _currentCharacterEquipment; + public void ApplyCharacterData(CharacterCacheDto characterData) + { + Logger.Debug("Received data for " + this); + + Logger.Debug("Checking for files to download for player " + PlayerName); + Logger.Debug("Hash for data is " + characterData.Hash); + if (!_cache.ContainsKey(characterData.Hash)) + { + Logger.Debug("Received total " + characterData.FileReplacements.Count + " file replacement data"); + _cache[characterData.Hash] = characterData; + } + else + { + Logger.Debug("Had valid local cache for " + PlayerName); + } + _lastAppliedEquipmentHash = characterData.Hash; + + DownloadAndApplyCharacter(); + } + private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e) { if (string.IsNullOrEmpty(PlayerName) || e.CharacterNameHash != PlayerNameHash) return; @@ -147,7 +167,7 @@ public class CachedPlayer Logger.Debug( $"Request Redraw for {PlayerName}"); _ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, cache.ManipulationData); - _ipcManager.GlamourerApplyAll(cache.GlamourerData, PlayerName!); + _ipcManager.GlamourerApplyAll(cache.GlamourerData, PlayerCharacter!); } public void DisposePlayer() @@ -166,8 +186,8 @@ public class CachedPlayer _ipcManager.PenumbraRemoveTemporaryCollection(PlayerName); if (PlayerCharacter != null) { - _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerName); - _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerName); + _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter); + _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter); } } catch (Exception ex) @@ -182,7 +202,7 @@ public class CachedPlayer } } - public void InitializePlayer(PlayerCharacter character) + public void InitializePlayer(PlayerCharacter character, CharacterCacheDto? cache) { IsVisible = true; PlayerName = character.Name.ToString(); @@ -190,10 +210,12 @@ public class CachedPlayer Logger.Debug("Initializing Player " + this); _dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate; _ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent; - _apiController.CharacterReceived += ApiControllerOnCharacterReceived; - _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerName); + _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter); _currentCharacterEquipment = new CharacterEquipment(PlayerCharacter); - _lastPlayerObjectCheck = DateTime.Now; + if (cache != null) + { + ApplyCharacterData(cache); + } } private void DalamudUtilOnFrameworkUpdate() @@ -250,11 +272,10 @@ public class CachedPlayer private void OnPlayerChanged() { Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}"); - PlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!); if (!RequestedPenumbraRedraw && PlayerCharacter is not null) { Logger.Debug($"Saving new Glamourer data"); - _lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerName!); + _lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter!); } } } \ No newline at end of file diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 41b3e95..fb8e9c8 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Ipc; using System; using System.Collections.Generic; using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; using MareSynchronos.Utils; namespace MareSynchronos.Managers @@ -11,11 +12,11 @@ namespace MareSynchronos.Managers public class IpcManager : IDisposable { private readonly ICallGateSubscriber _glamourerApiVersion; - private readonly ICallGateSubscriber? _glamourerApplyAll; - private readonly ICallGateSubscriber? _glamourerGetAllCustomization; - private readonly ICallGateSubscriber _glamourerRevertCustomization; - private readonly ICallGateSubscriber? _glamourerApplyOnlyEquipment; - private readonly ICallGateSubscriber? _glamourerApplyOnlyCustomization; + private readonly ICallGateSubscriber? _glamourerApplyAll; + private readonly ICallGateSubscriber? _glamourerGetAllCustomization; + private readonly ICallGateSubscriber _glamourerRevertCustomization; + private readonly ICallGateSubscriber? _glamourerApplyOnlyEquipment; + private readonly ICallGateSubscriber? _glamourerApplyOnlyCustomization; private readonly ICallGateSubscriber _penumbraApiVersion; private readonly ICallGateSubscriber _penumbraCreateTemporaryCollection; private readonly ICallGateSubscriber _penumbraGetMetaManipulations; @@ -45,11 +46,11 @@ namespace MareSynchronos.Managers pi.GetIpcSubscriber("Penumbra.GetMetaManipulations"); _glamourerApiVersion = pi.GetIpcSubscriber("Glamourer.ApiVersion"); - _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomization"); - _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAll"); - _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomization"); - _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipment"); - _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.Revert"); + _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomizationFromCharacter"); + _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacter"); + _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); + _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); + _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); _penumbraInit.Subscribe(PenumbraInit); @@ -110,37 +111,37 @@ namespace MareSynchronos.Managers Logger.Debug("IPC Manager disposed"); } - public void GlamourerApplyAll(string customization, string characterName) + public void GlamourerApplyAll(string customization, GameObject character) { if (!CheckGlamourerApi()) return; - Logger.Debug("Glamourer apply all to " + characterName); - _glamourerApplyAll!.InvokeAction(customization, characterName); + Logger.Debug("Glamourer apply all to " + character); + _glamourerApplyAll!.InvokeAction(customization, character); } - public void GlamourerApplyOnlyEquipment(string customization, string characterName) + public void GlamourerApplyOnlyEquipment(string customization, GameObject character) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - Logger.Debug("Glamourer apply only equipment to " + characterName); - _glamourerApplyOnlyEquipment!.InvokeAction(customization, characterName); + Logger.Debug("Glamourer apply only equipment to " + character); + _glamourerApplyOnlyEquipment!.InvokeAction(customization, character); } - public void GlamourerApplyOnlyCustomization(string customization, string characterName) + public void GlamourerApplyOnlyCustomization(string customization, GameObject character) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - Logger.Debug("Glamourer apply only customization to " + characterName); - _glamourerApplyOnlyCustomization!.InvokeAction(customization, characterName); + Logger.Debug("Glamourer apply only customization to " + character); + _glamourerApplyOnlyCustomization!.InvokeAction(customization, character); } - public string GlamourerGetCharacterCustomization(string characterName) + public string GlamourerGetCharacterCustomization(GameObject character) { if (!CheckGlamourerApi()) return string.Empty; - return _glamourerGetAllCustomization!.InvokeFunc(characterName); + return _glamourerGetAllCustomization!.InvokeFunc(character); } - public void GlamourerRevertCharacterCustomization(string characterName) + public void GlamourerRevertCharacterCustomization(GameObject character) { if (!CheckGlamourerApi()) return; - _glamourerRevertCustomization!.InvokeAction(characterName); + _glamourerRevertCustomization!.InvokeAction(character); } public string PenumbraCreateTemporaryCollection(string characterName) diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index 028ec8f..333b011 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Dalamud.Game; +using MareSynchronos.API; using MareSynchronos.Utils; using MareSynchronos.WebAPI; +using MareSynchronos.WebAPI.Utils; namespace MareSynchronos.Managers; @@ -14,10 +16,15 @@ public class OnlinePlayerManager : IDisposable private readonly DalamudUtil _dalamudUtil; private readonly Framework _framework; private readonly IpcManager _ipcManager; + private readonly PlayerManager _playerManager; private readonly List _onlineCachedPlayers = new(); + private readonly Dictionary _temporaryStoredCharacterCache = new(); + + private List OnlineVisiblePlayerHashes => _onlineCachedPlayers.Where(p => p.PlayerCharacter != null) + .Select(p => p.PlayerNameHash).ToList(); private DateTime _lastPlayerObjectCheck = DateTime.Now; - public OnlinePlayerManager(Framework framework, ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager) + public OnlinePlayerManager(Framework framework, ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager, PlayerManager playerManager) { Logger.Debug("Creating " + nameof(OnlinePlayerManager)); @@ -25,12 +32,15 @@ public class OnlinePlayerManager : IDisposable _apiController = apiController; _dalamudUtil = dalamudUtil; _ipcManager = ipcManager; + _playerManager = playerManager; _apiController.PairedClientOnline += ApiControllerOnPairedClientOnline; _apiController.PairedClientOffline += ApiControllerOnPairedClientOffline; _apiController.PairedWithOther += ApiControllerOnPairedWithOther; _apiController.UnpairedFromOther += ApiControllerOnUnpairedFromOther; + _apiController.Connected += ApiControllerOnConnected; _apiController.Disconnected += ApiControllerOnDisconnected; + _apiController.CharacterReceived += ApiControllerOnCharacterReceived; _ipcManager.PenumbraDisposed += IpcManagerOnPenumbraDisposed; @@ -43,6 +53,37 @@ public class OnlinePlayerManager : IDisposable } } + private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e) + { + var visiblePlayer = _onlineCachedPlayers.SingleOrDefault(c => c.IsVisible && c.PlayerNameHash == e.CharacterNameHash); + if (visiblePlayer != null) + { + Logger.Debug("Received data and applying to " + e.CharacterNameHash); + visiblePlayer.ApplyCharacterData(e.CharacterData); + } + else + { + Logger.Debug("Received data but no fitting character visible for " + e.CharacterNameHash); + _temporaryStoredCharacterCache[e.CharacterNameHash] = e.CharacterData; + } + } + + private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache) + { + _ = _apiController.PushCharacterData(characterCache, OnlineVisiblePlayerHashes); + } + + private void ApiControllerOnConnected(object? sender, EventArgs e) + { + var apiTask = _apiController.GetOnlineCharacters(); + + Task.WaitAll(apiTask); + + AddInitialPairs(apiTask.Result); + + _playerManager.PlayerHasChanged += PlayerManagerOnPlayerHasChanged; + } + private void DalamudUtilOnLogOut() { _framework.Update -= FrameworkOnUpdate; @@ -61,6 +102,7 @@ public class OnlinePlayerManager : IDisposable private void ApiControllerOnDisconnected(object? sender, EventArgs e) { RestoreAllCharacters(); + _playerManager.PlayerHasChanged -= PlayerManagerOnPlayerHasChanged; } public void AddInitialPairs(List apiTaskResult) @@ -163,12 +205,24 @@ public class OnlinePlayerManager : IDisposable continue; } - _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pChar); + if (_temporaryStoredCharacterCache.TryGetValue(hashedName, out var cache)) + { + _temporaryStoredCharacterCache.Remove(hashedName); + } + _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pChar, cache); } - Task.Run(async () => await UpdatePlayersFromService(_onlineCachedPlayers - .Where(p => p.PlayerCharacter != null && p.IsVisible && !p.WasVisible) - .ToDictionary(k => k.PlayerNameHash, k => (int)k.PlayerCharacter!.ClassJob.Id))); + var newlyVisiblePlayers = _onlineCachedPlayers + .Where(p => p.PlayerCharacter != null && p.IsVisible && !p.WasVisible).Select(p => p.PlayerNameHash) + .ToList(); + if (newlyVisiblePlayers.Any() && _playerManager.LastSentCharacterData != null) + { + Task.Run(async () => + { + await _apiController.PushCharacterData(_playerManager.LastSentCharacterData.ToCharacterCacheDto(), + newlyVisiblePlayers); + }); + } _lastPlayerObjectCheck = DateTime.Now; } diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index 62d27e4..41aab69 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -1,40 +1,41 @@ -using Dalamud.Logging; -using MareSynchronos.Factories; +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; -using Dalamud.Game.ClientState.Objects; +using MareSynchronos.API; using Penumbra.GameData.Structs; namespace MareSynchronos.Managers { + public delegate void PlayerHasChanged(CharacterCacheDto characterCache); + public class PlayerManager : IDisposable { private readonly ApiController _apiController; - private readonly OnlinePlayerManager _onlinePlayerManager; private readonly CharacterDataFactory _characterDataFactory; private readonly DalamudUtil _dalamudUtil; private readonly IpcManager _ipcManager; - private string _lastSentHash = string.Empty; + public event PlayerHasChanged? PlayerHasChanged; + public bool SendingData { get; private set; } + public CharacterData? LastSentCharacterData { get; private set; } + private CancellationTokenSource? _playerChangedCts; private DateTime _lastPlayerObjectCheck; - private CharacterEquipment _currentCharacterEquipment; + private CharacterEquipment? _currentCharacterEquipment; public PlayerManager(ApiController apiController, IpcManager ipcManager, - CharacterDataFactory characterDataFactory, OnlinePlayerManager onlinePlayerManager, DalamudUtil dalamudUtil) + CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil) { Logger.Debug("Creating " + nameof(PlayerManager)); _apiController = apiController; _ipcManager = ipcManager; _characterDataFactory = characterDataFactory; - _onlinePlayerManager = onlinePlayerManager; _dalamudUtil = dalamudUtil; _apiController.Connected += ApiController_Connected; @@ -75,12 +76,6 @@ namespace MareSynchronos.Managers private void ApiController_Connected(object? sender, EventArgs args) { Logger.Debug("ApiController Connected"); - var apiTask = _apiController.GetOnlineCharacters(); - _lastSentHash = string.Empty; - - Task.WaitAll(apiTask); - - _onlinePlayerManager.AddInitialPairs(apiTask.Result); _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; _dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate; @@ -154,6 +149,7 @@ namespace MareSynchronos.Managers Task.Run(async () => { + SendingData = true; int attempts = 0; while (!_apiController.IsConnected && attempts < 10 && !token.IsCancellationRequested) { @@ -167,11 +163,11 @@ namespace MareSynchronos.Managers Stopwatch st = Stopwatch.StartNew(); _dalamudUtil.WaitWhileSelfIsDrawing(token); - var characterCacheTask = await CreateFullCharacterCache(token); + var characterCache = await CreateFullCharacterCache(token); if (token.IsCancellationRequested) return; - var cacheDto = characterCacheTask.ToCharacterCacheDto(); + var cacheDto = characterCache.ToCharacterCacheDto(); st.Stop(); if (token.IsCancellationRequested) @@ -180,13 +176,16 @@ namespace MareSynchronos.Managers } Logger.Debug("Elapsed time PlayerChangedTask: " + st.Elapsed); - if (cacheDto.Hash == _lastSentHash) + if (cacheDto.Hash == (LastSentCharacterData?.CacheHash ?? "-")) { Logger.Debug("Not sending data, already sent"); return; } - _ = _apiController.SendCharacterData(cacheDto, _dalamudUtil.GetLocalPlayers().Select(d => d.Key).ToList()); - _lastSentHash = cacheDto.Hash; + + LastSentCharacterData = characterCache; + PlayerHasChanged?.Invoke(cacheDto); + SendingData = false; + }, token); } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index f15ddb5..fae0d1f 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -168,10 +168,10 @@ namespace MareSynchronos { var characterCacheFactory = new CharacterDataFactory(_dalamudUtil, _ipcManager); - _characterCacheManager = new OnlinePlayerManager(_framework, - _apiController, _dalamudUtil, _ipcManager); _playerManager = new PlayerManager(_apiController, _ipcManager, - characterCacheFactory, _characterCacheManager, _dalamudUtil); + characterCacheFactory, _dalamudUtil); + _characterCacheManager = new OnlinePlayerManager(_framework, + _apiController, _dalamudUtil, _ipcManager, _playerManager); } catch (Exception ex) { diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 6886ec1..1e4db58 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -56,23 +56,6 @@ namespace MareSynchronos.WebAPI foreach (var file in fileReplacementDto) { downloadFiles.Add(await _fileHub!.InvokeAsync("GetFileSize", file.Hash, ct)); - /*var downloadFileDto = ; - var downloadFileTransfer = new DownloadFileTransfer(downloadFileDto); - if (CurrentDownloads.Any(f => f.Hash == downloadFileTransfer.Hash)) - { - if (fileTransferList.All(f => f.Hash != downloadFileTransfer.Hash)) - { - fileTransferList.Add(downloadFileTransfer); - } - - continue; - } - - if (fileTransferList.All(f => f.Hash != downloadFileTransfer.Hash)) - { - fileTransferList.Add(downloadFileTransfer); - } - CurrentDownloads.Add(new DownloadFileTransfer(downloadFileDto));*/ } downloadFiles = downloadFiles.Distinct().ToList(); @@ -140,7 +123,7 @@ namespace MareSynchronos.WebAPI CurrentDownloads.RemoveAll(d => d.Transferred == d.Total); } - public async Task SendCharacterData(CharacterCacheDto character, List visibleCharacterIds) + public async Task PushCharacterData(CharacterCacheDto character, List visibleCharacterIds) { if (!IsConnected || SecretKey == "-") return; Logger.Debug("Sending Character data to service " + ApiUri); @@ -155,10 +138,19 @@ namespace MareSynchronos.WebAPI foreach (var file in filesToUpload.Where(f => !f.IsForbidden)) { await using var db = new FileCacheContext(); - CurrentUploads.Add(new UploadFileTransfer(file) + try { - Total = new FileInfo(db.FileCaches.First(f => f.Hash == file.Hash).Filepath).Length - }); + CurrentUploads.Add(new UploadFileTransfer(file) + { + Total = new FileInfo(db.FileCaches.FirstOrDefault(f => f.Hash.ToLower() == file.Hash.ToLower()) + ?.Filepath ?? string.Empty).Length + }); + } + catch (Exception ex) + { + Logger.Warn("Tried to request file " + file.Hash + " but file was not present"); + Logger.Warn(ex.StackTrace!); + } } foreach (var file in CurrentUploads.Where(c => c.IsForbidden)) @@ -197,7 +189,7 @@ namespace MareSynchronos.WebAPI if (!uploadToken.IsCancellationRequested) { Logger.Verbose("=== Pushing character data ==="); - await _userHub!.InvokeAsync("PushCharacterData", character, visibleCharacterIds, uploadToken); + await _userHub!.InvokeAsync("PushCharacterDataToVisibleClients", character, visibleCharacterIds, uploadToken); } else {