diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index 5826c62..5f9e8b7 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -62,7 +62,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase private readonly ActionSubscriber _penumbraRedrawObject; private readonly ConcurrentDictionary _penumbraRedrawRequests = new(); private readonly FuncSubscriber _penumbraRemoveTemporaryCollection; - private readonly FuncSubscriber _penumbraRemoveTemporaryMod; private readonly FuncSubscriber _penumbraResolveModDir; private readonly FuncSubscriber _penumbraResolvePaths; private readonly SemaphoreSlim _redrawSemaphore = new(2); @@ -92,7 +91,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase _penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi); _penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi); _penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi); - _penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi); _penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi); _penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi); _penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi); @@ -409,6 +407,31 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase }).ConfigureAwait(false); } + public async Task PenumbraAssignTemporaryCollectionAsync(ILogger logger, string collName, int idx) + { + if (!CheckPenumbraApi()) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, c: true); + logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign); + return collName; + }).ConfigureAwait(false); + } + + public async Task PenumbraCreateTemporaryCollectionAsync(ILogger logger, string uid) + { + if (!CheckPenumbraApi()) return string.Empty; + + return await _dalamudUtil.RunOnFrameworkThread(() => + { + var collName = "Mare_" + uid; + var retCreate = _penumbraCreateNamedTemporaryCollection.Invoke(collName); + logger.LogTrace("Creating Temp Collection {collName}, Success: {ret}", collName, retCreate); + return collName; + }).ConfigureAwait(false); + } + public string PenumbraGetMetaManipulations() { if (!CheckPenumbraApi()) return string.Empty; @@ -449,6 +472,18 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase return await _dalamudUtil.RunOnFrameworkThread(() => _penumbraResolvePaths.Invoke(forward, reverse)).ConfigureAwait(false); } + public async Task PenumbraSetManipulationDataAsync(ILogger logger, Guid applicationId, string collName, string manipulationData) + { + if (!CheckPenumbraApi()) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); + var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collName, new Dictionary(), manipulationData, 0); + logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd); + }).ConfigureAwait(false); + } + public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, string collName, Dictionary modPaths) { if (!CheckPenumbraApi()) return; @@ -459,54 +494,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase { logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value); } - var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Files", collName, 0); - logger.LogTrace("[{applicationId}] Removing prev mod files mod for {collName}, Success: {ret}", applicationId, collName, retRemove); var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collName, modPaths, string.Empty, 0); logger.LogTrace("[{applicationId}] Setting temp files mod for {collName}, Success: {ret}", applicationId, collName, retAdd); }).ConfigureAwait(false); } - public async Task PenumbraSetManipulationDataAsync(ILogger logger, Guid applicationId, string collName, string manipulationData) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); - var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Meta", collName, 0); - logger.LogTrace("[{applicationId}] Removing prev meta mod for {collName}, Success: {ret}", applicationId, collName, retRemove); - var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collName, new Dictionary(), manipulationData, 0); - logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd); - }).ConfigureAwait(false); - } - - public async Task PenumbraCreateTemporaryCollection(ILogger logger, string uid) - { - if (!CheckPenumbraApi()) return string.Empty; - - return await _dalamudUtil.RunOnFrameworkThread(() => - { - var collName = "Mare_" + uid; - var retRemove = _penumbraRemoveTemporaryCollection.Invoke(collName); - logger.LogTrace("Removing Temp Collection {collName}, Success: {ret}", collName, retRemove); - var retCreate = _penumbraCreateNamedTemporaryCollection.Invoke(collName); - logger.LogTrace("Creating Temp Collection {collName}, Success: {ret}", collName, retCreate); - return collName; - }).ConfigureAwait(false); - } - - public async Task PenumbraAssignTemporaryCollection(ILogger logger, string collName, int idx) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, c: true); - logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign); - return collName; - }).ConfigureAwait(false); - } - public void ToggleGposeQueueMode(bool on) { _inGposeQueueMode = on; diff --git a/MareSynchronos/PlayerData/Data/PlayerChanges.cs b/MareSynchronos/PlayerData/Data/PlayerChanges.cs new file mode 100644 index 0000000..6c7c9d4 --- /dev/null +++ b/MareSynchronos/PlayerData/Data/PlayerChanges.cs @@ -0,0 +1,12 @@ +namespace MareSynchronos.PlayerData.Pairs; + +public enum PlayerChanges +{ + Heels = 1, + Customize = 2, + Palette = 3, + Honorific = 4, + ModFiles = 5, + ModManip = 6, + Glamourer = 7 +} diff --git a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs index 02087db..9a34192 100644 --- a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs +++ b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs @@ -66,8 +66,8 @@ public class MareCharaFileManager var applicationId = Guid.NewGuid(); _ipcManager.ToggleGposeQueueMode(on: true); await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false); - var coll = await _ipcManager.PenumbraCreateTemporaryCollection(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); - await _ipcManager.PenumbraAssignTemporaryCollection(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); + var coll = await _ipcManager.PenumbraCreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); + await _ipcManager.PenumbraAssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false); diff --git a/MareSynchronos/PlayerData/Factories/PairFactory.cs b/MareSynchronos/PlayerData/Factories/PairFactory.cs index 200917d..faf1161 100644 --- a/MareSynchronos/PlayerData/Factories/PairFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairFactory.cs @@ -7,12 +7,12 @@ namespace MareSynchronos.PlayerData.Factories; public class PairFactory { - private readonly CachedPlayerFactory _cachedPlayerFactory; + private readonly PairHandlerFactory _cachedPlayerFactory; private readonly ILoggerFactory _loggerFactory; private readonly MareMediator _mareMediator; private readonly ServerConfigurationManager _serverConfigurationManager; - public PairFactory(ILoggerFactory loggerFactory, CachedPlayerFactory cachedPlayerFactory, + public PairFactory(ILoggerFactory loggerFactory, PairHandlerFactory cachedPlayerFactory, MareMediator mareMediator, ServerConfigurationManager serverConfigurationManager) { _loggerFactory = loggerFactory; diff --git a/MareSynchronos/PlayerData/Factories/CachedPlayerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs similarity index 58% rename from MareSynchronos/PlayerData/Factories/CachedPlayerFactory.cs rename to MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index d41ccd5..f9ee5f6 100644 --- a/MareSynchronos/PlayerData/Factories/CachedPlayerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -1,7 +1,7 @@ using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Interop; -using MareSynchronos.MareConfiguration; +using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace MareSynchronos.PlayerData.Factories; -public class CachedPlayerFactory +public class PairHandlerFactory { private readonly DalamudUtilService _dalamudUtilService; private readonly FileCacheManager _fileCacheManager; @@ -19,28 +19,29 @@ public class CachedPlayerFactory private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly IpcManager _ipcManager; private readonly ILoggerFactory _loggerFactory; - private readonly MareConfigService _mareConfigService; private readonly MareMediator _mareMediator; + private readonly PluginWarningNotificationService _pluginWarningNotificationManager; - public CachedPlayerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, - FileDownloadManagerFactory fileDownloadManagerFactory, MareConfigService mareConfigService, DalamudUtilService dalamudUtilService, - IHostApplicationLifetime hostApplicationLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator) + public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, + FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, + PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, + FileCacheManager fileCacheManager, MareMediator mareMediator) { _loggerFactory = loggerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory; _ipcManager = ipcManager; _fileDownloadManagerFactory = fileDownloadManagerFactory; - _mareConfigService = mareConfigService; _dalamudUtilService = dalamudUtilService; + _pluginWarningNotificationManager = pluginWarningNotificationManager; _hostApplicationLifetime = hostApplicationLifetime; _fileCacheManager = fileCacheManager; _mareMediator = mareMediator; } - public CachedPlayer Create(OnlineUserIdentDto onlineUserIdentDto) + public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto) { - return new CachedPlayer(_loggerFactory.CreateLogger(), onlineUserIdentDto, _gameObjectHandlerFactory, - _ipcManager, _fileDownloadManagerFactory.Create(), _mareConfigService, _dalamudUtilService, _hostApplicationLifetime, + return new PairHandler(_loggerFactory.CreateLogger(), onlineUserIdentDto, _gameObjectHandlerFactory, + _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, _fileCacheManager, _mareMediator); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 9e29d1b..663dece 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -118,6 +118,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase } } + public void CompareNameAndThrow(string name) + { + if (!string.Equals(Name, name, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Player name not equal to requested name, pointer invalid"); + } + if (Address == IntPtr.Zero) + { + throw new InvalidOperationException("Player pointer is zero, pointer invalid"); + } + } + public IntPtr CurrentAddress() { _dalamudUtil.EnsureIsOnFramework(); @@ -133,6 +145,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase { Address = IntPtr.Zero; DrawObjectAddress = IntPtr.Zero; + _haltProcessing = false; } public async Task IsBeingDrawnRunOnFrameworkAsync() @@ -156,8 +169,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private unsafe void CheckAndUpdateObject() { - if (_haltProcessing) return; - var prevAddr = Address; var prevDrawObj = DrawObjectAddress; @@ -172,6 +183,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase DrawObjectAddress = IntPtr.Zero; } + if (_haltProcessing) return; + bool drawObjDiff = DrawObjectAddress != prevDrawObj; bool addrDiff = Address != prevAddr; diff --git a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs similarity index 67% rename from MareSynchronos/PlayerData/Pairs/CachedPlayer.cs rename to MareSynchronos/PlayerData/Handlers/PairHandler.cs index b29c8bc..603d138 100644 --- a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -1,12 +1,10 @@ -using Dalamud.Interface.Internal.Notifications; -using Dalamud.Logging; +using Dalamud.Logging; using MareSynchronos.API.Data; using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Interop; -using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Factories; -using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; @@ -17,9 +15,9 @@ using System.Collections.Concurrent; using System.Diagnostics; using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; -namespace MareSynchronos.PlayerData.Pairs; +namespace MareSynchronos.PlayerData.Handlers; -public sealed class CachedPlayer : DisposableMediatorSubscriberBase +public sealed class PairHandler : DisposableMediatorSubscriberBase { private readonly DalamudUtilService _dalamudUtil; private readonly FileDownloadManager _downloadManager; @@ -27,33 +25,36 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly IpcManager _ipcManager; private readonly IHostApplicationLifetime _lifetime; - private readonly OptionalPluginWarning _pluginWarnings; + private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private CancellationTokenSource? _applicationCancellationTokenSource = new(); private Guid _applicationId; private Task? _applicationTask; + private bool _applyLastReceivedDataOnVisible = false; private CharacterData? _cachedData = null; private GameObjectHandler? _charaHandler; private CancellationTokenSource? _downloadCancellationTokenSource = new(); - private CharacterData? _firstTimeInitData; private string _lastGlamourerData = string.Empty; private string _originalGlamourerData = string.Empty; private string _penumbraCollection; private CancellationTokenSource _redrawCts = new(); - public CachedPlayer(ILogger logger, OnlineUserIdentDto onlineUser, - GameObjectHandlerFactory gameObjectHandlerFactory, - IpcManager ipcManager, FileDownloadManager transferManager, MareConfigService mareConfigService, - DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) + public PairHandler(ILogger logger, OnlineUserIdentDto onlineUser, + GameObjectHandlerFactory gameObjectHandlerFactory, + IpcManager ipcManager, FileDownloadManager transferManager, + PluginWarningNotificationService pluginWarningNotificationManager, + DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, + FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) { OnlineUser = onlineUser; _gameObjectHandlerFactory = gameObjectHandlerFactory; _ipcManager = ipcManager; _downloadManager = transferManager; + _pluginWarningNotificationManager = pluginWarningNotificationManager; _dalamudUtil = dalamudUtil; _lifetime = lifetime; _fileDbManager = fileDbManager; - _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollection(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); + _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); Mediator.Subscribe(this, (_) => FrameworkUpdate()); Mediator.Subscribe(this, (_) => @@ -63,7 +64,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (_) => { - _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollection(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); + _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); if (!IsVisible && _charaHandler != null) { PlayerName = string.Empty; @@ -71,40 +72,23 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase _charaHandler = null; } }); - _pluginWarnings ??= new() - { - ShownCustomizePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings, - ShownHeelsWarning = mareConfigService.Current.DisableOptionalPluginWarnings, - ShownPalettePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings, - ShownHonorificWarning = mareConfigService.Current.DisableOptionalPluginWarnings, - }; - } - - private enum PlayerChanges - { - Heels = 1, - Customize = 2, - Palette = 3, - Honorific = 4, - ModFiles = 5, - ModManip = 6, - Glamourer = 7 } public bool IsVisible { get; private set; } public OnlineUserIdentDto OnlineUser { get; private set; } - public IntPtr PlayerCharacter => _charaHandler?.Address ?? IntPtr.Zero; - public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? IntPtr.Zero) == IntPtr.Zero + public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero; + public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero ? uint.MaxValue : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->ObjectID; public string? PlayerName { get; private set; } public string PlayerNameHash => OnlineUser.Ident; - public void ApplyCharacterData(CharacterData characterData, bool forced = false) + public void ApplyCharacterData(API.Data.CharacterData characterData, bool forced = false) { if (_charaHandler == null) { - _firstTimeInitData = characterData; + _applyLastReceivedDataOnVisible = true; + _cachedData = characterData; return; } @@ -126,16 +110,16 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase return; } - var charaDataToUpdate = CheckUpdatedData(_cachedData?.DeepClone() ?? new(), characterData, forced); + var charaDataToUpdate = characterData.CheckUpdatedData(_cachedData?.DeepClone() ?? new(), Logger, this, forced, _applyLastReceivedDataOnVisible); - if (_charaHandler != null && _firstTimeInitData != null) + if (_charaHandler != null && _applyLastReceivedDataOnVisible) { - _firstTimeInitData = null; + _applyLastReceivedDataOnVisible = false; } if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) { - NotifyForMissingPlugins(playerChanges); + _pluginWarningNotificationManager.NotifyForMissingPlugins(OnlineUser.User, PlayerName!, playerChanges); } Logger.LogDebug("Downloading and applying character for {name}", this); @@ -146,8 +130,8 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase public override string ToString() { return OnlineUser == null - ? (base.ToString() ?? string.Empty) - : (OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != IntPtr.Zero ? "HasChar" : "NoChar")); + ? base.ToString() ?? string.Empty + : OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar"); } internal void SetUploading(bool isUploading = true) @@ -210,21 +194,9 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase } } - private static void CheckForNameAndThrow(GameObjectHandler handler, string name) - { - if (!string.Equals(handler.Name, name, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("Player name not equal to requested name, pointer invalid"); - } - if (handler.Address == IntPtr.Zero) - { - throw new InvalidOperationException("Player pointer is zero, pointer invalid"); - } - } - private async Task ApplyCustomizationDataAsync(Guid applicationId, KeyValuePair> changes, CharacterData charaData, CancellationToken token) { - if (PlayerCharacter == IntPtr.Zero) return; + if (PlayerCharacter == nint.Zero) return; var ptr = PlayerCharacter; var handler = changes.Key switch @@ -238,7 +210,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase try { - if (handler.Address == IntPtr.Zero) + if (handler.Address == nint.Zero) { return; } @@ -291,105 +263,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase } } - private Dictionary> CheckUpdatedData(CharacterData oldData, CharacterData newData, bool forced) - { - var charaDataToUpdate = new Dictionary>(); - foreach (ObjectKind objectKind in Enum.GetValues()) - { - charaDataToUpdate[objectKind] = new(); - oldData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements); - newData.FileReplacements.TryGetValue(objectKind, out var newFileReplacements); - oldData.GlamourerData.TryGetValue(objectKind, out var existingGlamourerData); - newData.GlamourerData.TryGetValue(objectKind, out var newGlamourerData); - - bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null; - bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null; - - bool hasNewButNotOldGlamourerData = newGlamourerData != null && existingGlamourerData == null; - bool hasOldButNotNewGlamourerData = existingGlamourerData != null && newGlamourerData == null; - - bool hasNewAndOldFileReplacements = newFileReplacements != null && existingFileReplacements != null; - bool hasNewAndOldGlamourerData = newGlamourerData != null && existingGlamourerData != null; - - if (hasNewButNotOldFileReplacements || hasOldButNotNewFileReplacements || hasNewButNotOldGlamourerData || hasOldButNotNewGlamourerData) - { - Logger.LogDebug("Updating {object}/{kind} (Some new data arrived: NewButNotOldFiles:{hasNewButNotOldFileReplacements}," + - " OldButNotNewFiles:{hasOldButNotNewFileReplacements}, NewButNotOldGlam:{hasNewButNotOldGlamourerData}, OldButNotNewGlam:{hasOldButNotNewGlamourerData}) => {change}, {change2}", - this, objectKind, hasNewButNotOldFileReplacements, hasOldButNotNewFileReplacements, hasNewButNotOldGlamourerData, hasOldButNotNewGlamourerData, PlayerChanges.ModFiles, PlayerChanges.Glamourer); - charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles); - charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer); - } - else - { - if (hasNewAndOldFileReplacements) - { - bool listsAreEqual = oldData.FileReplacements[objectKind].SequenceEqual(newData.FileReplacements[objectKind], Data.FileReplacementDataComparer.Instance); - if (!listsAreEqual || _firstTimeInitData != null) - { - Logger.LogDebug("Updating {object}/{kind} (FileReplacements not equal) => {change}", this, objectKind, PlayerChanges.ModFiles); - charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles); - } - } - - if (hasNewAndOldGlamourerData) - { - bool glamourerDataDifferent = !string.Equals(oldData.GlamourerData[objectKind], newData.GlamourerData[objectKind], StringComparison.Ordinal); - if (glamourerDataDifferent || forced) - { - Logger.LogDebug("Updating {object}/{kind} (Glamourer different) => {change}", this, objectKind, PlayerChanges.Glamourer); - charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer); - } - } - } - - if (objectKind != ObjectKind.Player) continue; - - bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal); - if (manipDataDifferent || _firstTimeInitData != null) - { - Logger.LogDebug("Updating {object}/{kind} (Diff manip data) => {change}", this, objectKind, PlayerChanges.ModManip); - charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip); - } - - bool heelsOffsetDifferent = oldData.HeelsOffset != newData.HeelsOffset; - if (heelsOffsetDifferent || (forced && newData.HeelsOffset != 0)) - { - Logger.LogDebug("Updating {object}/{kind} (Diff heels data) => {change}", this, objectKind, PlayerChanges.Heels); - charaDataToUpdate[objectKind].Add(PlayerChanges.Heels); - } - - bool customizeDataDifferent = !string.Equals(oldData.CustomizePlusData, newData.CustomizePlusData, StringComparison.Ordinal); - if (customizeDataDifferent || (forced && !string.IsNullOrEmpty(newData.CustomizePlusData))) - { - Logger.LogDebug("Updating {object}/{kind} (Diff customize data) => {change}", this, objectKind, PlayerChanges.Customize); - charaDataToUpdate[objectKind].Add(PlayerChanges.Customize); - } - - bool palettePlusDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal); - if (palettePlusDataDifferent || (forced && !string.IsNullOrEmpty(newData.PalettePlusData))) - { - Logger.LogDebug("Updating {object}/{kind} (Diff palette data) => {change}", this, objectKind, PlayerChanges.Palette); - charaDataToUpdate[objectKind].Add(PlayerChanges.Palette); - } - - bool honorificDataDifferent = !string.Equals(oldData.HonorificData, newData.HonorificData, StringComparison.Ordinal); - if (honorificDataDifferent || (forced && !string.IsNullOrEmpty(newData.HonorificData))) - { - Logger.LogDebug("Updating {object}/{kind} (Diff honorific data) => {change}", this, objectKind, PlayerChanges.Honorific); - charaDataToUpdate[objectKind].Add(PlayerChanges.Honorific); - } - } - - foreach (KeyValuePair> data in charaDataToUpdate.ToList()) - { - if (!data.Value.Any()) charaDataToUpdate.Remove(data.Key); - else charaDataToUpdate[data.Key] = data.Value.OrderByDescending(p => (int)p).ToHashSet(); - } - - return charaDataToUpdate; - } - - private void DownloadAndApplyCharacter(CharacterData charaData, Dictionary> updatedData) + private void DownloadAndApplyCharacter(API.Data.CharacterData charaData, Dictionary> updatedData) { if (!updatedData.Any()) { @@ -412,7 +286,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase int attempts = 0; List toDownloadReplacements = TryCalculateModdedDictionary(charaData, out moddedPaths, downloadToken); - while ((toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)) + while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) { _downloadManager.CancelDownload(); Logger.LogDebug("Downloading missing files for player {name}, {kind}", PlayerName, updatedData); @@ -494,14 +368,21 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase { Logger.LogWarning(ex, "[{applicationId}] Cancelled, player turned null during application", _applicationId); IsVisible = false; - if (_cachedData == null) - { - _firstTimeInitData = charaData.DeepClone(); - } + _applyLastReceivedDataOnVisible = true; } catch (Exception ex) { - Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId); + if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException)) + { + IsVisible = false; + _applyLastReceivedDataOnVisible = true; + _cachedData = charaData; + Logger.LogWarning(aggr, "[{applicationId}] Cancelled, player turned null during application", _applicationId); + } + else + { + Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId); + } } }, token); }, downloadToken); @@ -518,21 +399,21 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Logger.LogDebug("One-Time Initialized {this}", this); } - if (_charaHandler?.Address != IntPtr.Zero && !IsVisible) + if (_charaHandler?.Address != nint.Zero && !IsVisible) { IsVisible = true; - Mediator.Publish(new CachedPlayerVisibleMessage(this)); + Mediator.Publish(new PairHandlerVisibleMessage(this)); Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); - if (_firstTimeInitData != null || _cachedData != null) + if (_applyLastReceivedDataOnVisible && _cachedData != null) { Task.Run(async () => { _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false); - ApplyCharacterData(_firstTimeInitData ?? _cachedData!, true); + ApplyCharacterData(_cachedData!, true); }); } } - else if (_charaHandler?.Address == IntPtr.Zero && IsVisible) + else if (_charaHandler?.Address == nint.Zero && IsVisible) { IsVisible = false; Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); @@ -566,7 +447,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase await _ipcManager.HonorificSetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false); }); - _ipcManager.PenumbraAssignTemporaryCollection(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult(); + _ipcManager.PenumbraAssignTemporaryCollectionAsync(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult(); _downloadManager.Initialize(); } @@ -593,44 +474,10 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase }, token); } - private void NotifyForMissingPlugins(HashSet changes) - { - List missingPluginsForData = new(); - if (changes.Contains(PlayerChanges.Heels) && !_pluginWarnings.ShownHeelsWarning && !_ipcManager.CheckHeelsApi()) - { - missingPluginsForData.Add("Heels"); - _pluginWarnings.ShownHeelsWarning = true; - } - if (changes.Contains(PlayerChanges.Customize) && !_pluginWarnings.ShownCustomizePlusWarning && !_ipcManager.CheckCustomizePlusApi()) - { - missingPluginsForData.Add("Customize+"); - _pluginWarnings.ShownCustomizePlusWarning = true; - } - - if (changes.Contains(PlayerChanges.Palette) && !_pluginWarnings.ShownPalettePlusWarning && !_ipcManager.CheckPalettePlusApi()) - { - missingPluginsForData.Add("Palette+"); - _pluginWarnings.ShownPalettePlusWarning = true; - } - - if (changes.Contains(PlayerChanges.Honorific) && !_pluginWarnings.ShownHonorificWarning && !_ipcManager.CheckHonorificApi()) - { - missingPluginsForData.Add("Honorific"); - _pluginWarnings.ShownHonorificWarning = true; - } - - if (missingPluginsForData.Any()) - { - Mediator.Publish(new NotificationMessage("Missing plugins for " + PlayerName, - $"Received data for {PlayerName} that contained information for plugins you have not installed. Install {string.Join(", ", missingPluginsForData)} to experience their character fully.", - NotificationType.Warning, 10000)); - } - } - private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId) { nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident); - if (address == IntPtr.Zero) return; + if (address == nint.Zero) return; var cancelToken = new CancellationTokenSource(); cancelToken.CancelAfter(TimeSpan.FromSeconds(60)); @@ -640,26 +487,26 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase if (objectKind == ObjectKind.Player) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, false).ConfigureAwait(false); - CheckForNameAndThrow(tempHandler, name); + tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData); await _ipcManager.GlamourerApplyCustomizationAndEquipmentAsync(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); - CheckForNameAndThrow(tempHandler, name); + tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false); - CheckForNameAndThrow(tempHandler, name); + tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.CustomizePlusRevertAsync(address).ConfigureAwait(false); - CheckForNameAndThrow(tempHandler, name); + tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Palette+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.PalettePlusRemovePaletteAsync(address).ConfigureAwait(false); - CheckForNameAndThrow(tempHandler, name); + tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false); } else if (objectKind == ObjectKind.MinionOrMount) { var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false); - if (minionOrMount != IntPtr.Zero) + if (minionOrMount != nint.Zero) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false); await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); @@ -668,7 +515,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase else if (objectKind == ObjectKind.Pet) { var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); - if (pet != IntPtr.Zero) + if (pet != nint.Zero) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false); await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); @@ -677,7 +524,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase else if (objectKind == ObjectKind.Companion) { var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); - if (companion != IntPtr.Zero) + if (companion != nint.Zero) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false); await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); diff --git a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs index f65e718..fa4c136 100644 --- a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs +++ b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs @@ -1,4 +1,5 @@ using MareSynchronos.API.Data; +using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; @@ -13,7 +14,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase private readonly ApiController _apiController; private readonly DalamudUtilService _dalamudUtil; private readonly FileUploadManager _fileTransferManager; - private readonly HashSet _newVisiblePlayers = new(); + private readonly HashSet _newVisiblePlayers = new(); private readonly PairManager _pairManager; private CharacterData? _lastSentData; @@ -40,7 +41,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value); } }); - Mediator.Subscribe(this, (msg) => _newVisiblePlayers.Add(msg.Player)); + Mediator.Subscribe(this, (msg) => _newVisiblePlayers.Add(msg.Player)); Mediator.Subscribe(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers())); } diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index 53907bb..2f59733 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -5,6 +5,7 @@ using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.User; using MareSynchronos.PlayerData.Factories; +using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Utils; @@ -14,7 +15,7 @@ namespace MareSynchronos.PlayerData.Pairs; public class Pair { - private readonly CachedPlayerFactory _cachedPlayerFactory; + private readonly PairHandlerFactory _cachedPlayerFactory; private readonly SemaphoreSlim _creationSemaphore = new(1); private readonly ILogger _logger; private readonly MareMediator _mediator; @@ -22,7 +23,7 @@ public class Pair private CancellationTokenSource _applicationCts = new CancellationTokenSource(); private OnlineUserIdentDto? _onlineUserIdentDto = null; - public Pair(ILogger logger, CachedPlayerFactory cachedPlayerFactory, + public Pair(ILogger logger, PairHandlerFactory cachedPlayerFactory, MareMediator mediator, ServerConfigurationManager serverConfigurationManager) { _logger = logger; @@ -46,7 +47,7 @@ public class Pair public UserPairDto? UserPair { get; set; } - private CachedPlayer? CachedPlayer { get; set; } + private PairHandler? CachedPlayer { get; set; } public void AddContextMenu(GameObjectContextMenuOpenArgs args) { diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index ce4ddb7..26dc180 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -69,8 +69,9 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService>(), clientState, objectTable, framework, gameGui, condition, gameData, s.GetRequiredService(), s.GetRequiredService())); diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index c390a2c..05c11da 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -65,6 +65,6 @@ public record ProfilePopoutToggle(Pair? Pair) : MessageBase; public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase; public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase; public record RemoveWindowMessage(WindowMediatorSubscriberBase Window) : MessageBase; -public record CachedPlayerVisibleMessage(CachedPlayer Player) : MessageBase; +public record PairHandlerVisibleMessage(PairHandler Player) : MessageBase; #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/Services/PluginWarningNotificationService.cs b/MareSynchronos/Services/PluginWarningNotificationService.cs new file mode 100644 index 0000000..1765d60 --- /dev/null +++ b/MareSynchronos/Services/PluginWarningNotificationService.cs @@ -0,0 +1,68 @@ +using Dalamud.Interface.Internal.Notifications; +using MareSynchronos.API.Data; +using MareSynchronos.API.Data.Comparer; +using MareSynchronos.Interop; +using MareSynchronos.MareConfiguration; +using MareSynchronos.Services.Mediator; + +namespace MareSynchronos.PlayerData.Pairs; + +public class PluginWarningNotificationService +{ + private readonly Dictionary _cachedOptionalPluginWarnings = new(UserDataComparer.Instance); + private readonly IpcManager _ipcManager; + private readonly MareConfigService _mareConfigService; + private readonly MareMediator _mediator; + + public PluginWarningNotificationService(MareConfigService mareConfigService, IpcManager ipcManager, MareMediator mediator) + { + _mareConfigService = mareConfigService; + _ipcManager = ipcManager; + _mediator = mediator; + } + + public void NotifyForMissingPlugins(UserData user, string playerName, HashSet changes) + { + if (!_cachedOptionalPluginWarnings.TryGetValue(user, out var warning)) + { + _cachedOptionalPluginWarnings[user] = warning = new() + { + ShownCustomizePlusWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, + ShownHeelsWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, + ShownPalettePlusWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, + ShownHonorificWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, + }; + } + + List missingPluginsForData = new(); + if (changes.Contains(PlayerChanges.Heels) && !warning.ShownHeelsWarning && !_ipcManager.CheckHeelsApi()) + { + missingPluginsForData.Add("Heels"); + warning.ShownHeelsWarning = true; + } + if (changes.Contains(PlayerChanges.Customize) && !warning.ShownCustomizePlusWarning && !_ipcManager.CheckCustomizePlusApi()) + { + missingPluginsForData.Add("Customize+"); + warning.ShownCustomizePlusWarning = true; + } + + if (changes.Contains(PlayerChanges.Palette) && !warning.ShownPalettePlusWarning && !_ipcManager.CheckPalettePlusApi()) + { + missingPluginsForData.Add("Palette+"); + warning.ShownPalettePlusWarning = true; + } + + if (changes.Contains(PlayerChanges.Honorific) && !warning.ShownHonorificWarning && !_ipcManager.CheckHonorificApi()) + { + missingPluginsForData.Add("Honorific"); + warning.ShownHonorificWarning = true; + } + + if (missingPluginsForData.Any()) + { + _mediator.Publish(new NotificationMessage("Missing plugins for " + playerName, + $"Received data for {playerName} that contained information for plugins you have not installed. Install {string.Join(", ", missingPluginsForData)} to experience their character fully.", + NotificationType.Warning, 10000)); + } + } +} \ No newline at end of file diff --git a/MareSynchronos/Utils/VariousExtensions.cs b/MareSynchronos/Utils/VariousExtensions.cs index 3f49c39..07453e7 100644 --- a/MareSynchronos/Utils/VariousExtensions.cs +++ b/MareSynchronos/Utils/VariousExtensions.cs @@ -1,10 +1,133 @@ using Dalamud.Game.ClientState.Objects.Types; +using MareSynchronos.API.Data; +using MareSynchronos.API.Data.Enum; +using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.PlayerData.Pairs; +using Microsoft.Extensions.Logging; using System.Text.Json; namespace MareSynchronos.Utils; public static class VariousExtensions { + public static void CancelDispose(this CancellationTokenSource? cts) + { + try + { + cts?.Cancel(); + cts?.Dispose(); + } + catch (ObjectDisposedException) + { + // swallow it + } + } + + public static CancellationTokenSource CancelRecreate(this CancellationTokenSource? cts) + { + cts.CancelDispose(); + return new CancellationTokenSource(); + } + + public static Dictionary> CheckUpdatedData(this CharacterData newData, CharacterData? oldData, ILogger logger, PairHandler cachedPlayer, bool forced, bool applyLastOnVisible) + { + oldData ??= new(); + var charaDataToUpdate = new Dictionary>(); + foreach (ObjectKind objectKind in Enum.GetValues()) + { + charaDataToUpdate[objectKind] = new(); + oldData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements); + newData.FileReplacements.TryGetValue(objectKind, out var newFileReplacements); + oldData.GlamourerData.TryGetValue(objectKind, out var existingGlamourerData); + newData.GlamourerData.TryGetValue(objectKind, out var newGlamourerData); + + bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null; + bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null; + + bool hasNewButNotOldGlamourerData = newGlamourerData != null && existingGlamourerData == null; + bool hasOldButNotNewGlamourerData = existingGlamourerData != null && newGlamourerData == null; + + bool hasNewAndOldFileReplacements = newFileReplacements != null && existingFileReplacements != null; + bool hasNewAndOldGlamourerData = newGlamourerData != null && existingGlamourerData != null; + + if (hasNewButNotOldFileReplacements || hasOldButNotNewFileReplacements || hasNewButNotOldGlamourerData || hasOldButNotNewGlamourerData) + { + logger.LogDebug("Updating {object}/{kind} (Some new data arrived: NewButNotOldFiles:{hasNewButNotOldFileReplacements}," + + " OldButNotNewFiles:{hasOldButNotNewFileReplacements}, NewButNotOldGlam:{hasNewButNotOldGlamourerData}, OldButNotNewGlam:{hasOldButNotNewGlamourerData}) => {change}, {change2}", + cachedPlayer, objectKind, hasNewButNotOldFileReplacements, hasOldButNotNewFileReplacements, hasNewButNotOldGlamourerData, hasOldButNotNewGlamourerData, PlayerChanges.ModFiles, PlayerChanges.Glamourer); + charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles); + charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer); + } + else + { + if (hasNewAndOldFileReplacements) + { + bool listsAreEqual = oldData.FileReplacements[objectKind].SequenceEqual(newData.FileReplacements[objectKind], PlayerData.Data.FileReplacementDataComparer.Instance); + if (!listsAreEqual || applyLastOnVisible) + { + logger.LogDebug("Updating {object}/{kind} (FileReplacements not equal) => {change}", cachedPlayer, objectKind, PlayerChanges.ModFiles); + charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles); + } + } + + if (hasNewAndOldGlamourerData) + { + bool glamourerDataDifferent = !string.Equals(oldData.GlamourerData[objectKind], newData.GlamourerData[objectKind], StringComparison.Ordinal); + if (glamourerDataDifferent || forced) + { + logger.LogDebug("Updating {object}/{kind} (Glamourer different) => {change}", cachedPlayer, objectKind, PlayerChanges.Glamourer); + charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer); + } + } + } + + if (objectKind != ObjectKind.Player) continue; + + bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal); + if (manipDataDifferent || applyLastOnVisible) + { + logger.LogDebug("Updating {object}/{kind} (Diff manip data) => {change}", cachedPlayer, objectKind, PlayerChanges.ModManip); + charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip); + } + + bool heelsOffsetDifferent = oldData.HeelsOffset != newData.HeelsOffset; + if (heelsOffsetDifferent || (forced && newData.HeelsOffset != 0)) + { + logger.LogDebug("Updating {object}/{kind} (Diff heels data) => {change}", cachedPlayer, objectKind, PlayerChanges.Heels); + charaDataToUpdate[objectKind].Add(PlayerChanges.Heels); + } + + bool customizeDataDifferent = !string.Equals(oldData.CustomizePlusData, newData.CustomizePlusData, StringComparison.Ordinal); + if (customizeDataDifferent || (forced && !string.IsNullOrEmpty(newData.CustomizePlusData))) + { + logger.LogDebug("Updating {object}/{kind} (Diff customize data) => {change}", cachedPlayer, objectKind, PlayerChanges.Customize); + charaDataToUpdate[objectKind].Add(PlayerChanges.Customize); + } + + bool palettePlusDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal); + if (palettePlusDataDifferent || (forced && !string.IsNullOrEmpty(newData.PalettePlusData))) + { + logger.LogDebug("Updating {object}/{kind} (Diff palette data) => {change}", cachedPlayer, objectKind, PlayerChanges.Palette); + charaDataToUpdate[objectKind].Add(PlayerChanges.Palette); + } + + bool honorificDataDifferent = !string.Equals(oldData.HonorificData, newData.HonorificData, StringComparison.Ordinal); + if (honorificDataDifferent || (forced && !string.IsNullOrEmpty(newData.HonorificData))) + { + logger.LogDebug("Updating {object}/{kind} (Diff honorific data) => {change}", cachedPlayer, objectKind, PlayerChanges.Honorific); + charaDataToUpdate[objectKind].Add(PlayerChanges.Honorific); + } + } + + foreach (KeyValuePair> data in charaDataToUpdate.ToList()) + { + if (!data.Value.Any()) charaDataToUpdate.Remove(data.Key); + else charaDataToUpdate[data.Key] = data.Value.OrderByDescending(p => (int)p).ToHashSet(); + } + + return charaDataToUpdate; + } + public static T DeepClone(this T obj) { return JsonSerializer.Deserialize(JsonSerializer.Serialize(obj))!; @@ -19,23 +142,4 @@ public static class VariousExtensions return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address)->ObjectIndex; } - - public static void CancelDispose(this CancellationTokenSource? cts) - { - try - { - cts?.Cancel(); - cts?.Dispose(); - } - catch(ObjectDisposedException) - { - // swallow it - } - } - - public static CancellationTokenSource CancelRecreate(this CancellationTokenSource? cts) - { - cts.CancelDispose(); - return new CancellationTokenSource(); - } -} +} \ No newline at end of file