cleanup and fix application issues

This commit is contained in:
rootdarkarchon
2023-05-09 03:55:30 +02:00
parent 9cdd991fc2
commit de7e9d7293
13 changed files with 338 additions and 298 deletions

View File

@@ -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;

View 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
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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()));
}

View File

@@ -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)
{

View File

@@ -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>()));

View File

@@ -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

View 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));
}
}
}

View File

@@ -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();
}
}
}