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 ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new(); | ||||
|     private readonly FuncSubscriber<string, PenumbraApiEc> _penumbraRemoveTemporaryCollection; | ||||
|     private readonly FuncSubscriber<string, string, int, PenumbraApiEc> _penumbraRemoveTemporaryMod; | ||||
|     private readonly FuncSubscriber<string> _penumbraResolveModDir; | ||||
|     private readonly FuncSubscriber<string[], string[], (string[], string[][])> _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<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() | ||||
|     { | ||||
|         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<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) | ||||
|     { | ||||
|         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<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) | ||||
|     { | ||||
|         _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(); | ||||
|                 _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); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<CachedPlayer>(), onlineUserIdentDto, _gameObjectHandlerFactory, | ||||
|             _ipcManager, _fileDownloadManagerFactory.Create(), _mareConfigService, _dalamudUtilService, _hostApplicationLifetime, | ||||
|         return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), onlineUserIdentDto, _gameObjectHandlerFactory, | ||||
|             _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, | ||||
|             _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() | ||||
|     { | ||||
|         _dalamudUtil.EnsureIsOnFramework(); | ||||
| @@ -133,6 +145,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase | ||||
|     { | ||||
|         Address = IntPtr.Zero; | ||||
|         DrawObjectAddress = IntPtr.Zero; | ||||
|         _haltProcessing = false; | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> 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; | ||||
|  | ||||
|   | ||||
| @@ -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<CachedPlayer> 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<PairHandler> 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<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); | ||||
|         Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => | ||||
| @@ -63,7 +64,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | ||||
|         }); | ||||
|         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) | ||||
|             { | ||||
|                 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<ObjectKind, HashSet<PlayerChanges>> 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<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(CharacterData oldData, CharacterData newData, bool forced) | ||||
|     { | ||||
|         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) | ||||
|     private void DownloadAndApplyCharacter(API.Data.CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData) | ||||
|     { | ||||
|         if (!updatedData.Any()) | ||||
|         { | ||||
| @@ -412,7 +286,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase | ||||
|                 int attempts = 0; | ||||
|                 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(); | ||||
|                     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<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) | ||||
|     { | ||||
|         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); | ||||
| @@ -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<CachedPlayer> _newVisiblePlayers = new(); | ||||
|     private readonly HashSet<PairHandler> _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<CachedPlayerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player)); | ||||
|         Mediator.Subscribe<PairHandlerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player)); | ||||
|         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.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<Pair> _logger; | ||||
|     private readonly MareMediator _mediator; | ||||
| @@ -22,7 +23,7 @@ public class Pair | ||||
|     private CancellationTokenSource _applicationCts = new CancellationTokenSource(); | ||||
|     private OnlineUserIdentDto? _onlineUserIdentDto = null; | ||||
|  | ||||
|     public Pair(ILogger<Pair> logger, CachedPlayerFactory cachedPlayerFactory, | ||||
|     public Pair(ILogger<Pair> 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) | ||||
|     { | ||||
|   | ||||
| @@ -69,8 +69,9 @@ public sealed class Plugin : IDalamudPlugin | ||||
|             collection.AddSingleton<UidDisplayHandler>(); | ||||
|             collection.AddSingleton<GameObjectHandlerFactory>(); | ||||
|             collection.AddSingleton<FileDownloadManagerFactory>(); | ||||
|             collection.AddSingleton<CachedPlayerFactory>(); | ||||
|             collection.AddSingleton<PairHandlerFactory>(); | ||||
|             collection.AddSingleton<PairFactory>(); | ||||
|             collection.AddSingleton<PluginWarningNotificationService>(); | ||||
|             collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), | ||||
|                 clientState, objectTable, framework, gameGui, condition, gameData, | ||||
|                 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 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 | ||||
							
								
								
									
										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 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<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) | ||||
|     { | ||||
|         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; | ||||
|     } | ||||
|  | ||||
|     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