Initial
This commit is contained in:
7
MareSynchronos/Interop/Ipc/IIpcCaller.cs
Normal file
7
MareSynchronos/Interop/Ipc/IIpcCaller.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public interface IIpcCaller : IDisposable
|
||||
{
|
||||
bool APIAvailable { get; }
|
||||
void CheckAPI();
|
||||
}
|
147
MareSynchronos/Interop/Ipc/IpcCallerBrio.cs
Normal file
147
MareSynchronos/Interop/Ipc/IpcCallerBrio.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.API.Dto.CharaData;
|
||||
using MareSynchronos.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Numerics;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerBrio : IIpcCaller
|
||||
{
|
||||
private readonly ILogger<IpcCallerBrio> _logger;
|
||||
private readonly DalamudUtilService _dalamudUtilService;
|
||||
private readonly ICallGateSubscriber<(int, int)> _brioApiVersion;
|
||||
|
||||
private readonly ICallGateSubscriber<bool, bool, bool, Task<IGameObject>> _brioSpawnActorAsync;
|
||||
private readonly ICallGateSubscriber<IGameObject, bool> _brioDespawnActor;
|
||||
private readonly ICallGateSubscriber<IGameObject, Vector3?, Quaternion?, Vector3?, bool, bool> _brioSetModelTransform;
|
||||
private readonly ICallGateSubscriber<IGameObject, (Vector3?, Quaternion?, Vector3?)> _brioGetModelTransform;
|
||||
private readonly ICallGateSubscriber<IGameObject, string> _brioGetPoseAsJson;
|
||||
private readonly ICallGateSubscriber<IGameObject, string, bool, bool> _brioSetPoseFromJson;
|
||||
private readonly ICallGateSubscriber<IGameObject, bool> _brioFreezeActor;
|
||||
private readonly ICallGateSubscriber<bool> _brioFreezePhysics;
|
||||
|
||||
|
||||
public bool APIAvailable { get; private set; }
|
||||
|
||||
public IpcCallerBrio(ILogger<IpcCallerBrio> logger, IDalamudPluginInterface dalamudPluginInterface,
|
||||
DalamudUtilService dalamudUtilService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
|
||||
_brioApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("Brio.ApiVersion");
|
||||
_brioSpawnActorAsync = dalamudPluginInterface.GetIpcSubscriber<bool, bool, bool, Task<IGameObject>>("Brio.Actor.SpawnExAsync");
|
||||
_brioDespawnActor = dalamudPluginInterface.GetIpcSubscriber<IGameObject, bool>("Brio.Actor.Despawn");
|
||||
_brioSetModelTransform = dalamudPluginInterface.GetIpcSubscriber<IGameObject, Vector3?, Quaternion?, Vector3?, bool, bool>("Brio.Actor.SetModelTransform");
|
||||
_brioGetModelTransform = dalamudPluginInterface.GetIpcSubscriber<IGameObject, (Vector3?, Quaternion?, Vector3?)>("Brio.Actor.GetModelTransform");
|
||||
_brioGetPoseAsJson = dalamudPluginInterface.GetIpcSubscriber<IGameObject, string>("Brio.Actor.Pose.GetPoseAsJson");
|
||||
_brioSetPoseFromJson = dalamudPluginInterface.GetIpcSubscriber<IGameObject, string, bool, bool>("Brio.Actor.Pose.LoadFromJson");
|
||||
_brioFreezeActor = dalamudPluginInterface.GetIpcSubscriber<IGameObject, bool>("Brio.Actor.Freeze");
|
||||
_brioFreezePhysics = dalamudPluginInterface.GetIpcSubscriber<bool>("Brio.FreezePhysics");
|
||||
|
||||
CheckAPI();
|
||||
}
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = _brioApiVersion.InvokeFunc();
|
||||
APIAvailable = (version.Item1 == 2 && version.Item2 >= 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IGameObject?> SpawnActorAsync()
|
||||
{
|
||||
if (!APIAvailable) return null;
|
||||
_logger.LogDebug("Spawning Brio Actor");
|
||||
return await _brioSpawnActorAsync.InvokeFunc(false, false, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> DespawnActorAsync(nint address)
|
||||
{
|
||||
if (!APIAvailable) return false;
|
||||
var gameObject = await _dalamudUtilService.CreateGameObjectAsync(address).ConfigureAwait(false);
|
||||
if (gameObject == null) return false;
|
||||
_logger.LogDebug("Despawning Brio Actor {actor}", gameObject.Name.TextValue);
|
||||
return await _dalamudUtilService.RunOnFrameworkThread(() => _brioDespawnActor.InvokeFunc(gameObject)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> ApplyTransformAsync(nint address, WorldData data)
|
||||
{
|
||||
if (!APIAvailable) return false;
|
||||
var gameObject = await _dalamudUtilService.CreateGameObjectAsync(address).ConfigureAwait(false);
|
||||
if (gameObject == null) return false;
|
||||
_logger.LogDebug("Applying Transform to Actor {actor}", gameObject.Name.TextValue);
|
||||
|
||||
return await _dalamudUtilService.RunOnFrameworkThread(() => _brioSetModelTransform.InvokeFunc(gameObject,
|
||||
new Vector3(data.PositionX, data.PositionY, data.PositionZ),
|
||||
new Quaternion(data.RotationX, data.RotationY, data.RotationZ, data.RotationW),
|
||||
new Vector3(data.ScaleX, data.ScaleY, data.ScaleZ), false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<WorldData> GetTransformAsync(nint address)
|
||||
{
|
||||
if (!APIAvailable) return default;
|
||||
var gameObject = await _dalamudUtilService.CreateGameObjectAsync(address).ConfigureAwait(false);
|
||||
if (gameObject == null) return default;
|
||||
var data = await _dalamudUtilService.RunOnFrameworkThread(() => _brioGetModelTransform.InvokeFunc(gameObject)).ConfigureAwait(false);
|
||||
if (data.Item1 == null || data.Item2 == null || data.Item3 == null) return default;
|
||||
//_logger.LogDebug("Getting Transform from Actor {actor}", gameObject.Name.TextValue);
|
||||
|
||||
return new WorldData()
|
||||
{
|
||||
PositionX = data.Item1.Value.X,
|
||||
PositionY = data.Item1.Value.Y,
|
||||
PositionZ = data.Item1.Value.Z,
|
||||
RotationX = data.Item2.Value.X,
|
||||
RotationY = data.Item2.Value.Y,
|
||||
RotationZ = data.Item2.Value.Z,
|
||||
RotationW = data.Item2.Value.W,
|
||||
ScaleX = data.Item3.Value.X,
|
||||
ScaleY = data.Item3.Value.Y,
|
||||
ScaleZ = data.Item3.Value.Z
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string?> GetPoseAsync(nint address)
|
||||
{
|
||||
if (!APIAvailable) return null;
|
||||
var gameObject = await _dalamudUtilService.CreateGameObjectAsync(address).ConfigureAwait(false);
|
||||
if (gameObject == null) return null;
|
||||
_logger.LogDebug("Getting Pose from Actor {actor}", gameObject.Name.TextValue);
|
||||
|
||||
return await _dalamudUtilService.RunOnFrameworkThread(() => _brioGetPoseAsJson.InvokeFunc(gameObject)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SetPoseAsync(nint address, string pose)
|
||||
{
|
||||
if (!APIAvailable) return false;
|
||||
var gameObject = await _dalamudUtilService.CreateGameObjectAsync(address).ConfigureAwait(false);
|
||||
if (gameObject == null) return false;
|
||||
_logger.LogDebug("Setting Pose to Actor {actor}", gameObject.Name.TextValue);
|
||||
|
||||
var applicablePose = JsonNode.Parse(pose)!;
|
||||
var currentPose = await _dalamudUtilService.RunOnFrameworkThread(() => _brioGetPoseAsJson.InvokeFunc(gameObject)).ConfigureAwait(false);
|
||||
applicablePose["ModelDifference"] = JsonNode.Parse(JsonNode.Parse(currentPose)!["ModelDifference"]!.ToJsonString());
|
||||
|
||||
await _dalamudUtilService.RunOnFrameworkThread(() =>
|
||||
{
|
||||
_brioFreezeActor.InvokeFunc(gameObject);
|
||||
_brioFreezePhysics.InvokeFunc();
|
||||
}).ConfigureAwait(false);
|
||||
return await _dalamudUtilService.RunOnFrameworkThread(() => _brioSetPoseFromJson.InvokeFunc(gameObject, applicablePose.ToJsonString(), false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
139
MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs
Normal file
139
MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Dalamud.Utility;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerCustomize : IIpcCaller
|
||||
{
|
||||
private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion;
|
||||
private readonly ICallGateSubscriber<ushort, (int, Guid?)> _customizePlusGetActiveProfile;
|
||||
private readonly ICallGateSubscriber<Guid, (int, string?)> _customizePlusGetProfileById;
|
||||
private readonly ICallGateSubscriber<ushort, Guid, object> _customizePlusOnScaleUpdate;
|
||||
private readonly ICallGateSubscriber<ushort, int> _customizePlusRevertCharacter;
|
||||
private readonly ICallGateSubscriber<ushort, string, (int, Guid?)> _customizePlusSetBodyScaleToCharacter;
|
||||
private readonly ICallGateSubscriber<Guid, int> _customizePlusDeleteByUniqueId;
|
||||
private readonly ILogger<IpcCallerCustomize> _logger;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareMediator _mareMediator;
|
||||
|
||||
public IpcCallerCustomize(ILogger<IpcCallerCustomize> logger, IDalamudPluginInterface dalamudPluginInterface,
|
||||
DalamudUtilService dalamudUtil, MareMediator mareMediator)
|
||||
{
|
||||
_customizePlusApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("CustomizePlus.General.GetApiVersion");
|
||||
_customizePlusGetActiveProfile = dalamudPluginInterface.GetIpcSubscriber<ushort, (int, Guid?)>("CustomizePlus.Profile.GetActiveProfileIdOnCharacter");
|
||||
_customizePlusGetProfileById = dalamudPluginInterface.GetIpcSubscriber<Guid, (int, string?)>("CustomizePlus.Profile.GetByUniqueId");
|
||||
_customizePlusRevertCharacter = dalamudPluginInterface.GetIpcSubscriber<ushort, int>("CustomizePlus.Profile.DeleteTemporaryProfileOnCharacter");
|
||||
_customizePlusSetBodyScaleToCharacter = dalamudPluginInterface.GetIpcSubscriber<ushort, string, (int, Guid?)>("CustomizePlus.Profile.SetTemporaryProfileOnCharacter");
|
||||
_customizePlusOnScaleUpdate = dalamudPluginInterface.GetIpcSubscriber<ushort, Guid, object>("CustomizePlus.Profile.OnUpdate");
|
||||
_customizePlusDeleteByUniqueId = dalamudPluginInterface.GetIpcSubscriber<Guid, int>("CustomizePlus.Profile.DeleteTemporaryProfileByUniqueId");
|
||||
|
||||
_customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange);
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
|
||||
CheckAPI();
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
public async Task RevertAsync(nint character)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is ICharacter c)
|
||||
{
|
||||
_logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X"));
|
||||
_customizePlusRevertCharacter!.InvokeFunc(c.ObjectIndex);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<Guid?> SetBodyScaleAsync(nint character, string scale)
|
||||
{
|
||||
if (!APIAvailable) return null;
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is ICharacter c)
|
||||
{
|
||||
string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale));
|
||||
_logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X"));
|
||||
if (scale.IsNullOrEmpty())
|
||||
{
|
||||
_customizePlusRevertCharacter!.InvokeFunc(c.ObjectIndex);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = _customizePlusSetBodyScaleToCharacter!.InvokeFunc(c.ObjectIndex, decodedScale);
|
||||
return result.Item2;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task RevertByIdAsync(Guid? profileId)
|
||||
{
|
||||
if (!APIAvailable || profileId == null) return;
|
||||
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
_ = _customizePlusDeleteByUniqueId.InvokeFunc(profileId.Value);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<string?> GetScaleAsync(nint character)
|
||||
{
|
||||
if (!APIAvailable) return null;
|
||||
var scale = await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is ICharacter c)
|
||||
{
|
||||
var res = _customizePlusGetActiveProfile.InvokeFunc(c.ObjectIndex);
|
||||
_logger.LogTrace("CustomizePlus GetActiveProfile returned {err}", res.Item1);
|
||||
if (res.Item1 != 0 || res.Item2 == null) return string.Empty;
|
||||
return _customizePlusGetProfileById.InvokeFunc(res.Item2.Value).Item2;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(scale)) return string.Empty;
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale));
|
||||
}
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = _customizePlusApiVersion.InvokeFunc();
|
||||
APIAvailable = (version.Item1 == 6 && version.Item2 >= 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCustomizePlusScaleChange(ushort c, Guid g)
|
||||
{
|
||||
var obj = _dalamudUtil.GetCharacterFromObjectTableByIndex(c);
|
||||
_mareMediator.Publish(new CustomizePlusMessage(obj?.Address ?? null));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_customizePlusOnScaleUpdate.Unsubscribe(OnCustomizePlusScaleChange);
|
||||
}
|
||||
}
|
253
MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs
Normal file
253
MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Api.Helpers;
|
||||
using Glamourer.Api.IpcSubscribers;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcCaller
|
||||
{
|
||||
private readonly ILogger<IpcCallerGlamourer> _logger;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly RedrawManager _redrawManager;
|
||||
|
||||
private readonly ApiVersion _glamourerApiVersions;
|
||||
private readonly ApplyState? _glamourerApplyAll;
|
||||
private readonly GetStateBase64? _glamourerGetAllCustomization;
|
||||
private readonly RevertState _glamourerRevert;
|
||||
private readonly RevertStateName _glamourerRevertByName;
|
||||
private readonly UnlockState _glamourerUnlock;
|
||||
private readonly UnlockStateName _glamourerUnlockByName;
|
||||
private readonly EventSubscriber<nint>? _glamourerStateChanged;
|
||||
|
||||
private bool _pluginLoaded;
|
||||
private Version _pluginVersion;
|
||||
|
||||
private bool _shownGlamourerUnavailable = false;
|
||||
private readonly uint LockCode = 0x626E7579;
|
||||
|
||||
public IpcCallerGlamourer(ILogger<IpcCallerGlamourer> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator,
|
||||
RedrawManager redrawManager) : base(logger, mareMediator)
|
||||
{
|
||||
_glamourerApiVersions = new ApiVersion(pi);
|
||||
_glamourerGetAllCustomization = new GetStateBase64(pi);
|
||||
_glamourerApplyAll = new ApplyState(pi);
|
||||
_glamourerRevert = new RevertState(pi);
|
||||
_glamourerRevertByName = new RevertStateName(pi);
|
||||
_glamourerUnlock = new UnlockState(pi);
|
||||
_glamourerUnlockByName = new UnlockStateName(pi);
|
||||
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
_redrawManager = redrawManager;
|
||||
|
||||
var plugin = PluginWatcherService.GetInitialPluginState(pi, "Glamourer");
|
||||
|
||||
_pluginLoaded = plugin?.IsLoaded ?? false;
|
||||
_pluginVersion = plugin?.Version ?? new(0, 0, 0, 0);
|
||||
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "Glamourer", (msg) =>
|
||||
{
|
||||
_pluginLoaded = msg.IsLoaded;
|
||||
_pluginVersion = msg.Version;
|
||||
CheckAPI();
|
||||
});
|
||||
|
||||
CheckAPI();
|
||||
|
||||
_glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged);
|
||||
_glamourerStateChanged.Enable();
|
||||
|
||||
Mediator.Subscribe<DalamudLoginMessage>(this, s => _shownGlamourerUnavailable = false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_redrawManager.Cancel();
|
||||
_glamourerStateChanged?.Dispose();
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; }
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
bool apiAvailable = false;
|
||||
try
|
||||
{
|
||||
bool versionValid = _pluginLoaded && _pluginVersion >= new Version(1, 0, 6, 1);
|
||||
try
|
||||
{
|
||||
var version = _glamourerApiVersions.Invoke();
|
||||
if (version is { Major: 1, Minor: >= 1 } && versionValid)
|
||||
{
|
||||
apiAvailable = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
_shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable;
|
||||
|
||||
APIAvailable = apiAvailable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = apiAvailable;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!apiAvailable && !_shownGlamourerUnavailable)
|
||||
{
|
||||
_shownGlamourerUnavailable = true;
|
||||
_mareMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Elezen. If you just updated Glamourer, ignore this message.",
|
||||
NotificationType.Error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool allowImmediate = false)
|
||||
{
|
||||
if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;
|
||||
|
||||
// Call immediately if possible
|
||||
if (allowImmediate && _dalamudUtil.IsOnFrameworkThread && !await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false))
|
||||
{
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
|
||||
if (gameObj is ICharacter chara)
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId);
|
||||
_glamourerApplyAll!.Invoke(customization, chara.ObjectIndex, LockCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId);
|
||||
_glamourerApplyAll!.Invoke(customization, chara.ObjectIndex, LockCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "[{appid}] Failed to apply Glamourer data", applicationId);
|
||||
}
|
||||
}, token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_redrawManager.RedrawSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetCharacterCustomizationAsync(IntPtr character)
|
||||
{
|
||||
if (!APIAvailable) return string.Empty;
|
||||
try
|
||||
{
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is ICharacter c)
|
||||
{
|
||||
return _glamourerGetAllCustomization!.Invoke(c.ObjectIndex).Item2 ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RevertAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
||||
{
|
||||
if ((!APIAvailable) || _dalamudUtil.IsZoning) return;
|
||||
try
|
||||
{
|
||||
await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlock", applicationId);
|
||||
_glamourerUnlock.Invoke(chara.ObjectIndex, LockCode);
|
||||
logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId);
|
||||
_glamourerRevert.Invoke(chara.ObjectIndex, LockCode);
|
||||
logger.LogDebug("[{appid}] Calling On IPC: PenumbraRedraw", applicationId);
|
||||
_mareMediator.Publish(new PenumbraRedrawCharacterMessage(chara));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "[{appid}] Error during GlamourerRevert", applicationId);
|
||||
}
|
||||
}, token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_redrawManager.RedrawSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
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 void RevertByNameNow(ILogger logger, Guid applicationId, string name)
|
||||
{
|
||||
if ((!APIAvailable) || _dalamudUtil.IsZoning) return;
|
||||
logger.LogTrace("[{applicationId}] Immediately reverting {name}", applicationId, name);
|
||||
_glamourerRevertByName.Invoke(name, LockCode);
|
||||
}
|
||||
|
||||
public async Task RevertByNameAsync(ILogger logger, string name, Guid applicationId)
|
||||
{
|
||||
if ((!APIAvailable) || _dalamudUtil.IsZoning) return;
|
||||
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
RevertByName(logger, name, applicationId);
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void RevertByName(ILogger logger, string name, Guid applicationId)
|
||||
{
|
||||
if ((!APIAvailable) || _dalamudUtil.IsZoning) return;
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId);
|
||||
_glamourerRevertByName.Invoke(name, LockCode);
|
||||
logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId);
|
||||
_glamourerUnlockByName.Invoke(name, LockCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error during Glamourer RevertByName");
|
||||
}
|
||||
}
|
||||
|
||||
private void GlamourerChanged(nint address)
|
||||
{
|
||||
_mareMediator.Publish(new GlamourerChangedMessage(address));
|
||||
}
|
||||
}
|
93
MareSynchronos/Interop/Ipc/IpcCallerHeels.cs
Normal file
93
MareSynchronos/Interop/Ipc/IpcCallerHeels.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerHeels : IIpcCaller
|
||||
{
|
||||
private readonly ILogger<IpcCallerHeels> _logger;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly ICallGateSubscriber<(int, int)> _heelsGetApiVersion;
|
||||
private readonly ICallGateSubscriber<string> _heelsGetOffset;
|
||||
private readonly ICallGateSubscriber<string, object?> _heelsOffsetUpdate;
|
||||
private readonly ICallGateSubscriber<int, string, object?> _heelsRegisterPlayer;
|
||||
private readonly ICallGateSubscriber<int, object?> _heelsUnregisterPlayer;
|
||||
|
||||
public IpcCallerHeels(ILogger<IpcCallerHeels> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_mareMediator = mareMediator;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_heelsGetApiVersion = pi.GetIpcSubscriber<(int, int)>("SimpleHeels.ApiVersion");
|
||||
_heelsGetOffset = pi.GetIpcSubscriber<string>("SimpleHeels.GetLocalPlayer");
|
||||
_heelsRegisterPlayer = pi.GetIpcSubscriber<int, string, object?>("SimpleHeels.RegisterPlayer");
|
||||
_heelsUnregisterPlayer = pi.GetIpcSubscriber<int, object?>("SimpleHeels.UnregisterPlayer");
|
||||
_heelsOffsetUpdate = pi.GetIpcSubscriber<string, object?>("SimpleHeels.LocalChanged");
|
||||
|
||||
_heelsOffsetUpdate.Subscribe(HeelsOffsetChange);
|
||||
|
||||
CheckAPI();
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
private void HeelsOffsetChange(string offset)
|
||||
{
|
||||
_mareMediator.Publish(new HeelsOffsetMessage());
|
||||
}
|
||||
|
||||
public async Task<string> GetOffsetAsync()
|
||||
{
|
||||
if (!APIAvailable) return string.Empty;
|
||||
return await _dalamudUtil.RunOnFrameworkThread(_heelsGetOffset.InvokeFunc).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task RestoreOffsetForPlayerAsync(IntPtr character)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
_logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X"));
|
||||
_heelsUnregisterPlayer.InvokeAction(gameObj.ObjectIndex);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SetOffsetForPlayerAsync(IntPtr character, string data)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
_logger.LogTrace("Applying Heels data to {chara}", character.ToString("X"));
|
||||
_heelsRegisterPlayer.InvokeAction(gameObj.ObjectIndex, data);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
try
|
||||
{
|
||||
APIAvailable = _heelsGetApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 0 };
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||
}
|
||||
}
|
135
MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs
Normal file
135
MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerHonorific : IIpcCaller
|
||||
{
|
||||
private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion;
|
||||
private readonly ICallGateSubscriber<int, object> _honorificClearCharacterTitle;
|
||||
private readonly ICallGateSubscriber<object> _honorificDisposing;
|
||||
private readonly ICallGateSubscriber<string> _honorificGetLocalCharacterTitle;
|
||||
private readonly ICallGateSubscriber<string, object> _honorificLocalCharacterTitleChanged;
|
||||
private readonly ICallGateSubscriber<object> _honorificReady;
|
||||
private readonly ICallGateSubscriber<int, string, object> _honorificSetCharacterTitle;
|
||||
private readonly ILogger<IpcCallerHonorific> _logger;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
|
||||
public IpcCallerHonorific(ILogger<IpcCallerHonorific> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||
MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_mareMediator = mareMediator;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion");
|
||||
_honorificGetLocalCharacterTitle = pi.GetIpcSubscriber<string>("Honorific.GetLocalCharacterTitle");
|
||||
_honorificClearCharacterTitle = pi.GetIpcSubscriber<int, object>("Honorific.ClearCharacterTitle");
|
||||
_honorificSetCharacterTitle = pi.GetIpcSubscriber<int, string, object>("Honorific.SetCharacterTitle");
|
||||
_honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber<string, object>("Honorific.LocalCharacterTitleChanged");
|
||||
_honorificDisposing = pi.GetIpcSubscriber<object>("Honorific.Disposing");
|
||||
_honorificReady = pi.GetIpcSubscriber<object>("Honorific.Ready");
|
||||
|
||||
_honorificLocalCharacterTitleChanged.Subscribe(OnHonorificLocalCharacterTitleChanged);
|
||||
_honorificDisposing.Subscribe(OnHonorificDisposing);
|
||||
_honorificReady.Subscribe(OnHonorificReady);
|
||||
|
||||
CheckAPI();
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
try
|
||||
{
|
||||
APIAvailable = _honorificApiVersion.InvokeFunc() is { Item1: 3, Item2: >= 0 };
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_honorificLocalCharacterTitleChanged.Unsubscribe(OnHonorificLocalCharacterTitleChanged);
|
||||
_honorificDisposing.Unsubscribe(OnHonorificDisposing);
|
||||
_honorificReady.Unsubscribe(OnHonorificReady);
|
||||
}
|
||||
|
||||
public async Task ClearTitleAsync(nint character)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is IPlayerCharacter c)
|
||||
{
|
||||
_logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X"));
|
||||
_honorificClearCharacterTitle!.InvokeAction(c.ObjectIndex);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<string> GetTitle()
|
||||
{
|
||||
if (!APIAvailable) return string.Empty;
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
string title = _honorificGetLocalCharacterTitle.InvokeFunc();
|
||||
return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title));
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SetTitleAsync(IntPtr character, string honorificDataB64)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
_logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X"));
|
||||
try
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is IPlayerCharacter pc)
|
||||
{
|
||||
string honorificData = string.IsNullOrEmpty(honorificDataB64) ? string.Empty : Encoding.UTF8.GetString(Convert.FromBase64String(honorificDataB64));
|
||||
if (string.IsNullOrEmpty(honorificData))
|
||||
{
|
||||
_honorificClearCharacterTitle!.InvokeAction(pc.ObjectIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_honorificSetCharacterTitle!.InvokeAction(pc.ObjectIndex, honorificData);
|
||||
}
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not apply Honorific data");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHonorificDisposing()
|
||||
{
|
||||
_mareMediator.Publish(new HonorificMessage(string.Empty));
|
||||
}
|
||||
|
||||
private void OnHonorificLocalCharacterTitleChanged(string titleJson)
|
||||
{
|
||||
string titleData = string.IsNullOrEmpty(titleJson) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(titleJson));
|
||||
_mareMediator.Publish(new HonorificMessage(titleData));
|
||||
}
|
||||
|
||||
private void OnHonorificReady()
|
||||
{
|
||||
CheckAPI();
|
||||
_mareMediator.Publish(new HonorificReadyMessage());
|
||||
}
|
||||
}
|
44
MareSynchronos/Interop/Ipc/IpcCallerMare.cs
Normal file
44
MareSynchronos/Interop/Ipc/IpcCallerMare.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerMare : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly ICallGateSubscriber<List<nint>> _mareHandledGameAddresses;
|
||||
private readonly List<nint> _emptyList = [];
|
||||
|
||||
private bool _pluginLoaded;
|
||||
|
||||
public IpcCallerMare(ILogger<IpcCallerMare> logger, IDalamudPluginInterface pi, MareMediator mediator) : base(logger, mediator)
|
||||
{
|
||||
_mareHandledGameAddresses = pi.GetIpcSubscriber<List<nint>>("MareSynchronos.GetHandledAddresses");
|
||||
|
||||
_pluginLoaded = PluginWatcherService.GetInitialPluginState(pi, "MareSynchronos")?.IsLoaded ?? false;
|
||||
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "MareSynchronos", (msg) =>
|
||||
{
|
||||
_pluginLoaded = msg.IsLoaded;
|
||||
});
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
// Must be called on framework thread
|
||||
public IReadOnlyList<nint> GetHandledGameAddresses()
|
||||
{
|
||||
if (!_pluginLoaded) return _emptyList;
|
||||
|
||||
try
|
||||
{
|
||||
return _mareHandledGameAddresses.InvokeFunc();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _emptyList;
|
||||
}
|
||||
}
|
||||
}
|
104
MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs
Normal file
104
MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerMoodles : IIpcCaller
|
||||
{
|
||||
private readonly ICallGateSubscriber<int> _moodlesApiVersion;
|
||||
private readonly ICallGateSubscriber<IPlayerCharacter, object> _moodlesOnChange;
|
||||
private readonly ICallGateSubscriber<nint, string> _moodlesGetStatus;
|
||||
private readonly ICallGateSubscriber<nint, string, object> _moodlesSetStatus;
|
||||
private readonly ICallGateSubscriber<nint, object> _moodlesRevertStatus;
|
||||
private readonly ILogger<IpcCallerMoodles> _logger;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareMediator _mareMediator;
|
||||
|
||||
public IpcCallerMoodles(ILogger<IpcCallerMoodles> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||
MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
|
||||
_moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version");
|
||||
_moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified");
|
||||
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtr");
|
||||
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtr");
|
||||
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtr");
|
||||
|
||||
_moodlesOnChange.Subscribe(OnMoodlesChange);
|
||||
|
||||
CheckAPI();
|
||||
}
|
||||
|
||||
private void OnMoodlesChange(IPlayerCharacter character)
|
||||
{
|
||||
_mareMediator.Publish(new MoodlesMessage(character.Address));
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
try
|
||||
{
|
||||
APIAvailable = _moodlesApiVersion.InvokeFunc() == 1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_moodlesOnChange.Unsubscribe(OnMoodlesChange);
|
||||
}
|
||||
|
||||
public async Task<string?> GetStatusAsync(nint address)
|
||||
{
|
||||
if (!APIAvailable) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() => _moodlesGetStatus.InvokeFunc(address)).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not Get Moodles Status");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetStatusAsync(nint pointer, string status)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
try
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() => _moodlesSetStatus.InvokeAction(pointer, status)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not Set Moodles Status");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RevertStatusAsync(nint pointer)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
try
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() => _moodlesRevertStatus.InvokeAction(pointer)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not Set Moodles Status");
|
||||
}
|
||||
}
|
||||
}
|
360
MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs
Normal file
360
MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using Dalamud.Plugin;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCaller
|
||||
{
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly RedrawManager _redrawManager;
|
||||
private bool _shownPenumbraUnavailable = false;
|
||||
private string? _penumbraModDirectory;
|
||||
public string? ModDirectory
|
||||
{
|
||||
get => _penumbraModDirectory;
|
||||
private set
|
||||
{
|
||||
if (!string.Equals(_penumbraModDirectory, value, StringComparison.Ordinal))
|
||||
{
|
||||
_penumbraModDirectory = value;
|
||||
_mareMediator.Publish(new PenumbraDirectoryChangedMessage(_penumbraModDirectory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new();
|
||||
|
||||
private readonly EventSubscriber _penumbraDispose;
|
||||
private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved;
|
||||
private readonly EventSubscriber _penumbraInit;
|
||||
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _penumbraModSettingChanged;
|
||||
private readonly EventSubscriber<nint, int> _penumbraObjectIsRedrawn;
|
||||
|
||||
private readonly AddTemporaryMod _penumbraAddTemporaryMod;
|
||||
private readonly AssignTemporaryCollection _penumbraAssignTemporaryCollection;
|
||||
private readonly ConvertTextureFile _penumbraConvertTextureFile;
|
||||
private readonly CreateTemporaryCollection _penumbraCreateNamedTemporaryCollection;
|
||||
private readonly GetEnabledState _penumbraEnabled;
|
||||
private readonly GetPlayerMetaManipulations _penumbraGetMetaManipulations;
|
||||
private readonly RedrawObject _penumbraRedraw;
|
||||
private readonly DeleteTemporaryCollection _penumbraRemoveTemporaryCollection;
|
||||
private readonly RemoveTemporaryMod _penumbraRemoveTemporaryMod;
|
||||
private readonly GetModDirectory _penumbraResolveModDir;
|
||||
private readonly ResolvePlayerPathsAsync _penumbraResolvePaths;
|
||||
private readonly GetGameObjectResourcePaths _penumbraResourcePaths;
|
||||
|
||||
private bool _pluginLoaded;
|
||||
private Version _pluginVersion;
|
||||
|
||||
public IpcCallerPenumbra(ILogger<IpcCallerPenumbra> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||
MareMediator mareMediator, RedrawManager redrawManager) : base(logger, mareMediator)
|
||||
{
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
_redrawManager = redrawManager;
|
||||
_penumbraInit = Initialized.Subscriber(pi, PenumbraInit);
|
||||
_penumbraDispose = Disposed.Subscriber(pi, PenumbraDispose);
|
||||
_penumbraResolveModDir = new GetModDirectory(pi);
|
||||
_penumbraRedraw = new RedrawObject(pi);
|
||||
_penumbraObjectIsRedrawn = GameObjectRedrawn.Subscriber(pi, RedrawEvent);
|
||||
_penumbraGetMetaManipulations = new GetPlayerMetaManipulations(pi);
|
||||
_penumbraRemoveTemporaryMod = new RemoveTemporaryMod(pi);
|
||||
_penumbraAddTemporaryMod = new AddTemporaryMod(pi);
|
||||
_penumbraCreateNamedTemporaryCollection = new CreateTemporaryCollection(pi);
|
||||
_penumbraRemoveTemporaryCollection = new DeleteTemporaryCollection(pi);
|
||||
_penumbraAssignTemporaryCollection = new AssignTemporaryCollection(pi);
|
||||
_penumbraResolvePaths = new ResolvePlayerPathsAsync(pi);
|
||||
_penumbraEnabled = new GetEnabledState(pi);
|
||||
_penumbraModSettingChanged = ModSettingChanged.Subscriber(pi, (change, arg1, arg, b) =>
|
||||
{
|
||||
if (change == ModSettingChange.EnableState)
|
||||
_mareMediator.Publish(new PenumbraModSettingChangedMessage());
|
||||
});
|
||||
_penumbraConvertTextureFile = new ConvertTextureFile(pi);
|
||||
_penumbraResourcePaths = new GetGameObjectResourcePaths(pi);
|
||||
|
||||
_penumbraGameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded);
|
||||
|
||||
var plugin = PluginWatcherService.GetInitialPluginState(pi, "Penumbra");
|
||||
|
||||
_pluginLoaded = plugin?.IsLoaded ?? false;
|
||||
_pluginVersion = plugin?.Version ?? new(0, 0, 0, 0);
|
||||
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "Penumbra", (msg) =>
|
||||
{
|
||||
_pluginLoaded = msg.IsLoaded;
|
||||
_pluginVersion = msg.Version;
|
||||
CheckAPI();
|
||||
});
|
||||
|
||||
CheckAPI();
|
||||
CheckModDirectory();
|
||||
|
||||
Mediator.Subscribe<PenumbraRedrawCharacterMessage>(this, (msg) =>
|
||||
{
|
||||
_penumbraRedraw.Invoke(msg.Character.ObjectIndex, RedrawType.AfterGPose);
|
||||
});
|
||||
|
||||
Mediator.Subscribe<DalamudLoginMessage>(this, (msg) => _shownPenumbraUnavailable = false);
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
bool penumbraAvailable = false;
|
||||
try
|
||||
{
|
||||
penumbraAvailable = _pluginLoaded && _pluginVersion >= new Version(1, 0, 1, 0);
|
||||
try
|
||||
{
|
||||
penumbraAvailable &= _penumbraEnabled.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
penumbraAvailable = false;
|
||||
}
|
||||
_shownPenumbraUnavailable = _shownPenumbraUnavailable && !penumbraAvailable;
|
||||
APIAvailable = penumbraAvailable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = penumbraAvailable;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!penumbraAvailable && !_shownPenumbraUnavailable)
|
||||
{
|
||||
_shownPenumbraUnavailable = true;
|
||||
_mareMediator.Publish(new NotificationMessage("Penumbra inactive",
|
||||
"Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Elezen. If you just updated Penumbra, ignore this message.",
|
||||
NotificationType.Error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckModDirectory()
|
||||
{
|
||||
if (!APIAvailable)
|
||||
{
|
||||
ModDirectory = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModDirectory = _penumbraResolveModDir!.Invoke().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_redrawManager.Cancel();
|
||||
|
||||
_penumbraModSettingChanged.Dispose();
|
||||
_penumbraGameObjectResourcePathResolved.Dispose();
|
||||
_penumbraDispose.Dispose();
|
||||
_penumbraInit.Dispose();
|
||||
_penumbraObjectIsRedrawn.Dispose();
|
||||
}
|
||||
|
||||
public async Task AssignTemporaryCollectionAsync(ILogger logger, Guid collName, int idx)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, forceAssignment: true);
|
||||
logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign);
|
||||
return collName;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task ConvertTextureFiles(ILogger logger, Dictionary<string, string[]> textures, IProgress<(string, int)> progress, CancellationToken token)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
|
||||
_mareMediator.Publish(new HaltScanMessage(nameof(ConvertTextureFiles)));
|
||||
int currentTexture = 0;
|
||||
foreach (var texture in textures)
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
progress.Report((texture.Key, ++currentTexture));
|
||||
|
||||
logger.LogInformation("Converting Texture {path} to {type}", texture.Key, TextureType.Bc7Tex);
|
||||
var convertTask = _penumbraConvertTextureFile.Invoke(texture.Key, texture.Key, TextureType.Bc7Tex, mipMaps: true);
|
||||
await convertTask.ConfigureAwait(false);
|
||||
if (convertTask.IsCompletedSuccessfully && texture.Value.Any())
|
||||
{
|
||||
foreach (var duplicatedTexture in texture.Value)
|
||||
{
|
||||
logger.LogInformation("Migrating duplicate {dup}", duplicatedTexture);
|
||||
try
|
||||
{
|
||||
File.Copy(texture.Key, duplicatedTexture, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to copy duplicate {dup}", duplicatedTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_mareMediator.Publish(new ResumeScanMessage(nameof(ConvertTextureFiles)));
|
||||
|
||||
await _dalamudUtil.RunOnFrameworkThread(async () =>
|
||||
{
|
||||
var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
_penumbraRedraw.Invoke(gameObject!.ObjectIndex, setting: RedrawType.Redraw);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
|
||||
{
|
||||
if (!APIAvailable) return Guid.Empty;
|
||||
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var collName = "ElfSync_" + uid;
|
||||
var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
|
||||
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
|
||||
return collId;
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, HashSet<string>>?> GetCharacterData(ILogger logger, GameObjectHandler handler)
|
||||
{
|
||||
if (!APIAvailable) return null;
|
||||
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths");
|
||||
var idx = handler.GetGameObject()?.ObjectIndex;
|
||||
if (idx == null) return null;
|
||||
return _penumbraResourcePaths.Invoke(idx.Value)[0];
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string GetMetaManipulations()
|
||||
{
|
||||
if (!APIAvailable) return string.Empty;
|
||||
return _penumbraGetMetaManipulations.Invoke();
|
||||
}
|
||||
|
||||
public async Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
||||
{
|
||||
if (!APIAvailable || _dalamudUtil.IsZoning) return;
|
||||
try
|
||||
{
|
||||
await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) =>
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId);
|
||||
_penumbraRedraw!.Invoke(chara.ObjectIndex, setting: RedrawType.Redraw);
|
||||
|
||||
}, token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_redrawManager.RedrawSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
logger.LogTrace("[{applicationId}] Removing temp collection for {collId}", applicationId, collId);
|
||||
var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collId);
|
||||
logger.LogTrace("[{applicationId}] RemoveTemporaryCollection: {ret2}", applicationId, ret2);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse)
|
||||
{
|
||||
return await _penumbraResolvePaths.Invoke(forward, reverse).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collId, string manipulationData)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData);
|
||||
var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collId, [], manipulationData, 0);
|
||||
logger.LogTrace("[{applicationId}] Setting temp meta mod for {collId}, Success: {ret}", applicationId, collId, retAdd);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collId, Dictionary<string, string> modPaths)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
foreach (var mod in modPaths)
|
||||
{
|
||||
logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value);
|
||||
}
|
||||
var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Files", collId, 0);
|
||||
logger.LogTrace("[{applicationId}] Removing temp files mod for {collId}, Success: {ret}", applicationId, collId, retRemove);
|
||||
var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collId, modPaths, string.Empty, 0);
|
||||
logger.LogTrace("[{applicationId}] Setting temp files mod for {collId}, Success: {ret}", applicationId, collId, retAdd);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||
{
|
||||
bool wasRequested = false;
|
||||
if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest) && redrawRequest)
|
||||
{
|
||||
_penumbraRedrawRequests[objectAddress] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mareMediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested));
|
||||
}
|
||||
}
|
||||
|
||||
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
|
||||
{
|
||||
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, ignoreCase: true, System.Globalization.CultureInfo.InvariantCulture) != 0)
|
||||
{
|
||||
_mareMediator.Publish(new PenumbraResourceLoadMessage(ptr, arg1, arg2));
|
||||
}
|
||||
}
|
||||
|
||||
private void PenumbraDispose()
|
||||
{
|
||||
_redrawManager.Cancel();
|
||||
_mareMediator.Publish(new PenumbraDisposedMessage());
|
||||
}
|
||||
|
||||
private void PenumbraInit()
|
||||
{
|
||||
APIAvailable = true;
|
||||
ModDirectory = _penumbraResolveModDir.Invoke();
|
||||
_mareMediator.Publish(new PenumbraInitializedMessage());
|
||||
_penumbraRedraw!.Invoke(0, setting: RedrawType.Redraw);
|
||||
}
|
||||
}
|
158
MareSynchronos/Interop/Ipc/IpcCallerPetNames.cs
Normal file
158
MareSynchronos/Interop/Ipc/IpcCallerPetNames.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerPetNames : IIpcCaller
|
||||
{
|
||||
private readonly ILogger<IpcCallerPetNames> _logger;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareMediator _mareMediator;
|
||||
|
||||
private readonly ICallGateSubscriber<object> _petnamesReady;
|
||||
private readonly ICallGateSubscriber<object> _petnamesDisposing;
|
||||
private readonly ICallGateSubscriber<(uint, uint)> _apiVersion;
|
||||
private readonly ICallGateSubscriber<bool> _enabled;
|
||||
|
||||
private readonly ICallGateSubscriber<string, object> _playerDataChanged;
|
||||
private readonly ICallGateSubscriber<string> _getPlayerData;
|
||||
private readonly ICallGateSubscriber<string, object> _setPlayerData;
|
||||
private readonly ICallGateSubscriber<ushort, object> _clearPlayerData;
|
||||
|
||||
public IpcCallerPetNames(ILogger<IpcCallerPetNames> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||
MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
|
||||
_petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.Ready");
|
||||
_petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.Disposing");
|
||||
_apiVersion = pi.GetIpcSubscriber<(uint, uint)>("PetRenamer.ApiVersion");
|
||||
_enabled = pi.GetIpcSubscriber<bool>("PetRenamer.Enabled");
|
||||
|
||||
_playerDataChanged = pi.GetIpcSubscriber<string, object>("PetRenamer.PlayerDataChanged");
|
||||
_getPlayerData = pi.GetIpcSubscriber<string>("PetRenamer.GetPlayerData");
|
||||
_setPlayerData = pi.GetIpcSubscriber<string, object>("PetRenamer.SetPlayerData");
|
||||
_clearPlayerData = pi.GetIpcSubscriber<ushort, object>("PetRenamer.ClearPlayerData");
|
||||
|
||||
_petnamesReady.Subscribe(OnPetNicknamesReady);
|
||||
_petnamesDisposing.Subscribe(OnPetNicknamesDispose);
|
||||
_playerDataChanged.Subscribe(OnLocalPetNicknamesDataChange);
|
||||
|
||||
CheckAPI();
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
public void CheckAPI()
|
||||
{
|
||||
try
|
||||
{
|
||||
APIAvailable = _enabled?.InvokeFunc() ?? false;
|
||||
if (APIAvailable)
|
||||
{
|
||||
APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 3, Item2: >= 1 };
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPetNicknamesReady()
|
||||
{
|
||||
CheckAPI();
|
||||
_mareMediator.Publish(new PetNamesReadyMessage());
|
||||
}
|
||||
|
||||
private void OnPetNicknamesDispose()
|
||||
{
|
||||
_mareMediator.Publish(new PetNamesMessage(string.Empty));
|
||||
}
|
||||
|
||||
public string GetLocalNames()
|
||||
{
|
||||
if (!APIAvailable) return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
string localNameData = _getPlayerData.InvokeFunc();
|
||||
return string.IsNullOrEmpty(localNameData) ? string.Empty : localNameData;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not obtain Pet Nicknames data");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task SetPlayerData(nint character, string playerData)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
|
||||
_logger.LogTrace("Applying Pet Nicknames data to {chara}", character.ToString("X"));
|
||||
|
||||
try
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(playerData))
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is IPlayerCharacter pc)
|
||||
{
|
||||
_clearPlayerData.InvokeAction(pc.ObjectIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_setPlayerData.InvokeAction(playerData);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not apply Pet Nicknames data");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearPlayerData(nint characterPointer)
|
||||
{
|
||||
if (!APIAvailable) return;
|
||||
try
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(characterPointer);
|
||||
if (gameObj is IPlayerCharacter pc)
|
||||
{
|
||||
_logger.LogTrace("Pet Nicknames removing for {addr}", pc.Address.ToString("X"));
|
||||
_clearPlayerData.InvokeAction(pc.ObjectIndex);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Could not clear Pet Nicknames data");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLocalPetNicknamesDataChange(string data)
|
||||
{
|
||||
_mareMediator.Publish(new PetNamesMessage(data));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_petnamesReady.Unsubscribe(OnPetNicknamesReady);
|
||||
_petnamesDisposing.Unsubscribe(OnPetNicknamesDispose);
|
||||
_playerDataChanged.Unsubscribe(OnLocalPetNicknamesDataChange);
|
||||
}
|
||||
}
|
68
MareSynchronos/Interop/Ipc/IpcManager.cs
Normal file
68
MareSynchronos/Interop/Ipc/IpcManager.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
||||
{
|
||||
public IpcManager(ILogger<IpcManager> logger, MareMediator mediator,
|
||||
IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc,
|
||||
IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio) : base(logger, mediator)
|
||||
{
|
||||
CustomizePlus = customizeIpc;
|
||||
Heels = heelsIpc;
|
||||
Glamourer = glamourerIpc;
|
||||
Penumbra = penumbraIpc;
|
||||
Honorific = honorificIpc;
|
||||
Moodles = moodlesIpc;
|
||||
PetNames = ipcCallerPetNames;
|
||||
Brio = ipcCallerBrio;
|
||||
|
||||
if (Initialized)
|
||||
{
|
||||
Mediator.Publish(new PenumbraInitializedMessage());
|
||||
}
|
||||
|
||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => PeriodicApiStateCheck());
|
||||
|
||||
try
|
||||
{
|
||||
PeriodicApiStateCheck();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to check for some IPC, plugin not installed?");
|
||||
}
|
||||
}
|
||||
|
||||
public bool Initialized => Penumbra.APIAvailable && Glamourer.APIAvailable;
|
||||
|
||||
public IpcCallerCustomize CustomizePlus { get; init; }
|
||||
public IpcCallerHonorific Honorific { get; init; }
|
||||
public IpcCallerHeels Heels { get; init; }
|
||||
public IpcCallerGlamourer Glamourer { get; }
|
||||
public IpcCallerPenumbra Penumbra { get; }
|
||||
public IpcCallerMoodles Moodles { get; }
|
||||
public IpcCallerPetNames PetNames { get; }
|
||||
|
||||
public IpcCallerBrio Brio { get; }
|
||||
|
||||
private int _stateCheckCounter = -1;
|
||||
|
||||
private void PeriodicApiStateCheck()
|
||||
{
|
||||
// Stagger API checks
|
||||
if (++_stateCheckCounter > 8)
|
||||
_stateCheckCounter = 0;
|
||||
int i = _stateCheckCounter;
|
||||
if (i == 0) Penumbra.CheckAPI();
|
||||
if (i == 1) Penumbra.CheckModDirectory();
|
||||
if (i == 2) Glamourer.CheckAPI();
|
||||
if (i == 3) Heels.CheckAPI();
|
||||
if (i == 4) CustomizePlus.CheckAPI();
|
||||
if (i == 5) Honorific.CheckAPI();
|
||||
if (i == 6) Moodles.CheckAPI();
|
||||
if (i == 7) PetNames.CheckAPI();
|
||||
if (i == 8) Brio.CheckAPI();
|
||||
}
|
||||
}
|
196
MareSynchronos/Interop/Ipc/IpcProvider.cs
Normal file
196
MareSynchronos/Interop/Ipc/IpcProvider.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
private readonly ILogger<IpcProvider> _logger;
|
||||
private readonly IDalamudPluginInterface _pi;
|
||||
private readonly MareConfigService _mareConfig;
|
||||
private readonly CharaDataManager _charaDataManager;
|
||||
private ICallGateProvider<string, IGameObject, bool>? _loadFileProvider;
|
||||
private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProvider;
|
||||
private ICallGateProvider<List<nint>>? _handledGameAddresses;
|
||||
private readonly List<GameObjectHandler> _activeGameObjectHandlers = [];
|
||||
|
||||
private ICallGateProvider<string, IGameObject, bool>? _loadFileProviderMare;
|
||||
private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProviderMare;
|
||||
private ICallGateProvider<List<nint>>? _handledGameAddressesMare;
|
||||
|
||||
private bool _marePluginEnabled = false;
|
||||
private bool _impersonating = false;
|
||||
private DateTime _unregisterTime = DateTime.UtcNow;
|
||||
private CancellationTokenSource _registerDelayCts = new();
|
||||
|
||||
public bool MarePluginEnabled => _marePluginEnabled;
|
||||
public bool ImpersonationActive => _impersonating;
|
||||
|
||||
public MareMediator Mediator { get; init; }
|
||||
|
||||
public IpcProvider(ILogger<IpcProvider> logger, IDalamudPluginInterface pi, MareConfigService mareConfig,
|
||||
CharaDataManager charaDataManager, MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_pi = pi;
|
||||
_mareConfig = mareConfig;
|
||||
_charaDataManager = charaDataManager;
|
||||
Mediator = mareMediator;
|
||||
|
||||
Mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, (msg) =>
|
||||
{
|
||||
if (msg.OwnedObject) return;
|
||||
_activeGameObjectHandlers.Add(msg.GameObjectHandler);
|
||||
});
|
||||
Mediator.Subscribe<GameObjectHandlerDestroyedMessage>(this, (msg) =>
|
||||
{
|
||||
if (msg.OwnedObject) return;
|
||||
_activeGameObjectHandlers.Remove(msg.GameObjectHandler);
|
||||
});
|
||||
|
||||
_marePluginEnabled = PluginWatcherService.GetInitialPluginState(pi, "MareSynchronos")?.IsLoaded ?? false;
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "MareSynchronos", p => {
|
||||
_marePluginEnabled = p.IsLoaded;
|
||||
HandleMareImpersonation(automatic: true);
|
||||
});
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("Starting IpcProvider Service");
|
||||
_loadFileProvider = _pi.GetIpcProvider<string, IGameObject, bool>("ElfSync.LoadMcdf");
|
||||
_loadFileProvider.RegisterFunc(LoadMcdf);
|
||||
_loadFileAsyncProvider = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("ElezenSync.LoadMcdfAsync");
|
||||
_loadFileAsyncProvider.RegisterFunc(LoadMcdfAsync);
|
||||
_handledGameAddresses = _pi.GetIpcProvider<List<nint>>("ElezenSync.GetHandledAddresses");
|
||||
_handledGameAddresses.RegisterFunc(GetHandledAddresses);
|
||||
|
||||
_loadFileProviderMare = _pi.GetIpcProvider<string, IGameObject, bool>("MareSynchronos.LoadMcdf");
|
||||
_loadFileAsyncProviderMare = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("MareSynchronos.LoadMcdfAsync");
|
||||
_handledGameAddressesMare = _pi.GetIpcProvider<List<nint>>("MareSynchronos.GetHandledAddresses");
|
||||
HandleMareImpersonation(automatic: true);
|
||||
|
||||
_logger.LogInformation("Started IpcProviderService");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void HandleMareImpersonation(bool automatic = false)
|
||||
{
|
||||
if (_marePluginEnabled)
|
||||
{
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
_loadFileAsyncProviderMare?.UnregisterFunc();
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
_impersonating = false;
|
||||
_unregisterTime = DateTime.UtcNow;
|
||||
_logger.LogDebug("Unregistered MareSynchronos API");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_mareConfig.Current.MareAPI)
|
||||
{
|
||||
var cancelToken = _registerDelayCts.Token;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// Wait before registering to reduce the chance of a race condition
|
||||
if (automatic)
|
||||
await Task.Delay(5000);
|
||||
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (_marePluginEnabled)
|
||||
{
|
||||
_logger.LogDebug("Not registering MareSynchronos API: Mare plugin is loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
_loadFileProviderMare?.RegisterFunc(LoadMcdf);
|
||||
_loadFileAsyncProviderMare?.RegisterFunc(LoadMcdfAsync);
|
||||
_handledGameAddressesMare?.RegisterFunc(GetHandledAddresses);
|
||||
_impersonating = true;
|
||||
_logger.LogDebug("Registered MareSynchronos API");
|
||||
}, cancelToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_registerDelayCts = _registerDelayCts.CancelRecreate();
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
_loadFileAsyncProviderMare?.UnregisterFunc();
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
_impersonating = false;
|
||||
_unregisterTime = DateTime.UtcNow;
|
||||
_logger.LogDebug("Unregistered MareSynchronos API");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("Stopping IpcProvider Service");
|
||||
_loadFileProvider?.UnregisterFunc();
|
||||
_loadFileAsyncProvider?.UnregisterFunc();
|
||||
_handledGameAddresses?.UnregisterFunc();
|
||||
|
||||
_registerDelayCts.Cancel();
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
_loadFileAsyncProviderMare?.UnregisterFunc();
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
}
|
||||
|
||||
Mediator.UnsubscribeAll(this);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task<bool> LoadMcdfAsync(string path, IGameObject target)
|
||||
{
|
||||
await ApplyFileAsync(path, target).ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool LoadMcdf(string path, IGameObject target)
|
||||
{
|
||||
_ = Task.Run(async () => await ApplyFileAsync(path, target).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ApplyFileAsync(string path, IGameObject target)
|
||||
{
|
||||
_charaDataManager.LoadMcdf(path);
|
||||
await (_charaDataManager.LoadedMcdfHeader ?? Task.CompletedTask).ConfigureAwait(false);
|
||||
_charaDataManager.McdfApplyToTarget(target.Name.TextValue);
|
||||
}
|
||||
|
||||
private List<nint> GetHandledAddresses()
|
||||
{
|
||||
if (!_impersonating)
|
||||
{
|
||||
if ((DateTime.UtcNow - _unregisterTime).TotalSeconds >= 1.0)
|
||||
{
|
||||
_logger.LogWarning("GetHandledAddresses called when it should not be registered");
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return _activeGameObjectHandlers.Where(g => g.Address != nint.Zero).Select(g => g.Address).Distinct().ToList();
|
||||
}
|
||||
}
|
54
MareSynchronos/Interop/Ipc/RedrawManager.cs
Normal file
54
MareSynchronos/Interop/Ipc/RedrawManager.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public class RedrawManager
|
||||
{
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly ConcurrentDictionary<nint, bool> _penumbraRedrawRequests = [];
|
||||
private CancellationTokenSource _disposalCts = new();
|
||||
|
||||
public SemaphoreSlim RedrawSemaphore { get; init; } = new(2, 2);
|
||||
|
||||
public RedrawManager(MareMediator mareMediator, DalamudUtilService dalamudUtil)
|
||||
{
|
||||
_mareMediator = mareMediator;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
}
|
||||
|
||||
public async Task PenumbraRedrawInternalAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, Action<ICharacter> action, CancellationToken token)
|
||||
{
|
||||
_mareMediator.Publish(new PenumbraStartRedrawMessage(handler.Address));
|
||||
|
||||
_penumbraRedrawRequests[handler.Address] = true;
|
||||
|
||||
try
|
||||
{
|
||||
using CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||
using CancellationTokenSource combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, token, _disposalCts.Token);
|
||||
var combinedToken = combinedCts.Token;
|
||||
cancelToken.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, combinedToken).ConfigureAwait(false);
|
||||
|
||||
if (!_disposalCts.Token.IsCancellationRequested)
|
||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, combinedToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_penumbraRedrawRequests[handler.Address] = false;
|
||||
_mareMediator.Publish(new PenumbraEndRedrawMessage(handler.Address));
|
||||
}
|
||||
}
|
||||
|
||||
internal void Cancel()
|
||||
{
|
||||
_disposalCts = _disposalCts.CancelRecreate();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user