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)
{
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)
{
if (!APIAvailable) return;

View File

@@ -27,13 +27,14 @@ public class PairHandlerFactory
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly PairAnalyzerFactory _pairAnalyzerFactory;
private readonly VisibilityService _visibilityService;
private readonly NoSnapService _noSnapService;
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
MareConfigService configService, VisibilityService visibilityService)
MareConfigService configService, VisibilityService visibilityService, NoSnapService noSnapService)
{
_loggerFactory = loggerFactory;
_gameObjectHandlerFactory = gameObjectHandlerFactory;
@@ -49,12 +50,13 @@ public class PairHandlerFactory
_pairAnalyzerFactory = pairAnalyzerFactory;
_configService = configService;
_visibilityService = visibilityService;
_noSnapService = noSnapService;
}
public PairHandler Create(Pair pair)
{
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
_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 PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly VisibilityService _visibilityService;
private readonly NoSnapService _noSnapService;
private CancellationTokenSource? _applicationCancellationTokenSource = new();
private Guid _applicationId;
private Task? _applicationTask;
@@ -55,7 +56,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
FileCacheManager fileDbManager, MareMediator mediator,
PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager,
MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator)
MareConfigService configService, VisibilityService visibilityService,
NoSnapService noSnapService) : base(logger, mediator)
{
Pair = pair;
PairAnalyzer = pairAnalyzer;
@@ -69,6 +71,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_serverConfigManager = serverConfigManager;
_configService = configService;
_visibilityService = visibilityService;
_noSnapService = noSnapService;
_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)
{
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);
_penumbraCollection = Guid.Empty;
RegisterGposeClones();
}
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 DalamudUtilService _dalamudUtilService;
private readonly IpcManager _ipcManager;
private readonly NoSnapService _noSnapService;
private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal);
public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData;
public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator,
GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService,
IpcManager ipcManager)
IpcManager ipcManager, NoSnapService noSnapService)
: base(logger, mediator)
{
_gameObjectHandlerFactory = gameObjectHandlerFactory;
_dalamudUtilService = dalamudUtilService;
_ipcManager = ipcManager;
_noSnapService = noSnapService;
mediator.Subscribe<GposeEndMessage>(this, msg =>
{
foreach (var chara in _handledCharaData)
@@ -90,13 +92,18 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
{
if (handled == null) return false;
_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;
}
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
{
_handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry);
_ = _dalamudUtilService.RunOnFrameworkThread(() => AddGposer(handledCharaDataEntry));
}
public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData)
@@ -127,4 +134,23 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
if (handler.Address == nint.Zero) return null;
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 MareSynchronos.Interop.Ipc;
using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Hosting;
@@ -15,16 +16,23 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
["Snappy"] = 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 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;
Mediator = mediator;
_hostApplicationLifetime = hostApplicationLifetime;
_dalamudUtilService = dalamudUtilService;
_ipcManager = ipcManager;
foreach (var pluginName in _listOfPlugins.Keys)
{
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();
}
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)
{
return Task.CompletedTask;
@@ -48,6 +127,7 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
public Task StopAsync(CancellationToken cancellationToken)
{
RevertGposers();
return Task.CompletedTask;
}
@@ -63,6 +143,7 @@ public class NoSnapService : IHostedService, IMediatorSubscriber
if (AnyLoaded)
{
RevertGposers();
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}.",
NotificationType.Error));

View File

@@ -75,6 +75,19 @@ public class PluginWatcherService : MediatorSubscriberBase
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()