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 ActionSubscriber<GameObject, RedrawType> _penumbraRedrawObject;
private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new(); private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new();
private readonly FuncSubscriber<string, PenumbraApiEc> _penumbraRemoveTemporaryCollection; private readonly FuncSubscriber<string, PenumbraApiEc> _penumbraRemoveTemporaryCollection;
private readonly FuncSubscriber<string, string, int, PenumbraApiEc> _penumbraRemoveTemporaryMod;
private readonly FuncSubscriber<string> _penumbraResolveModDir; private readonly FuncSubscriber<string> _penumbraResolveModDir;
private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths; private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths;
private readonly SemaphoreSlim _redrawSemaphore = new(2); private readonly SemaphoreSlim _redrawSemaphore = new(2);
@@ -92,7 +91,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
_penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi); _penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi);
_penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi); _penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi);
_penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi); _penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi);
_penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi);
_penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi); _penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi);
_penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi); _penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi);
_penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi); _penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi);
@@ -409,6 +407,31 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
public async Task PenumbraAssignTemporaryCollectionAsync(ILogger logger, string collName, int idx)
{
if (!CheckPenumbraApi()) return;
await _dalamudUtil.RunOnFrameworkThread(() =>
{
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, c: true);
logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign);
return collName;
}).ConfigureAwait(false);
}
public async Task<string> PenumbraCreateTemporaryCollectionAsync(ILogger logger, string uid)
{
if (!CheckPenumbraApi()) return string.Empty;
return await _dalamudUtil.RunOnFrameworkThread(() =>
{
var collName = "Mare_" + uid;
var retCreate = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
logger.LogTrace("Creating Temp Collection {collName}, Success: {ret}", collName, retCreate);
return collName;
}).ConfigureAwait(false);
}
public string PenumbraGetMetaManipulations() public string PenumbraGetMetaManipulations()
{ {
if (!CheckPenumbraApi()) return string.Empty; if (!CheckPenumbraApi()) return string.Empty;
@@ -449,6 +472,18 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
return await _dalamudUtil.RunOnFrameworkThread(() => _penumbraResolvePaths.Invoke(forward, reverse)).ConfigureAwait(false); return await _dalamudUtil.RunOnFrameworkThread(() => _penumbraResolvePaths.Invoke(forward, reverse)).ConfigureAwait(false);
} }
public async Task PenumbraSetManipulationDataAsync(ILogger logger, Guid applicationId, string collName, string manipulationData)
{
if (!CheckPenumbraApi()) return;
await _dalamudUtil.RunOnFrameworkThread(() =>
{
logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData);
var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collName, new Dictionary<string, string>(), manipulationData, 0);
logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd);
}).ConfigureAwait(false);
}
public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, string collName, Dictionary<string, string> modPaths) public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, string collName, Dictionary<string, string> modPaths)
{ {
if (!CheckPenumbraApi()) return; if (!CheckPenumbraApi()) return;
@@ -459,54 +494,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
{ {
logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value); logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value);
} }
var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Files", collName, 0);
logger.LogTrace("[{applicationId}] Removing prev mod files mod for {collName}, Success: {ret}", applicationId, collName, retRemove);
var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collName, modPaths, string.Empty, 0); var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collName, modPaths, string.Empty, 0);
logger.LogTrace("[{applicationId}] Setting temp files mod for {collName}, Success: {ret}", applicationId, collName, retAdd); logger.LogTrace("[{applicationId}] Setting temp files mod for {collName}, Success: {ret}", applicationId, collName, retAdd);
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
public async Task PenumbraSetManipulationDataAsync(ILogger logger, Guid applicationId, string collName, string manipulationData)
{
if (!CheckPenumbraApi()) return;
await _dalamudUtil.RunOnFrameworkThread(() =>
{
logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData);
var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Meta", collName, 0);
logger.LogTrace("[{applicationId}] Removing prev meta mod for {collName}, Success: {ret}", applicationId, collName, retRemove);
var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collName, new Dictionary<string, string>(), manipulationData, 0);
logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd);
}).ConfigureAwait(false);
}
public async Task<string> PenumbraCreateTemporaryCollection(ILogger logger, string uid)
{
if (!CheckPenumbraApi()) return string.Empty;
return await _dalamudUtil.RunOnFrameworkThread(() =>
{
var collName = "Mare_" + uid;
var retRemove = _penumbraRemoveTemporaryCollection.Invoke(collName);
logger.LogTrace("Removing Temp Collection {collName}, Success: {ret}", collName, retRemove);
var retCreate = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
logger.LogTrace("Creating Temp Collection {collName}, Success: {ret}", collName, retCreate);
return collName;
}).ConfigureAwait(false);
}
public async Task PenumbraAssignTemporaryCollection(ILogger logger, string collName, int idx)
{
if (!CheckPenumbraApi()) return;
await _dalamudUtil.RunOnFrameworkThread(() =>
{
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, c: true);
logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign);
return collName;
}).ConfigureAwait(false);
}
public void ToggleGposeQueueMode(bool on) public void ToggleGposeQueueMode(bool on)
{ {
_inGposeQueueMode = on; _inGposeQueueMode = on;

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(); var applicationId = Guid.NewGuid();
_ipcManager.ToggleGposeQueueMode(on: true); _ipcManager.ToggleGposeQueueMode(on: true);
await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false); await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false);
var coll = await _ipcManager.PenumbraCreateTemporaryCollection(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); var coll = await _ipcManager.PenumbraCreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false);
await _ipcManager.PenumbraAssignTemporaryCollection(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); await _ipcManager.PenumbraAssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false);
await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false);
await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false);
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false);

View File

@@ -7,12 +7,12 @@ namespace MareSynchronos.PlayerData.Factories;
public class PairFactory public class PairFactory
{ {
private readonly CachedPlayerFactory _cachedPlayerFactory; private readonly PairHandlerFactory _cachedPlayerFactory;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly MareMediator _mareMediator; private readonly MareMediator _mareMediator;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
public PairFactory(ILoggerFactory loggerFactory, CachedPlayerFactory cachedPlayerFactory, public PairFactory(ILoggerFactory loggerFactory, PairHandlerFactory cachedPlayerFactory,
MareMediator mareMediator, ServerConfigurationManager serverConfigurationManager) MareMediator mareMediator, ServerConfigurationManager serverConfigurationManager)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;

View File

@@ -1,7 +1,7 @@
using MareSynchronos.API.Dto.User; using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
using MareSynchronos.Interop; using MareSynchronos.Interop;
using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace MareSynchronos.PlayerData.Factories; namespace MareSynchronos.PlayerData.Factories;
public class CachedPlayerFactory public class PairHandlerFactory
{ {
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
@@ -19,28 +19,29 @@ public class CachedPlayerFactory
private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly MareConfigService _mareConfigService;
private readonly MareMediator _mareMediator; private readonly MareMediator _mareMediator;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
public CachedPlayerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
FileDownloadManagerFactory fileDownloadManagerFactory, MareConfigService mareConfigService, DalamudUtilService dalamudUtilService, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
IHostApplicationLifetime hostApplicationLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator) PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
FileCacheManager fileCacheManager, MareMediator mareMediator)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
_ipcManager = ipcManager; _ipcManager = ipcManager;
_fileDownloadManagerFactory = fileDownloadManagerFactory; _fileDownloadManagerFactory = fileDownloadManagerFactory;
_mareConfigService = mareConfigService;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_pluginWarningNotificationManager = pluginWarningNotificationManager;
_hostApplicationLifetime = hostApplicationLifetime; _hostApplicationLifetime = hostApplicationLifetime;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_mareMediator = mareMediator; _mareMediator = mareMediator;
} }
public CachedPlayer Create(OnlineUserIdentDto onlineUserIdentDto) public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto)
{ {
return new CachedPlayer(_loggerFactory.CreateLogger<CachedPlayer>(), onlineUserIdentDto, _gameObjectHandlerFactory, return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), onlineUserIdentDto, _gameObjectHandlerFactory,
_ipcManager, _fileDownloadManagerFactory.Create(), _mareConfigService, _dalamudUtilService, _hostApplicationLifetime, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
_fileCacheManager, _mareMediator); _fileCacheManager, _mareMediator);
} }
} }

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() public IntPtr CurrentAddress()
{ {
_dalamudUtil.EnsureIsOnFramework(); _dalamudUtil.EnsureIsOnFramework();
@@ -133,6 +145,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
{ {
Address = IntPtr.Zero; Address = IntPtr.Zero;
DrawObjectAddress = IntPtr.Zero; DrawObjectAddress = IntPtr.Zero;
_haltProcessing = false;
} }
public async Task<bool> IsBeingDrawnRunOnFrameworkAsync() public async Task<bool> IsBeingDrawnRunOnFrameworkAsync()
@@ -156,8 +169,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private unsafe void CheckAndUpdateObject() private unsafe void CheckAndUpdateObject()
{ {
if (_haltProcessing) return;
var prevAddr = Address; var prevAddr = Address;
var prevDrawObj = DrawObjectAddress; var prevDrawObj = DrawObjectAddress;
@@ -172,6 +183,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
DrawObjectAddress = IntPtr.Zero; DrawObjectAddress = IntPtr.Zero;
} }
if (_haltProcessing) return;
bool drawObjDiff = DrawObjectAddress != prevDrawObj; bool drawObjDiff = DrawObjectAddress != prevDrawObj;
bool addrDiff = Address != prevAddr; bool addrDiff = Address != prevAddr;

View File

@@ -1,12 +1,10 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Logging;
using Dalamud.Logging;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.User; using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
using MareSynchronos.Interop; using MareSynchronos.Interop;
using MareSynchronos.MareConfiguration;
using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Factories;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
@@ -17,9 +15,9 @@ using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
namespace MareSynchronos.PlayerData.Pairs; namespace MareSynchronos.PlayerData.Handlers;
public sealed class CachedPlayer : DisposableMediatorSubscriberBase public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly FileDownloadManager _downloadManager; private readonly FileDownloadManager _downloadManager;
@@ -27,33 +25,36 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly IHostApplicationLifetime _lifetime; private readonly IHostApplicationLifetime _lifetime;
private readonly OptionalPluginWarning _pluginWarnings; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private CancellationTokenSource? _applicationCancellationTokenSource = new(); private CancellationTokenSource? _applicationCancellationTokenSource = new();
private Guid _applicationId; private Guid _applicationId;
private Task? _applicationTask; private Task? _applicationTask;
private bool _applyLastReceivedDataOnVisible = false;
private CharacterData? _cachedData = null; private CharacterData? _cachedData = null;
private GameObjectHandler? _charaHandler; private GameObjectHandler? _charaHandler;
private CancellationTokenSource? _downloadCancellationTokenSource = new(); private CancellationTokenSource? _downloadCancellationTokenSource = new();
private CharacterData? _firstTimeInitData;
private string _lastGlamourerData = string.Empty; private string _lastGlamourerData = string.Empty;
private string _originalGlamourerData = string.Empty; private string _originalGlamourerData = string.Empty;
private string _penumbraCollection; private string _penumbraCollection;
private CancellationTokenSource _redrawCts = new(); private CancellationTokenSource _redrawCts = new();
public CachedPlayer(ILogger<CachedPlayer> logger, OnlineUserIdentDto onlineUser, public PairHandler(ILogger<PairHandler> logger, OnlineUserIdentDto onlineUser,
GameObjectHandlerFactory gameObjectHandlerFactory, GameObjectHandlerFactory gameObjectHandlerFactory,
IpcManager ipcManager, FileDownloadManager transferManager, MareConfigService mareConfigService, IpcManager ipcManager, FileDownloadManager transferManager,
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) PluginWarningNotificationService pluginWarningNotificationManager,
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator)
{ {
OnlineUser = onlineUser; OnlineUser = onlineUser;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
_ipcManager = ipcManager; _ipcManager = ipcManager;
_downloadManager = transferManager; _downloadManager = transferManager;
_pluginWarningNotificationManager = pluginWarningNotificationManager;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_lifetime = lifetime; _lifetime = lifetime;
_fileDbManager = fileDbManager; _fileDbManager = fileDbManager;
_penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollection(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult();
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) =>
@@ -63,7 +64,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
}); });
Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) =>
{ {
_penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollection(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult();
if (!IsVisible && _charaHandler != null) if (!IsVisible && _charaHandler != null)
{ {
PlayerName = string.Empty; PlayerName = string.Empty;
@@ -71,40 +72,23 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
_charaHandler = null; _charaHandler = null;
} }
}); });
_pluginWarnings ??= new()
{
ShownCustomizePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings,
ShownHeelsWarning = mareConfigService.Current.DisableOptionalPluginWarnings,
ShownPalettePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings,
ShownHonorificWarning = mareConfigService.Current.DisableOptionalPluginWarnings,
};
}
private enum PlayerChanges
{
Heels = 1,
Customize = 2,
Palette = 3,
Honorific = 4,
ModFiles = 5,
ModManip = 6,
Glamourer = 7
} }
public bool IsVisible { get; private set; } public bool IsVisible { get; private set; }
public OnlineUserIdentDto OnlineUser { get; private set; } public OnlineUserIdentDto OnlineUser { get; private set; }
public IntPtr PlayerCharacter => _charaHandler?.Address ?? IntPtr.Zero; public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero;
public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? IntPtr.Zero) == IntPtr.Zero public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero
? uint.MaxValue ? uint.MaxValue
: ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->ObjectID; : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->ObjectID;
public string? PlayerName { get; private set; } public string? PlayerName { get; private set; }
public string PlayerNameHash => OnlineUser.Ident; public string PlayerNameHash => OnlineUser.Ident;
public void ApplyCharacterData(CharacterData characterData, bool forced = false) public void ApplyCharacterData(API.Data.CharacterData characterData, bool forced = false)
{ {
if (_charaHandler == null) if (_charaHandler == null)
{ {
_firstTimeInitData = characterData; _applyLastReceivedDataOnVisible = true;
_cachedData = characterData;
return; return;
} }
@@ -126,16 +110,16 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
return; return;
} }
var charaDataToUpdate = CheckUpdatedData(_cachedData?.DeepClone() ?? new(), characterData, forced); var charaDataToUpdate = characterData.CheckUpdatedData(_cachedData?.DeepClone() ?? new(), Logger, this, forced, _applyLastReceivedDataOnVisible);
if (_charaHandler != null && _firstTimeInitData != null) if (_charaHandler != null && _applyLastReceivedDataOnVisible)
{ {
_firstTimeInitData = null; _applyLastReceivedDataOnVisible = false;
} }
if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges))
{ {
NotifyForMissingPlugins(playerChanges); _pluginWarningNotificationManager.NotifyForMissingPlugins(OnlineUser.User, PlayerName!, playerChanges);
} }
Logger.LogDebug("Downloading and applying character for {name}", this); Logger.LogDebug("Downloading and applying character for {name}", this);
@@ -146,8 +130,8 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
public override string ToString() public override string ToString()
{ {
return OnlineUser == null return OnlineUser == null
? (base.ToString() ?? string.Empty) ? base.ToString() ?? string.Empty
: (OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != IntPtr.Zero ? "HasChar" : "NoChar")); : OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar");
} }
internal void SetUploading(bool isUploading = true) internal void SetUploading(bool isUploading = true)
@@ -210,21 +194,9 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
} }
} }
private static void CheckForNameAndThrow(GameObjectHandler handler, string name)
{
if (!string.Equals(handler.Name, name, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Player name not equal to requested name, pointer invalid");
}
if (handler.Address == IntPtr.Zero)
{
throw new InvalidOperationException("Player pointer is zero, pointer invalid");
}
}
private async Task ApplyCustomizationDataAsync(Guid applicationId, KeyValuePair<ObjectKind, HashSet<PlayerChanges>> changes, CharacterData charaData, CancellationToken token) private async Task ApplyCustomizationDataAsync(Guid applicationId, KeyValuePair<ObjectKind, HashSet<PlayerChanges>> changes, CharacterData charaData, CancellationToken token)
{ {
if (PlayerCharacter == IntPtr.Zero) return; if (PlayerCharacter == nint.Zero) return;
var ptr = PlayerCharacter; var ptr = PlayerCharacter;
var handler = changes.Key switch var handler = changes.Key switch
@@ -238,7 +210,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
try try
{ {
if (handler.Address == IntPtr.Zero) if (handler.Address == nint.Zero)
{ {
return; return;
} }
@@ -291,105 +263,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
} }
} }
private Dictionary<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(CharacterData oldData, CharacterData newData, bool forced) private void DownloadAndApplyCharacter(API.Data.CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData)
{
var charaDataToUpdate = new Dictionary<ObjectKind, HashSet<PlayerChanges>>();
foreach (ObjectKind objectKind in Enum.GetValues<ObjectKind>())
{
charaDataToUpdate[objectKind] = new();
oldData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements);
newData.FileReplacements.TryGetValue(objectKind, out var newFileReplacements);
oldData.GlamourerData.TryGetValue(objectKind, out var existingGlamourerData);
newData.GlamourerData.TryGetValue(objectKind, out var newGlamourerData);
bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null;
bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null;
bool hasNewButNotOldGlamourerData = newGlamourerData != null && existingGlamourerData == null;
bool hasOldButNotNewGlamourerData = existingGlamourerData != null && newGlamourerData == null;
bool hasNewAndOldFileReplacements = newFileReplacements != null && existingFileReplacements != null;
bool hasNewAndOldGlamourerData = newGlamourerData != null && existingGlamourerData != null;
if (hasNewButNotOldFileReplacements || hasOldButNotNewFileReplacements || hasNewButNotOldGlamourerData || hasOldButNotNewGlamourerData)
{
Logger.LogDebug("Updating {object}/{kind} (Some new data arrived: NewButNotOldFiles:{hasNewButNotOldFileReplacements}," +
" OldButNotNewFiles:{hasOldButNotNewFileReplacements}, NewButNotOldGlam:{hasNewButNotOldGlamourerData}, OldButNotNewGlam:{hasOldButNotNewGlamourerData}) => {change}, {change2}",
this, objectKind, hasNewButNotOldFileReplacements, hasOldButNotNewFileReplacements, hasNewButNotOldGlamourerData, hasOldButNotNewGlamourerData, PlayerChanges.ModFiles, PlayerChanges.Glamourer);
charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles);
charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer);
}
else
{
if (hasNewAndOldFileReplacements)
{
bool listsAreEqual = oldData.FileReplacements[objectKind].SequenceEqual(newData.FileReplacements[objectKind], Data.FileReplacementDataComparer.Instance);
if (!listsAreEqual || _firstTimeInitData != null)
{
Logger.LogDebug("Updating {object}/{kind} (FileReplacements not equal) => {change}", this, objectKind, PlayerChanges.ModFiles);
charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles);
}
}
if (hasNewAndOldGlamourerData)
{
bool glamourerDataDifferent = !string.Equals(oldData.GlamourerData[objectKind], newData.GlamourerData[objectKind], StringComparison.Ordinal);
if (glamourerDataDifferent || forced)
{
Logger.LogDebug("Updating {object}/{kind} (Glamourer different) => {change}", this, objectKind, PlayerChanges.Glamourer);
charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer);
}
}
}
if (objectKind != ObjectKind.Player) continue;
bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal);
if (manipDataDifferent || _firstTimeInitData != null)
{
Logger.LogDebug("Updating {object}/{kind} (Diff manip data) => {change}", this, objectKind, PlayerChanges.ModManip);
charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip);
}
bool heelsOffsetDifferent = oldData.HeelsOffset != newData.HeelsOffset;
if (heelsOffsetDifferent || (forced && newData.HeelsOffset != 0))
{
Logger.LogDebug("Updating {object}/{kind} (Diff heels data) => {change}", this, objectKind, PlayerChanges.Heels);
charaDataToUpdate[objectKind].Add(PlayerChanges.Heels);
}
bool customizeDataDifferent = !string.Equals(oldData.CustomizePlusData, newData.CustomizePlusData, StringComparison.Ordinal);
if (customizeDataDifferent || (forced && !string.IsNullOrEmpty(newData.CustomizePlusData)))
{
Logger.LogDebug("Updating {object}/{kind} (Diff customize data) => {change}", this, objectKind, PlayerChanges.Customize);
charaDataToUpdate[objectKind].Add(PlayerChanges.Customize);
}
bool palettePlusDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal);
if (palettePlusDataDifferent || (forced && !string.IsNullOrEmpty(newData.PalettePlusData)))
{
Logger.LogDebug("Updating {object}/{kind} (Diff palette data) => {change}", this, objectKind, PlayerChanges.Palette);
charaDataToUpdate[objectKind].Add(PlayerChanges.Palette);
}
bool honorificDataDifferent = !string.Equals(oldData.HonorificData, newData.HonorificData, StringComparison.Ordinal);
if (honorificDataDifferent || (forced && !string.IsNullOrEmpty(newData.HonorificData)))
{
Logger.LogDebug("Updating {object}/{kind} (Diff honorific data) => {change}", this, objectKind, PlayerChanges.Honorific);
charaDataToUpdate[objectKind].Add(PlayerChanges.Honorific);
}
}
foreach (KeyValuePair<ObjectKind, HashSet<PlayerChanges>> data in charaDataToUpdate.ToList())
{
if (!data.Value.Any()) charaDataToUpdate.Remove(data.Key);
else charaDataToUpdate[data.Key] = data.Value.OrderByDescending(p => (int)p).ToHashSet();
}
return charaDataToUpdate;
}
private void DownloadAndApplyCharacter(CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData)
{ {
if (!updatedData.Any()) if (!updatedData.Any())
{ {
@@ -412,7 +286,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
int attempts = 0; int attempts = 0;
List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(charaData, out moddedPaths, downloadToken); List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(charaData, out moddedPaths, downloadToken);
while ((toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)) while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)
{ {
_downloadManager.CancelDownload(); _downloadManager.CancelDownload();
Logger.LogDebug("Downloading missing files for player {name}, {kind}", PlayerName, updatedData); Logger.LogDebug("Downloading missing files for player {name}, {kind}", PlayerName, updatedData);
@@ -494,15 +368,22 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
{ {
Logger.LogWarning(ex, "[{applicationId}] Cancelled, player turned null during application", _applicationId); Logger.LogWarning(ex, "[{applicationId}] Cancelled, player turned null during application", _applicationId);
IsVisible = false; IsVisible = false;
if (_cachedData == null) _applyLastReceivedDataOnVisible = true;
{
_firstTimeInitData = charaData.DeepClone();
}
} }
catch (Exception ex) catch (Exception ex)
{
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); Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId);
} }
}
}, token); }, token);
}, downloadToken); }, downloadToken);
} }
@@ -518,21 +399,21 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
Logger.LogDebug("One-Time Initialized {this}", this); Logger.LogDebug("One-Time Initialized {this}", this);
} }
if (_charaHandler?.Address != IntPtr.Zero && !IsVisible) if (_charaHandler?.Address != nint.Zero && !IsVisible)
{ {
IsVisible = true; IsVisible = true;
Mediator.Publish(new CachedPlayerVisibleMessage(this)); Mediator.Publish(new PairHandlerVisibleMessage(this));
Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible);
if (_firstTimeInitData != null || _cachedData != null) if (_applyLastReceivedDataOnVisible && _cachedData != null)
{ {
Task.Run(async () => Task.Run(async () =>
{ {
_lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false); _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false);
ApplyCharacterData(_firstTimeInitData ?? _cachedData!, true); ApplyCharacterData(_cachedData!, true);
}); });
} }
} }
else if (_charaHandler?.Address == IntPtr.Zero && IsVisible) else if (_charaHandler?.Address == nint.Zero && IsVisible)
{ {
IsVisible = false; IsVisible = false;
Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible);
@@ -566,7 +447,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
await _ipcManager.HonorificSetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false); await _ipcManager.HonorificSetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false);
}); });
_ipcManager.PenumbraAssignTemporaryCollection(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult(); _ipcManager.PenumbraAssignTemporaryCollectionAsync(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult();
_downloadManager.Initialize(); _downloadManager.Initialize();
} }
@@ -593,44 +474,10 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
}, token); }, token);
} }
private void NotifyForMissingPlugins(HashSet<PlayerChanges> changes)
{
List<string> missingPluginsForData = new();
if (changes.Contains(PlayerChanges.Heels) && !_pluginWarnings.ShownHeelsWarning && !_ipcManager.CheckHeelsApi())
{
missingPluginsForData.Add("Heels");
_pluginWarnings.ShownHeelsWarning = true;
}
if (changes.Contains(PlayerChanges.Customize) && !_pluginWarnings.ShownCustomizePlusWarning && !_ipcManager.CheckCustomizePlusApi())
{
missingPluginsForData.Add("Customize+");
_pluginWarnings.ShownCustomizePlusWarning = true;
}
if (changes.Contains(PlayerChanges.Palette) && !_pluginWarnings.ShownPalettePlusWarning && !_ipcManager.CheckPalettePlusApi())
{
missingPluginsForData.Add("Palette+");
_pluginWarnings.ShownPalettePlusWarning = true;
}
if (changes.Contains(PlayerChanges.Honorific) && !_pluginWarnings.ShownHonorificWarning && !_ipcManager.CheckHonorificApi())
{
missingPluginsForData.Add("Honorific");
_pluginWarnings.ShownHonorificWarning = true;
}
if (missingPluginsForData.Any())
{
Mediator.Publish(new NotificationMessage("Missing plugins for " + PlayerName,
$"Received data for {PlayerName} that contained information for plugins you have not installed. Install {string.Join(", ", missingPluginsForData)} to experience their character fully.",
NotificationType.Warning, 10000));
}
}
private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId) private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId)
{ {
nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident); nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident);
if (address == IntPtr.Zero) return; if (address == nint.Zero) return;
var cancelToken = new CancellationTokenSource(); var cancelToken = new CancellationTokenSource();
cancelToken.CancelAfter(TimeSpan.FromSeconds(60)); cancelToken.CancelAfter(TimeSpan.FromSeconds(60));
@@ -640,26 +487,26 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
if (objectKind == ObjectKind.Player) if (objectKind == ObjectKind.Player)
{ {
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, false).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, false).ConfigureAwait(false);
CheckForNameAndThrow(tempHandler, name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData); Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData);
await _ipcManager.GlamourerApplyCustomizationAndEquipmentAsync(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); await _ipcManager.GlamourerApplyCustomizationAndEquipmentAsync(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
CheckForNameAndThrow(tempHandler, name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false); await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false);
CheckForNameAndThrow(tempHandler, name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
await _ipcManager.CustomizePlusRevertAsync(address).ConfigureAwait(false); await _ipcManager.CustomizePlusRevertAsync(address).ConfigureAwait(false);
CheckForNameAndThrow(tempHandler, name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Palette+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Palette+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
await _ipcManager.PalettePlusRemovePaletteAsync(address).ConfigureAwait(false); await _ipcManager.PalettePlusRemovePaletteAsync(address).ConfigureAwait(false);
CheckForNameAndThrow(tempHandler, name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false); await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false);
} }
else if (objectKind == ObjectKind.MinionOrMount) else if (objectKind == ObjectKind.MinionOrMount)
{ {
var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false); var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false);
if (minionOrMount != IntPtr.Zero) if (minionOrMount != nint.Zero)
{ {
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false);
@@ -668,7 +515,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
else if (objectKind == ObjectKind.Pet) else if (objectKind == ObjectKind.Pet)
{ {
var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false);
if (pet != IntPtr.Zero) if (pet != nint.Zero)
{ {
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false);
@@ -677,7 +524,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
else if (objectKind == ObjectKind.Companion) else if (objectKind == ObjectKind.Companion)
{ {
var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false);
if (companion != IntPtr.Zero) if (companion != nint.Zero)
{ {
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false);

View File

@@ -1,4 +1,5 @@
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
@@ -13,7 +14,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly FileUploadManager _fileTransferManager; private readonly FileUploadManager _fileTransferManager;
private readonly HashSet<CachedPlayer> _newVisiblePlayers = new(); private readonly HashSet<PairHandler> _newVisiblePlayers = new();
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private CharacterData? _lastSentData; private CharacterData? _lastSentData;
@@ -40,7 +41,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase
Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value); Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value);
} }
}); });
Mediator.Subscribe<CachedPlayerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player)); Mediator.Subscribe<PairHandlerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player));
Mediator.Subscribe<ConnectedMessage>(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers())); Mediator.Subscribe<ConnectedMessage>(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers()));
} }

View File

@@ -5,6 +5,7 @@ using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User; using MareSynchronos.API.Dto.User;
using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Factories;
using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils; using MareSynchronos.Utils;
@@ -14,7 +15,7 @@ namespace MareSynchronos.PlayerData.Pairs;
public class Pair public class Pair
{ {
private readonly CachedPlayerFactory _cachedPlayerFactory; private readonly PairHandlerFactory _cachedPlayerFactory;
private readonly SemaphoreSlim _creationSemaphore = new(1); private readonly SemaphoreSlim _creationSemaphore = new(1);
private readonly ILogger<Pair> _logger; private readonly ILogger<Pair> _logger;
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
@@ -22,7 +23,7 @@ public class Pair
private CancellationTokenSource _applicationCts = new CancellationTokenSource(); private CancellationTokenSource _applicationCts = new CancellationTokenSource();
private OnlineUserIdentDto? _onlineUserIdentDto = null; private OnlineUserIdentDto? _onlineUserIdentDto = null;
public Pair(ILogger<Pair> logger, CachedPlayerFactory cachedPlayerFactory, public Pair(ILogger<Pair> logger, PairHandlerFactory cachedPlayerFactory,
MareMediator mediator, ServerConfigurationManager serverConfigurationManager) MareMediator mediator, ServerConfigurationManager serverConfigurationManager)
{ {
_logger = logger; _logger = logger;
@@ -46,7 +47,7 @@ public class Pair
public UserPairDto? UserPair { get; set; } public UserPairDto? UserPair { get; set; }
private CachedPlayer? CachedPlayer { get; set; } private PairHandler? CachedPlayer { get; set; }
public void AddContextMenu(GameObjectContextMenuOpenArgs args) public void AddContextMenu(GameObjectContextMenuOpenArgs args)
{ {

View File

@@ -69,8 +69,9 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<UidDisplayHandler>(); collection.AddSingleton<UidDisplayHandler>();
collection.AddSingleton<GameObjectHandlerFactory>(); collection.AddSingleton<GameObjectHandlerFactory>();
collection.AddSingleton<FileDownloadManagerFactory>(); collection.AddSingleton<FileDownloadManagerFactory>();
collection.AddSingleton<CachedPlayerFactory>(); collection.AddSingleton<PairHandlerFactory>();
collection.AddSingleton<PairFactory>(); collection.AddSingleton<PairFactory>();
collection.AddSingleton<PluginWarningNotificationService>();
collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(),
clientState, objectTable, framework, gameGui, condition, gameData, clientState, objectTable, framework, gameGui, condition, gameData,
s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>()));

View File

@@ -65,6 +65,6 @@ public record ProfilePopoutToggle(Pair? Pair) : MessageBase;
public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase; public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase;
public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase; public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase;
public record RemoveWindowMessage(WindowMediatorSubscriberBase Window) : MessageBase; public record RemoveWindowMessage(WindowMediatorSubscriberBase Window) : MessageBase;
public record CachedPlayerVisibleMessage(CachedPlayer Player) : MessageBase; public record PairHandlerVisibleMessage(PairHandler Player) : MessageBase;
#pragma warning restore MA0048 // File name must match type name #pragma warning restore MA0048 // File name must match type name

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,25 +1,15 @@
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.PlayerData.Pairs;
using Microsoft.Extensions.Logging;
using System.Text.Json; using System.Text.Json;
namespace MareSynchronos.Utils; namespace MareSynchronos.Utils;
public static class VariousExtensions public static class VariousExtensions
{ {
public static T DeepClone<T>(this T obj)
{
return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj))!;
}
public static unsafe int? ObjectTableIndex(this GameObject? gameObject)
{
if (gameObject == null || gameObject.Address == IntPtr.Zero)
{
return null;
}
return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address)->ObjectIndex;
}
public static void CancelDispose(this CancellationTokenSource? cts) public static void CancelDispose(this CancellationTokenSource? cts)
{ {
try try
@@ -38,4 +28,118 @@ public static class VariousExtensions
cts.CancelDispose(); cts.CancelDispose();
return new CancellationTokenSource(); 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))!;
}
public static unsafe int? ObjectTableIndex(this GameObject? gameObject)
{
if (gameObject == null || gameObject.Address == IntPtr.Zero)
{
return null;
}
return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address)->ObjectIndex;
}
} }