cleanup and fix application issues
This commit is contained in:
		| @@ -62,7 +62,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase | |||||||
|     private readonly ActionSubscriber<GameObject, RedrawType> _penumbraRedrawObject; |     private readonly ActionSubscriber<GameObject, RedrawType> _penumbraRedrawObject; | ||||||
|     private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new(); |     private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new(); | ||||||
|     private readonly FuncSubscriber<string, PenumbraApiEc> _penumbraRemoveTemporaryCollection; |     private readonly FuncSubscriber<string, PenumbraApiEc> _penumbraRemoveTemporaryCollection; | ||||||
|     private readonly FuncSubscriber<string, string, int, PenumbraApiEc> _penumbraRemoveTemporaryMod; |  | ||||||
|     private readonly FuncSubscriber<string> _penumbraResolveModDir; |     private readonly FuncSubscriber<string> _penumbraResolveModDir; | ||||||
|     private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths; |     private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths; | ||||||
|     private readonly SemaphoreSlim _redrawSemaphore = new(2); |     private readonly SemaphoreSlim _redrawSemaphore = new(2); | ||||||
| @@ -92,7 +91,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase | |||||||
|         _penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi); |         _penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi); | ||||||
|         _penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi); |         _penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi); | ||||||
|         _penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi); |         _penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi); | ||||||
|         _penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi); |  | ||||||
|         _penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi); |         _penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi); | ||||||
|         _penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi); |         _penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi); | ||||||
|         _penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi); |         _penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi); | ||||||
| @@ -409,6 +407,31 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase | |||||||
|         }).ConfigureAwait(false); |         }).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<string> 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() |     public string PenumbraGetMetaManipulations() | ||||||
|     { |     { | ||||||
|         if (!CheckPenumbraApi()) return string.Empty; |         if (!CheckPenumbraApi()) return string.Empty; | ||||||
| @@ -449,6 +472,18 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase | |||||||
|         return await _dalamudUtil.RunOnFrameworkThread(() => _penumbraResolvePaths.Invoke(forward, reverse)).ConfigureAwait(false); |         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<string, string>(), 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<string, string> modPaths) |     public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, string collName, Dictionary<string, string> modPaths) | ||||||
|     { |     { | ||||||
|         if (!CheckPenumbraApi()) return; |         if (!CheckPenumbraApi()) return; | ||||||
| @@ -459,54 +494,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase | |||||||
|             { |             { | ||||||
|                 logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value); |                 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); |             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); |             logger.LogTrace("[{applicationId}] Setting temp files mod for {collName}, Success: {ret}", applicationId, collName, retAdd); | ||||||
|         }).ConfigureAwait(false); |         }).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<string, string>(), manipulationData, 0); |  | ||||||
|             logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd); |  | ||||||
|         }).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public async Task<string> 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) |     public void ToggleGposeQueueMode(bool on) | ||||||
|     { |     { | ||||||
|         _inGposeQueueMode = on; |         _inGposeQueueMode = on; | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								MareSynchronos/PlayerData/Data/PlayerChanges.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								MareSynchronos/PlayerData/Data/PlayerChanges.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
| @@ -66,8 +66,8 @@ public class MareCharaFileManager | |||||||
|                 var applicationId = Guid.NewGuid(); |                 var applicationId = Guid.NewGuid(); | ||||||
|                 _ipcManager.ToggleGposeQueueMode(on: true); |                 _ipcManager.ToggleGposeQueueMode(on: true); | ||||||
|                 await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false); |                 await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false); | ||||||
|                 var coll = await _ipcManager.PenumbraCreateTemporaryCollection(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); |                 var coll = await _ipcManager.PenumbraCreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraAssignTemporaryCollection(_logger, coll, charaTarget.ObjectTableIndex()!.Value).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.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); |                 await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); | ||||||
|                 using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false); |                 using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false); | ||||||
|   | |||||||
| @@ -7,12 +7,12 @@ namespace MareSynchronos.PlayerData.Factories; | |||||||
|  |  | ||||||
| public class PairFactory | public class PairFactory | ||||||
| { | { | ||||||
|     private readonly CachedPlayerFactory _cachedPlayerFactory; |     private readonly PairHandlerFactory _cachedPlayerFactory; | ||||||
|     private readonly ILoggerFactory _loggerFactory; |     private readonly ILoggerFactory _loggerFactory; | ||||||
|     private readonly MareMediator _mareMediator; |     private readonly MareMediator _mareMediator; | ||||||
|     private readonly ServerConfigurationManager _serverConfigurationManager; |     private readonly ServerConfigurationManager _serverConfigurationManager; | ||||||
|  |  | ||||||
|     public PairFactory(ILoggerFactory loggerFactory, CachedPlayerFactory cachedPlayerFactory, |     public PairFactory(ILoggerFactory loggerFactory, PairHandlerFactory cachedPlayerFactory, | ||||||
|         MareMediator mareMediator, ServerConfigurationManager serverConfigurationManager) |         MareMediator mareMediator, ServerConfigurationManager serverConfigurationManager) | ||||||
|     { |     { | ||||||
|         _loggerFactory = loggerFactory; |         _loggerFactory = loggerFactory; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| using MareSynchronos.API.Dto.User; | using MareSynchronos.API.Dto.User; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; | |||||||
| 
 | 
 | ||||||
| namespace MareSynchronos.PlayerData.Factories; | namespace MareSynchronos.PlayerData.Factories; | ||||||
| 
 | 
 | ||||||
| public class CachedPlayerFactory | public class PairHandlerFactory | ||||||
| { | { | ||||||
|     private readonly DalamudUtilService _dalamudUtilService; |     private readonly DalamudUtilService _dalamudUtilService; | ||||||
|     private readonly FileCacheManager _fileCacheManager; |     private readonly FileCacheManager _fileCacheManager; | ||||||
| @@ -19,28 +19,29 @@ public class CachedPlayerFactory | |||||||
|     private readonly IHostApplicationLifetime _hostApplicationLifetime; |     private readonly IHostApplicationLifetime _hostApplicationLifetime; | ||||||
|     private readonly IpcManager _ipcManager; |     private readonly IpcManager _ipcManager; | ||||||
|     private readonly ILoggerFactory _loggerFactory; |     private readonly ILoggerFactory _loggerFactory; | ||||||
|     private readonly MareConfigService _mareConfigService; |  | ||||||
|     private readonly MareMediator _mareMediator; |     private readonly MareMediator _mareMediator; | ||||||
|  |     private readonly PluginWarningNotificationService _pluginWarningNotificationManager; | ||||||
| 
 | 
 | ||||||
|     public CachedPlayerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, |     public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, | ||||||
|         FileDownloadManagerFactory fileDownloadManagerFactory, MareConfigService mareConfigService, DalamudUtilService dalamudUtilService, |         FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, | ||||||
|         IHostApplicationLifetime hostApplicationLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator) |         PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, | ||||||
|  |         FileCacheManager fileCacheManager, MareMediator mareMediator) | ||||||
|     { |     { | ||||||
|         _loggerFactory = loggerFactory; |         _loggerFactory = loggerFactory; | ||||||
|         _gameObjectHandlerFactory = gameObjectHandlerFactory; |         _gameObjectHandlerFactory = gameObjectHandlerFactory; | ||||||
|         _ipcManager = ipcManager; |         _ipcManager = ipcManager; | ||||||
|         _fileDownloadManagerFactory = fileDownloadManagerFactory; |         _fileDownloadManagerFactory = fileDownloadManagerFactory; | ||||||
|         _mareConfigService = mareConfigService; |  | ||||||
|         _dalamudUtilService = dalamudUtilService; |         _dalamudUtilService = dalamudUtilService; | ||||||
|  |         _pluginWarningNotificationManager = pluginWarningNotificationManager; | ||||||
|         _hostApplicationLifetime = hostApplicationLifetime; |         _hostApplicationLifetime = hostApplicationLifetime; | ||||||
|         _fileCacheManager = fileCacheManager; |         _fileCacheManager = fileCacheManager; | ||||||
|         _mareMediator = mareMediator; |         _mareMediator = mareMediator; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public CachedPlayer Create(OnlineUserIdentDto onlineUserIdentDto) |     public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto) | ||||||
|     { |     { | ||||||
|         return new CachedPlayer(_loggerFactory.CreateLogger<CachedPlayer>(), onlineUserIdentDto, _gameObjectHandlerFactory, |         return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), onlineUserIdentDto, _gameObjectHandlerFactory, | ||||||
|             _ipcManager, _fileDownloadManagerFactory.Create(), _mareConfigService, _dalamudUtilService, _hostApplicationLifetime, |             _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, | ||||||
|             _fileCacheManager, _mareMediator); |             _fileCacheManager, _mareMediator); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -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() |     public IntPtr CurrentAddress() | ||||||
|     { |     { | ||||||
|         _dalamudUtil.EnsureIsOnFramework(); |         _dalamudUtil.EnsureIsOnFramework(); | ||||||
| @@ -133,6 +145,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase | |||||||
|     { |     { | ||||||
|         Address = IntPtr.Zero; |         Address = IntPtr.Zero; | ||||||
|         DrawObjectAddress = IntPtr.Zero; |         DrawObjectAddress = IntPtr.Zero; | ||||||
|  |         _haltProcessing = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<bool> IsBeingDrawnRunOnFrameworkAsync() |     public async Task<bool> IsBeingDrawnRunOnFrameworkAsync() | ||||||
| @@ -156,8 +169,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|     private unsafe void CheckAndUpdateObject() |     private unsafe void CheckAndUpdateObject() | ||||||
|     { |     { | ||||||
|         if (_haltProcessing) return; |  | ||||||
|  |  | ||||||
|         var prevAddr = Address; |         var prevAddr = Address; | ||||||
|         var prevDrawObj = DrawObjectAddress; |         var prevDrawObj = DrawObjectAddress; | ||||||
|  |  | ||||||
| @@ -172,6 +183,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase | |||||||
|             DrawObjectAddress = IntPtr.Zero; |             DrawObjectAddress = IntPtr.Zero; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (_haltProcessing) return; | ||||||
|  |  | ||||||
|         bool drawObjDiff = DrawObjectAddress != prevDrawObj; |         bool drawObjDiff = DrawObjectAddress != prevDrawObj; | ||||||
|         bool addrDiff = Address != prevAddr; |         bool addrDiff = Address != prevAddr; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,10 @@ | |||||||
| using Dalamud.Interface.Internal.Notifications; | using Dalamud.Logging; | ||||||
| using Dalamud.Logging; |  | ||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
| using MareSynchronos.API.Dto.User; | using MareSynchronos.API.Dto.User; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop; | ||||||
| using MareSynchronos.MareConfiguration; |  | ||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
| using MareSynchronos.PlayerData.Handlers; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Utils; | using MareSynchronos.Utils; | ||||||
| @@ -17,9 +15,9 @@ using System.Collections.Concurrent; | |||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; | 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 DalamudUtilService _dalamudUtil; | ||||||
|     private readonly FileDownloadManager _downloadManager; |     private readonly FileDownloadManager _downloadManager; | ||||||
| @@ -27,33 +25,36 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|     private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; |     private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; | ||||||
|     private readonly IpcManager _ipcManager; |     private readonly IpcManager _ipcManager; | ||||||
|     private readonly IHostApplicationLifetime _lifetime; |     private readonly IHostApplicationLifetime _lifetime; | ||||||
|     private readonly OptionalPluginWarning _pluginWarnings; |     private readonly PluginWarningNotificationService _pluginWarningNotificationManager; | ||||||
|     private CancellationTokenSource? _applicationCancellationTokenSource = new(); |     private CancellationTokenSource? _applicationCancellationTokenSource = new(); | ||||||
|     private Guid _applicationId; |     private Guid _applicationId; | ||||||
|     private Task? _applicationTask; |     private Task? _applicationTask; | ||||||
|  |     private bool _applyLastReceivedDataOnVisible = false; | ||||||
|     private CharacterData? _cachedData = null; |     private CharacterData? _cachedData = null; | ||||||
|     private GameObjectHandler? _charaHandler; |     private GameObjectHandler? _charaHandler; | ||||||
|     private CancellationTokenSource? _downloadCancellationTokenSource = new(); |     private CancellationTokenSource? _downloadCancellationTokenSource = new(); | ||||||
|     private CharacterData? _firstTimeInitData; |  | ||||||
|     private string _lastGlamourerData = string.Empty; |     private string _lastGlamourerData = string.Empty; | ||||||
|     private string _originalGlamourerData = string.Empty; |     private string _originalGlamourerData = string.Empty; | ||||||
|     private string _penumbraCollection; |     private string _penumbraCollection; | ||||||
|     private CancellationTokenSource _redrawCts = new(); |     private CancellationTokenSource _redrawCts = new(); | ||||||
| 
 | 
 | ||||||
|     public CachedPlayer(ILogger<CachedPlayer> logger, OnlineUserIdentDto onlineUser, |     public PairHandler(ILogger<PairHandler> logger, OnlineUserIdentDto onlineUser, | ||||||
|                 GameObjectHandlerFactory gameObjectHandlerFactory, |         GameObjectHandlerFactory gameObjectHandlerFactory, | ||||||
|         IpcManager ipcManager, FileDownloadManager transferManager, MareConfigService mareConfigService, |         IpcManager ipcManager, FileDownloadManager transferManager, | ||||||
|         DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) |         PluginWarningNotificationService pluginWarningNotificationManager, | ||||||
|  |         DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, | ||||||
|  |         FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) | ||||||
|     { |     { | ||||||
|         OnlineUser = onlineUser; |         OnlineUser = onlineUser; | ||||||
|         _gameObjectHandlerFactory = gameObjectHandlerFactory; |         _gameObjectHandlerFactory = gameObjectHandlerFactory; | ||||||
|         _ipcManager = ipcManager; |         _ipcManager = ipcManager; | ||||||
|         _downloadManager = transferManager; |         _downloadManager = transferManager; | ||||||
|  |         _pluginWarningNotificationManager = pluginWarningNotificationManager; | ||||||
|         _dalamudUtil = dalamudUtil; |         _dalamudUtil = dalamudUtil; | ||||||
|         _lifetime = lifetime; |         _lifetime = lifetime; | ||||||
|         _fileDbManager = fileDbManager; |         _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<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); |         Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); | ||||||
|         Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => |         Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => | ||||||
| @@ -63,7 +64,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|         }); |         }); | ||||||
|         Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => |         Mediator.Subscribe<PenumbraInitializedMessage>(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) |             if (!IsVisible && _charaHandler != null) | ||||||
|             { |             { | ||||||
|                 PlayerName = string.Empty; |                 PlayerName = string.Empty; | ||||||
| @@ -71,40 +72,23 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|                 _charaHandler = null; |                 _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 bool IsVisible { get; private set; } | ||||||
|     public OnlineUserIdentDto OnlineUser { get; private set; } |     public OnlineUserIdentDto OnlineUser { get; private set; } | ||||||
|     public IntPtr PlayerCharacter => _charaHandler?.Address ?? IntPtr.Zero; |     public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero; | ||||||
|     public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? IntPtr.Zero) == IntPtr.Zero |     public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero | ||||||
|         ? uint.MaxValue |         ? uint.MaxValue | ||||||
|         : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->ObjectID; |         : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->ObjectID; | ||||||
|     public string? PlayerName { get; private set; } |     public string? PlayerName { get; private set; } | ||||||
|     public string PlayerNameHash => OnlineUser.Ident; |     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) |         if (_charaHandler == null) | ||||||
|         { |         { | ||||||
|             _firstTimeInitData = characterData; |             _applyLastReceivedDataOnVisible = true; | ||||||
|  |             _cachedData = characterData; | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -126,16 +110,16 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|             return; |             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)) |         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); |         Logger.LogDebug("Downloading and applying character for {name}", this); | ||||||
| @@ -146,8 +130,8 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|     public override string ToString() |     public override string ToString() | ||||||
|     { |     { | ||||||
|         return OnlineUser == null |         return OnlineUser == null | ||||||
|             ? (base.ToString() ?? string.Empty) |             ? base.ToString() ?? string.Empty | ||||||
|             : (OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != IntPtr.Zero ? "HasChar" : "NoChar")); |             : OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     internal void SetUploading(bool isUploading = true) |     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<ObjectKind, HashSet<PlayerChanges>> changes, CharacterData charaData, CancellationToken token) |     private async Task ApplyCustomizationDataAsync(Guid applicationId, KeyValuePair<ObjectKind, HashSet<PlayerChanges>> changes, CharacterData charaData, CancellationToken token) | ||||||
|     { |     { | ||||||
|         if (PlayerCharacter == IntPtr.Zero) return; |         if (PlayerCharacter == nint.Zero) return; | ||||||
|         var ptr = PlayerCharacter; |         var ptr = PlayerCharacter; | ||||||
| 
 | 
 | ||||||
|         var handler = changes.Key switch |         var handler = changes.Key switch | ||||||
| @@ -238,7 +210,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
| 
 | 
 | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             if (handler.Address == IntPtr.Zero) |             if (handler.Address == nint.Zero) | ||||||
|             { |             { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| @@ -291,105 +263,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Dictionary<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(CharacterData oldData, CharacterData newData, bool forced) |     private void DownloadAndApplyCharacter(API.Data.CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData) | ||||||
|     { |  | ||||||
|         var charaDataToUpdate = new Dictionary<ObjectKind, HashSet<PlayerChanges>>(); |  | ||||||
|         foreach (ObjectKind objectKind in Enum.GetValues<ObjectKind>()) |  | ||||||
|         { |  | ||||||
|             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<ObjectKind, HashSet<PlayerChanges>> 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<ObjectKind, HashSet<PlayerChanges>> updatedData) |  | ||||||
|     { |     { | ||||||
|         if (!updatedData.Any()) |         if (!updatedData.Any()) | ||||||
|         { |         { | ||||||
| @@ -412,7 +286,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|                 int attempts = 0; |                 int attempts = 0; | ||||||
|                 List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(charaData, out moddedPaths, downloadToken); |                 List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(charaData, out moddedPaths, downloadToken); | ||||||
| 
 | 
 | ||||||
|                 while ((toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)) |                 while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) | ||||||
|                 { |                 { | ||||||
|                     _downloadManager.CancelDownload(); |                     _downloadManager.CancelDownload(); | ||||||
|                     Logger.LogDebug("Downloading missing files for player {name}, {kind}", PlayerName, updatedData); |                     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); |                     Logger.LogWarning(ex, "[{applicationId}] Cancelled, player turned null during application", _applicationId); | ||||||
|                     IsVisible = false; |                     IsVisible = false; | ||||||
|                     if (_cachedData == null) |                     _applyLastReceivedDataOnVisible = true; | ||||||
|                     { |  | ||||||
|                         _firstTimeInitData = charaData.DeepClone(); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 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); |             }, token); | ||||||
|         }, downloadToken); |         }, downloadToken); | ||||||
| @@ -518,21 +399,21 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|             Logger.LogDebug("One-Time Initialized {this}", this); |             Logger.LogDebug("One-Time Initialized {this}", this); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (_charaHandler?.Address != IntPtr.Zero && !IsVisible) |         if (_charaHandler?.Address != nint.Zero && !IsVisible) | ||||||
|         { |         { | ||||||
|             IsVisible = true; |             IsVisible = true; | ||||||
|             Mediator.Publish(new CachedPlayerVisibleMessage(this)); |             Mediator.Publish(new PairHandlerVisibleMessage(this)); | ||||||
|             Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); |             Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); | ||||||
|             if (_firstTimeInitData != null || _cachedData != null) |             if (_applyLastReceivedDataOnVisible && _cachedData != null) | ||||||
|             { |             { | ||||||
|                 Task.Run(async () => |                 Task.Run(async () => | ||||||
|                 { |                 { | ||||||
|                     _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false); |                     _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; |             IsVisible = false; | ||||||
|             Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); |             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); |             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(); |         _downloadManager.Initialize(); | ||||||
|     } |     } | ||||||
| @@ -593,44 +474,10 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|         }, token); |         }, token); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void NotifyForMissingPlugins(HashSet<PlayerChanges> changes) |  | ||||||
|     { |  | ||||||
|         List<string> 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) |     private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId) | ||||||
|     { |     { | ||||||
|         nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident); |         nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident); | ||||||
|         if (address == IntPtr.Zero) return; |         if (address == nint.Zero) return; | ||||||
| 
 | 
 | ||||||
|         var cancelToken = new CancellationTokenSource(); |         var cancelToken = new CancellationTokenSource(); | ||||||
|         cancelToken.CancelAfter(TimeSpan.FromSeconds(60)); |         cancelToken.CancelAfter(TimeSpan.FromSeconds(60)); | ||||||
| @@ -640,26 +487,26 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | |||||||
|         if (objectKind == ObjectKind.Player) |         if (objectKind == ObjectKind.Player) | ||||||
|         { |         { | ||||||
|             using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, false).ConfigureAwait(false); |             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); |             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); |             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); |             Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); | ||||||
|             await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false); |             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); |             Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); | ||||||
|             await _ipcManager.CustomizePlusRevertAsync(address).ConfigureAwait(false); |             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); |             Logger.LogDebug("[{applicationId}] Restoring Palette+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); | ||||||
|             await _ipcManager.PalettePlusRemovePaletteAsync(address).ConfigureAwait(false); |             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); |             Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); | ||||||
|             await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false); |             await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|         else if (objectKind == ObjectKind.MinionOrMount) |         else if (objectKind == ObjectKind.MinionOrMount) | ||||||
|         { |         { | ||||||
|             var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false); |             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); |                 using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).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) |         else if (objectKind == ObjectKind.Pet) | ||||||
|         { |         { | ||||||
|             var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); |             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); |                 using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).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) |         else if (objectKind == ObjectKind.Companion) | ||||||
|         { |         { | ||||||
|             var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); |             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); |                 using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); |                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
|  | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Utils; | using MareSynchronos.Utils; | ||||||
| @@ -13,7 +14,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase | |||||||
|     private readonly ApiController _apiController; |     private readonly ApiController _apiController; | ||||||
|     private readonly DalamudUtilService _dalamudUtil; |     private readonly DalamudUtilService _dalamudUtil; | ||||||
|     private readonly FileUploadManager _fileTransferManager; |     private readonly FileUploadManager _fileTransferManager; | ||||||
|     private readonly HashSet<CachedPlayer> _newVisiblePlayers = new(); |     private readonly HashSet<PairHandler> _newVisiblePlayers = new(); | ||||||
|     private readonly PairManager _pairManager; |     private readonly PairManager _pairManager; | ||||||
|     private CharacterData? _lastSentData; |     private CharacterData? _lastSentData; | ||||||
|  |  | ||||||
| @@ -40,7 +41,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase | |||||||
|                 Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value); |                 Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         Mediator.Subscribe<CachedPlayerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player)); |         Mediator.Subscribe<PairHandlerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player)); | ||||||
|         Mediator.Subscribe<ConnectedMessage>(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers())); |         Mediator.Subscribe<ConnectedMessage>(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ using MareSynchronos.API.Data.Extensions; | |||||||
| using MareSynchronos.API.Dto.Group; | using MareSynchronos.API.Dto.Group; | ||||||
| using MareSynchronos.API.Dto.User; | using MareSynchronos.API.Dto.User; | ||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
|  | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Services.ServerConfiguration; | using MareSynchronos.Services.ServerConfiguration; | ||||||
| using MareSynchronos.Utils; | using MareSynchronos.Utils; | ||||||
| @@ -14,7 +15,7 @@ namespace MareSynchronos.PlayerData.Pairs; | |||||||
|  |  | ||||||
| public class Pair | public class Pair | ||||||
| { | { | ||||||
|     private readonly CachedPlayerFactory _cachedPlayerFactory; |     private readonly PairHandlerFactory _cachedPlayerFactory; | ||||||
|     private readonly SemaphoreSlim _creationSemaphore = new(1); |     private readonly SemaphoreSlim _creationSemaphore = new(1); | ||||||
|     private readonly ILogger<Pair> _logger; |     private readonly ILogger<Pair> _logger; | ||||||
|     private readonly MareMediator _mediator; |     private readonly MareMediator _mediator; | ||||||
| @@ -22,7 +23,7 @@ public class Pair | |||||||
|     private CancellationTokenSource _applicationCts = new CancellationTokenSource(); |     private CancellationTokenSource _applicationCts = new CancellationTokenSource(); | ||||||
|     private OnlineUserIdentDto? _onlineUserIdentDto = null; |     private OnlineUserIdentDto? _onlineUserIdentDto = null; | ||||||
|  |  | ||||||
|     public Pair(ILogger<Pair> logger, CachedPlayerFactory cachedPlayerFactory, |     public Pair(ILogger<Pair> logger, PairHandlerFactory cachedPlayerFactory, | ||||||
|         MareMediator mediator, ServerConfigurationManager serverConfigurationManager) |         MareMediator mediator, ServerConfigurationManager serverConfigurationManager) | ||||||
|     { |     { | ||||||
|         _logger = logger; |         _logger = logger; | ||||||
| @@ -46,7 +47,7 @@ public class Pair | |||||||
|  |  | ||||||
|     public UserPairDto? UserPair { get; set; } |     public UserPairDto? UserPair { get; set; } | ||||||
|  |  | ||||||
|     private CachedPlayer? CachedPlayer { get; set; } |     private PairHandler? CachedPlayer { get; set; } | ||||||
|  |  | ||||||
|     public void AddContextMenu(GameObjectContextMenuOpenArgs args) |     public void AddContextMenu(GameObjectContextMenuOpenArgs args) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -69,8 +69,9 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|             collection.AddSingleton<UidDisplayHandler>(); |             collection.AddSingleton<UidDisplayHandler>(); | ||||||
|             collection.AddSingleton<GameObjectHandlerFactory>(); |             collection.AddSingleton<GameObjectHandlerFactory>(); | ||||||
|             collection.AddSingleton<FileDownloadManagerFactory>(); |             collection.AddSingleton<FileDownloadManagerFactory>(); | ||||||
|             collection.AddSingleton<CachedPlayerFactory>(); |             collection.AddSingleton<PairHandlerFactory>(); | ||||||
|             collection.AddSingleton<PairFactory>(); |             collection.AddSingleton<PairFactory>(); | ||||||
|  |             collection.AddSingleton<PluginWarningNotificationService>(); | ||||||
|             collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), |             collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), | ||||||
|                 clientState, objectTable, framework, gameGui, condition, gameData, |                 clientState, objectTable, framework, gameGui, condition, gameData, | ||||||
|                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); |                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); | ||||||
|   | |||||||
| @@ -65,6 +65,6 @@ public record ProfilePopoutToggle(Pair? Pair) : MessageBase; | |||||||
| public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase; | public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase; | ||||||
| public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase; | public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase; | ||||||
| public record RemoveWindowMessage(WindowMediatorSubscriberBase Window) : 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 | #pragma warning restore MA0048 // File name must match type name | ||||||
							
								
								
									
										68
									
								
								MareSynchronos/Services/PluginWarningNotificationService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								MareSynchronos/Services/PluginWarningNotificationService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<UserData, OptionalPluginWarning> _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<PlayerChanges> 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<string> 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)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,10 +1,133 @@ | |||||||
| using Dalamud.Game.ClientState.Objects.Types; | 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; | using System.Text.Json; | ||||||
|  |  | ||||||
| namespace MareSynchronos.Utils; | namespace MareSynchronos.Utils; | ||||||
|  |  | ||||||
| public static class VariousExtensions | 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<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(this CharacterData newData, CharacterData? oldData, ILogger logger, PairHandler cachedPlayer, bool forced, bool applyLastOnVisible) | ||||||
|  |     { | ||||||
|  |         oldData ??= new(); | ||||||
|  |         var charaDataToUpdate = new Dictionary<ObjectKind, HashSet<PlayerChanges>>(); | ||||||
|  |         foreach (ObjectKind objectKind in Enum.GetValues<ObjectKind>()) | ||||||
|  |         { | ||||||
|  |             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<ObjectKind, HashSet<PlayerChanges>> 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<T>(this T obj) |     public static T DeepClone<T>(this T obj) | ||||||
|     { |     { | ||||||
|         return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj))!; |         return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj))!; | ||||||
| @@ -19,23 +142,4 @@ public static class VariousExtensions | |||||||
|  |  | ||||||
|         return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address)->ObjectIndex; |         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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user
	 rootdarkarchon
					rootdarkarchon