Revert gpose actors on plugin unload or when NoSnap triggers

This commit is contained in:
Loporrit
2025-07-25 18:11:16 +00:00
parent 9fd390caab
commit 924a3803d9
7 changed files with 165 additions and 7 deletions

View File

@@ -204,6 +204,13 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC
} }
} }
public void RevertNow(ILogger logger, Guid applicationId, int objectIndex)
{
if ((!APIAvailable) || _dalamudUtil.IsZoning) return;
logger.LogTrace("[{applicationId}] Immediately reverting object index {objId}", applicationId, objectIndex);
_glamourerRevert.Invoke(objectIndex, LockCode);
}
public async Task RevertByNameAsync(ILogger logger, string name, Guid applicationId) public async Task RevertByNameAsync(ILogger logger, string name, Guid applicationId)
{ {
if ((!APIAvailable) || _dalamudUtil.IsZoning) return; if ((!APIAvailable) || _dalamudUtil.IsZoning) return;

View File

@@ -271,6 +271,13 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
} }
} }
public void RedrawNow(ILogger logger, Guid applicationId, int objectIndex)
{
if (!APIAvailable || _dalamudUtil.IsZoning) return;
logger.LogTrace("[{applicationId}] Immediately redrawing object index {objId}", applicationId, objectIndex);
_penumbraRedraw.Invoke(objectIndex);
}
public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collId) public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collId)
{ {
if (!APIAvailable) return; if (!APIAvailable) return;

View File

@@ -27,13 +27,14 @@ public class PairHandlerFactory
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly PairAnalyzerFactory _pairAnalyzerFactory; private readonly PairAnalyzerFactory _pairAnalyzerFactory;
private readonly VisibilityService _visibilityService; private readonly VisibilityService _visibilityService;
private readonly NoSnapService _noSnapService;
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService, FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory, ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
MareConfigService configService, VisibilityService visibilityService) MareConfigService configService, VisibilityService visibilityService, NoSnapService noSnapService)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
@@ -49,12 +50,13 @@ public class PairHandlerFactory
_pairAnalyzerFactory = pairAnalyzerFactory; _pairAnalyzerFactory = pairAnalyzerFactory;
_configService = configService; _configService = configService;
_visibilityService = visibilityService; _visibilityService = visibilityService;
_noSnapService = noSnapService;
} }
public PairHandler Create(Pair pair) public PairHandler Create(Pair pair)
{ {
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory, return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService); _fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService, _noSnapService);
} }
} }

View File

@@ -33,6 +33,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private readonly ServerConfigurationManager _serverConfigManager; private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly VisibilityService _visibilityService; private readonly VisibilityService _visibilityService;
private readonly NoSnapService _noSnapService;
private CancellationTokenSource? _applicationCancellationTokenSource = new(); private CancellationTokenSource? _applicationCancellationTokenSource = new();
private Guid _applicationId; private Guid _applicationId;
private Task? _applicationTask; private Task? _applicationTask;
@@ -55,7 +56,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
FileCacheManager fileDbManager, MareMediator mediator, FileCacheManager fileDbManager, MareMediator mediator,
PlayerPerformanceService playerPerformanceService, PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager, ServerConfigurationManager serverConfigManager,
MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator) MareConfigService configService, VisibilityService visibilityService,
NoSnapService noSnapService) : base(logger, mediator)
{ {
Pair = pair; Pair = pair;
PairAnalyzer = pairAnalyzer; PairAnalyzer = pairAnalyzer;
@@ -69,6 +71,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_serverConfigManager = serverConfigManager; _serverConfigManager = serverConfigManager;
_configService = configService; _configService = configService;
_visibilityService = visibilityService; _visibilityService = visibilityService;
_noSnapService = noSnapService;
_visibilityService.StartTracking(Pair.Ident); _visibilityService.StartTracking(Pair.Ident);
@@ -317,6 +320,24 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
}); });
} }
private void RegisterGposeClones()
{
var name = PlayerName;
if (name == null)
return;
_ = _dalamudUtil.RunOnFrameworkThread(() =>
{
foreach (var actor in _dalamudUtil.GetGposeCharactersFromObjectTable())
{
if (actor == null) continue;
var gposeName = actor.Name.TextValue;
if (!name.Equals(gposeName, StringComparison.Ordinal))
continue;
_noSnapService.AddGposer(actor.ObjectIndex);
}
});
}
private async Task UndoApplicationAsync(Guid applicationId = default) private async Task UndoApplicationAsync(Guid applicationId = default)
{ {
Logger.LogDebug($"Undoing application of {Pair.UserPair}"); Logger.LogDebug($"Undoing application of {Pair.UserPair}");
@@ -333,6 +354,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).ConfigureAwait(false); await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).ConfigureAwait(false);
_penumbraCollection = Guid.Empty; _penumbraCollection = Guid.Empty;
RegisterGposeClones();
} }
if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name))

View File

@@ -13,18 +13,20 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly NoSnapService _noSnapService;
private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal); private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal);
public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData; public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData;
public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator, public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator,
GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService, GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService,
IpcManager ipcManager) IpcManager ipcManager, NoSnapService noSnapService)
: base(logger, mediator) : base(logger, mediator)
{ {
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_ipcManager = ipcManager; _ipcManager = ipcManager;
_noSnapService = noSnapService;
mediator.Subscribe<GposeEndMessage>(this, msg => mediator.Subscribe<GposeEndMessage>(this, msg =>
{ {
foreach (var chara in _handledCharaData) foreach (var chara in _handledCharaData)
@@ -90,13 +92,18 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
{ {
if (handled == null) return false; if (handled == null) return false;
_handledCharaData.Remove(handled.Name); _handledCharaData.Remove(handled.Name);
await _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus)).ConfigureAwait(false); await _dalamudUtilService.RunOnFrameworkThread(async () =>
{
RemoveGposer(handled);
await RevertChara(handled.Name, handled.CustomizePlus).ConfigureAwait(false);
}).ConfigureAwait(false);
return true; return true;
} }
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry) internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
{ {
_handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry); _handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry);
_ = _dalamudUtilService.RunOnFrameworkThread(() => AddGposer(handledCharaDataEntry));
} }
public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData) public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData)
@@ -127,4 +134,23 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
if (handler.Address == nint.Zero) return null; if (handler.Address == nint.Zero) return null;
return handler; return handler;
} }
private int GetGposerObjectIndex(string name)
{
return _dalamudUtilService.GetGposeCharacterFromObjectTableByName(name, _dalamudUtilService.IsInGpose)?.ObjectIndex ?? -1;
}
private void AddGposer(HandledCharaDataEntry handled)
{
int objectIndex = GetGposerObjectIndex(handled.Name);
if (objectIndex > 0)
_noSnapService.AddGposer(objectIndex);
}
private void RemoveGposer(HandledCharaDataEntry handled)
{
int objectIndex = GetGposerObjectIndex(handled.Name);
if (objectIndex > 0)
_noSnapService.RemoveGposer(objectIndex);
}
} }

View File

@@ -1,4 +1,5 @@
using Dalamud.Plugin; using Dalamud.Plugin;
using MareSynchronos.Interop.Ipc;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -15,16 +16,23 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
["Snappy"] = false, ["Snappy"] = false,
["Meddle.Plugin"] = false ["Meddle.Plugin"] = false
}; };
private static readonly HashSet<int> _gposers = new();
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly DalamudUtilService _dalamudUtilService;
private readonly IpcManager _ipcManager;
public static bool AnyLoaded { get; private set; } = false; public static bool AnyLoaded { get; private set; } = false;
public MareMediator Mediator { get; init; } public MareMediator Mediator { get; init; }
public NoSnapService(ILogger<NoSnapService> logger, IDalamudPluginInterface pi, MareMediator mediator) public NoSnapService(ILogger<NoSnapService> logger, IDalamudPluginInterface pi, MareMediator mediator,
IHostApplicationLifetime hostApplicationLifetime, DalamudUtilService dalamudUtilService, IpcManager ipcManager)
{ {
_logger = logger; _logger = logger;
Mediator = mediator; Mediator = mediator;
_hostApplicationLifetime = hostApplicationLifetime;
_dalamudUtilService = dalamudUtilService;
_ipcManager = ipcManager;
foreach (var pluginName in _listOfPlugins.Keys) foreach (var pluginName in _listOfPlugins.Keys)
{ {
var plugin = pi.InstalledPlugins.FirstOrDefault(p => p.InternalName.Equals(pluginName, StringComparison.Ordinal)); var plugin = pi.InstalledPlugins.FirstOrDefault(p => p.InternalName.Equals(pluginName, StringComparison.Ordinal));
@@ -38,9 +46,80 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
}); });
} }
Mediator.Subscribe<GposeEndMessage>(this, msg => ClearGposeList());
Mediator.Subscribe<CutsceneEndMessage>(this, msg => ClearGposeList());
Update(); Update();
} }
public void AddGposer(int objectIndex)
{
if (AnyLoaded || _hostApplicationLifetime.ApplicationStopping.IsCancellationRequested)
{
_logger.LogInformation("Immediately reverting object index {id}", objectIndex);
try
{
Guid applicationId = Guid.NewGuid();
_ipcManager.Glamourer.RevertNow(_logger, applicationId, objectIndex);
_ipcManager.Penumbra.RedrawNow(_logger, applicationId, objectIndex);
}
catch { }
return;
}
_logger.LogInformation("Registering gposer object index {id}", objectIndex);
lock (_gposers)
_gposers.Add(objectIndex);
}
public void RemoveGposer(int objectIndex)
{
_logger.LogInformation("Un-registering gposer object index {id}", objectIndex);
lock (_gposers)
_gposers.Remove(objectIndex);
}
private void ClearGposeList()
{
if (_gposers.Count > 0)
_logger.LogInformation("Clearing gposer list");
lock (_gposers)
_gposers.Clear();
}
private void RevertGposers()
{
List<int>? gposersList = null;
lock (_gposers)
{
if (_gposers.Count > 0)
{
_logger.LogInformation("Reverting gposers");
gposersList = _gposers.ToList();
_gposers.Clear();
}
}
if (gposersList == null)
return;
_dalamudUtilService.RunOnFrameworkThread(() =>
{
Guid applicationId = Guid.NewGuid();
foreach (var gposer in gposersList)
{
try
{
_ipcManager.Glamourer.RevertNow(_logger, applicationId, gposer);
_ipcManager.Penumbra.RedrawNow(_logger, applicationId, gposer);
}
catch { }
}
}).GetAwaiter().GetResult();
}
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.CompletedTask;
@@ -48,6 +127,7 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
RevertGposers();
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -63,6 +143,7 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
if (AnyLoaded) if (AnyLoaded)
{ {
RevertGposers();
var pluginList = string.Join(", ", _listOfPlugins.Where(p => p.Value).Select(p => p.Key)); var pluginList = string.Join(", ", _listOfPlugins.Where(p => p.Value).Select(p => p.Key));
Mediator.Publish(new NotificationMessage("Incompatible plugin loaded", $"Synced player appearances will not apply until incompatible plugins are disabled: {pluginList}.", Mediator.Publish(new NotificationMessage("Incompatible plugin loaded", $"Synced player appearances will not apply until incompatible plugins are disabled: {pluginList}.",
NotificationType.Error)); NotificationType.Error));

View File

@@ -75,6 +75,19 @@ public class PluginWatcherService : MediatorSubscriberBase
Logger.LogError(e, "PluginWatcherService exception"); Logger.LogError(e, "PluginWatcherService exception");
} }
}); });
// Continue scanning plugins during gpose as well
Mediator.Subscribe<CutsceneFrameworkUpdateMessage>(this, (_) =>
{
try
{
Update();
}
catch (Exception e)
{
Logger.LogError(e, "PluginWatcherService exception");
}
});
} }
private void Update() private void Update()