Add project files.
This commit is contained in:
42
MareSynchronos/Interop/BlockedCharacterHandler.cs
Normal file
42
MareSynchronos/Interop/BlockedCharacterHandler.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
public unsafe class BlockedCharacterHandler
|
||||
{
|
||||
private sealed record CharaData(ulong AccId, ulong ContentId);
|
||||
private readonly Dictionary<CharaData, bool> _blockedCharacterCache = new();
|
||||
|
||||
private readonly ILogger<BlockedCharacterHandler> _logger;
|
||||
|
||||
public BlockedCharacterHandler(ILogger<BlockedCharacterHandler> logger, IGameInteropProvider gameInteropProvider)
|
||||
{
|
||||
gameInteropProvider.InitializeFromAttributes(this);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static CharaData GetIdsFromPlayerPointer(nint ptr)
|
||||
{
|
||||
if (ptr == nint.Zero) return new(0, 0);
|
||||
var castChar = ((BattleChara*)ptr);
|
||||
return new(castChar->Character.AccountId, castChar->Character.ContentId);
|
||||
}
|
||||
|
||||
public bool IsCharacterBlocked(nint ptr, out bool firstTime)
|
||||
{
|
||||
firstTime = false;
|
||||
var combined = GetIdsFromPlayerPointer(ptr);
|
||||
if (_blockedCharacterCache.TryGetValue(combined, out var isBlocked))
|
||||
return isBlocked;
|
||||
|
||||
firstTime = true;
|
||||
var blockStatus = InfoProxyBlacklist.Instance()->GetBlockResultType(combined.AccId, combined.ContentId);
|
||||
_logger.LogTrace("CharaPtr {ptr} is BlockStatus: {status}", ptr, blockStatus);
|
||||
if ((int)blockStatus == 0)
|
||||
return false;
|
||||
return _blockedCharacterCache[combined] = blockStatus != InfoProxyBlacklist.BlockResultType.NotBlocked;
|
||||
}
|
||||
}
|
||||
59
MareSynchronos/Interop/DalamudLogger.cs
Normal file
59
MareSynchronos/Interop/DalamudLogger.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
internal sealed class DalamudLogger : ILogger
|
||||
{
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly string _name;
|
||||
private readonly IPluginLog _pluginLog;
|
||||
private readonly bool _hasModifiedGameFiles;
|
||||
|
||||
public DalamudLogger(string name, MareConfigService mareConfigService, IPluginLog pluginLog, bool hasModifiedGameFiles)
|
||||
{
|
||||
_name = name;
|
||||
_mareConfigService = mareConfigService;
|
||||
_pluginLog = pluginLog;
|
||||
_hasModifiedGameFiles = hasModifiedGameFiles;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => default!;
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return (int)_mareConfigService.Current.LogLevel <= (int)logLevel;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel)) return;
|
||||
|
||||
string unsupported = _hasModifiedGameFiles ? "[UNSUPPORTED]" : string.Empty;
|
||||
|
||||
if ((int)logLevel <= (int)LogLevel.Information)
|
||||
_pluginLog.Information($"{unsupported}[{_name}]{{{(int)logLevel}}} {state}{(_hasModifiedGameFiles ? "." : string.Empty)}");
|
||||
else
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.Append($"{unsupported}[{_name}]{{{(int)logLevel}}} {state}{(_hasModifiedGameFiles ? "." : string.Empty)} {exception?.Message}");
|
||||
if (!string.IsNullOrWhiteSpace(exception?.StackTrace))
|
||||
sb.AppendLine(exception?.StackTrace);
|
||||
var innerException = exception?.InnerException;
|
||||
while (innerException != null)
|
||||
{
|
||||
sb.AppendLine($"InnerException {innerException}: {innerException.Message}");
|
||||
sb.AppendLine(innerException.StackTrace);
|
||||
innerException = innerException.InnerException;
|
||||
}
|
||||
if (logLevel == LogLevel.Warning)
|
||||
_pluginLog.Warning(sb.ToString());
|
||||
else if (logLevel == LogLevel.Error)
|
||||
_pluginLog.Error(sb.ToString());
|
||||
else
|
||||
_pluginLog.Fatal(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
46
MareSynchronos/Interop/DalamudLoggingProvider.cs
Normal file
46
MareSynchronos/Interop/DalamudLoggingProvider.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
[ProviderAlias("Dalamud")]
|
||||
public sealed class DalamudLoggingProvider : ILoggerProvider
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, DalamudLogger> _loggers =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly IPluginLog _pluginLog;
|
||||
private readonly bool _hasModifiedGameFiles;
|
||||
|
||||
public DalamudLoggingProvider(MareConfigService mareConfigService, IPluginLog pluginLog, bool hasModifiedGameFiles)
|
||||
{
|
||||
_mareConfigService = mareConfigService;
|
||||
_pluginLog = pluginLog;
|
||||
_hasModifiedGameFiles = hasModifiedGameFiles;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
string catName = categoryName.Split(".", StringSplitOptions.RemoveEmptyEntries).Last();
|
||||
if (catName.Length > 15)
|
||||
{
|
||||
catName = string.Join("", catName.Take(6)) + "..." + string.Join("", catName.TakeLast(6));
|
||||
}
|
||||
else
|
||||
{
|
||||
catName = string.Join("", Enumerable.Range(0, 15 - catName.Length).Select(_ => " ")) + catName;
|
||||
}
|
||||
|
||||
return _loggers.GetOrAdd(catName, name => new DalamudLogger(name, _mareConfigService, _pluginLog, _hasModifiedGameFiles));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loggers.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
19
MareSynchronos/Interop/DalamudLoggingProviderExtensions.cs
Normal file
19
MareSynchronos/Interop/DalamudLoggingProviderExtensions.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
public static class DalamudLoggingProviderExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddDalamudLogging(this ILoggingBuilder builder, IPluginLog pluginLog, bool hasModifiedGameFiles)
|
||||
{
|
||||
builder.ClearProviders();
|
||||
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DalamudLoggingProvider>
|
||||
(b => new DalamudLoggingProvider(b.GetRequiredService<MareConfigService>(), pluginLog, hasModifiedGameFiles)));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
257
MareSynchronos/Interop/GameModel/MdlFile.cs
Normal file
257
MareSynchronos/Interop/GameModel/MdlFile.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using Lumina.Data;
|
||||
using Lumina.Extensions;
|
||||
using System.Text;
|
||||
using static Lumina.Data.Parsing.MdlStructs;
|
||||
|
||||
namespace MareSynchronos.Interop.GameModel;
|
||||
|
||||
#pragma warning disable S1104 // Fields should not have public accessibility
|
||||
|
||||
// This code is completely and shamelessly borrowed from Penumbra to load V5 and V6 model files.
|
||||
// Original Source: https://github.com/Ottermandias/Penumbra.GameData/blob/main/Files/MdlFile.cs
|
||||
public class MdlFile
|
||||
{
|
||||
public const int V5 = 0x01000005;
|
||||
public const int V6 = 0x01000006;
|
||||
public const uint NumVertices = 17;
|
||||
public const uint FileHeaderSize = 0x44;
|
||||
|
||||
// Raw data to write back.
|
||||
public uint Version = 0x01000005;
|
||||
public float Radius;
|
||||
public float ModelClipOutDistance;
|
||||
public float ShadowClipOutDistance;
|
||||
public byte BgChangeMaterialIndex;
|
||||
public byte BgCrestChangeMaterialIndex;
|
||||
public ushort CullingGridCount;
|
||||
public byte Flags3;
|
||||
public byte Unknown6;
|
||||
public ushort Unknown8;
|
||||
public ushort Unknown9;
|
||||
|
||||
// Offsets are stored relative to RuntimeSize instead of file start.
|
||||
public uint[] VertexOffset = [0, 0, 0];
|
||||
public uint[] IndexOffset = [0, 0, 0];
|
||||
|
||||
public uint[] VertexBufferSize = [0, 0, 0];
|
||||
public uint[] IndexBufferSize = [0, 0, 0];
|
||||
public byte LodCount;
|
||||
public bool EnableIndexBufferStreaming;
|
||||
public bool EnableEdgeGeometry;
|
||||
|
||||
public ModelFlags1 Flags1;
|
||||
public ModelFlags2 Flags2;
|
||||
|
||||
public VertexDeclarationStruct[] VertexDeclarations = [];
|
||||
public ElementIdStruct[] ElementIds = [];
|
||||
public MeshStruct[] Meshes = [];
|
||||
public BoundingBoxStruct[] BoneBoundingBoxes = [];
|
||||
public LodStruct[] Lods = [];
|
||||
public ExtraLodStruct[] ExtraLods = [];
|
||||
|
||||
public MdlFile(string filePath)
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using var r = new LuminaBinaryReader(stream);
|
||||
|
||||
var header = LoadModelFileHeader(r);
|
||||
LodCount = header.LodCount;
|
||||
VertexBufferSize = header.VertexBufferSize;
|
||||
IndexBufferSize = header.IndexBufferSize;
|
||||
VertexOffset = header.VertexOffset;
|
||||
IndexOffset = header.IndexOffset;
|
||||
|
||||
var dataOffset = FileHeaderSize + header.RuntimeSize + header.StackSize;
|
||||
for (var i = 0; i < LodCount; ++i)
|
||||
{
|
||||
VertexOffset[i] -= dataOffset;
|
||||
IndexOffset[i] -= dataOffset;
|
||||
}
|
||||
|
||||
VertexDeclarations = new VertexDeclarationStruct[header.VertexDeclarationCount];
|
||||
for (var i = 0; i < header.VertexDeclarationCount; ++i)
|
||||
VertexDeclarations[i] = VertexDeclarationStruct.Read(r);
|
||||
|
||||
_ = LoadStrings(r);
|
||||
|
||||
var modelHeader = LoadModelHeader(r);
|
||||
ElementIds = new ElementIdStruct[modelHeader.ElementIdCount];
|
||||
for (var i = 0; i < modelHeader.ElementIdCount; i++)
|
||||
ElementIds[i] = ElementIdStruct.Read(r);
|
||||
|
||||
Lods = new LodStruct[3];
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var lod = r.ReadStructure<LodStruct>();
|
||||
if (i < LodCount)
|
||||
{
|
||||
lod.VertexDataOffset -= dataOffset;
|
||||
lod.IndexDataOffset -= dataOffset;
|
||||
}
|
||||
|
||||
Lods[i] = lod;
|
||||
}
|
||||
|
||||
ExtraLods = (modelHeader.Flags2 & ModelFlags2.ExtraLodEnabled) != 0
|
||||
? r.ReadStructuresAsArray<ExtraLodStruct>(3)
|
||||
: [];
|
||||
|
||||
Meshes = new MeshStruct[modelHeader.MeshCount];
|
||||
for (var i = 0; i < modelHeader.MeshCount; i++)
|
||||
Meshes[i] = MeshStruct.Read(r);
|
||||
}
|
||||
|
||||
private ModelFileHeader LoadModelFileHeader(LuminaBinaryReader r)
|
||||
{
|
||||
var header = ModelFileHeader.Read(r);
|
||||
Version = header.Version;
|
||||
EnableIndexBufferStreaming = header.EnableIndexBufferStreaming;
|
||||
EnableEdgeGeometry = header.EnableEdgeGeometry;
|
||||
return header;
|
||||
}
|
||||
|
||||
private ModelHeader LoadModelHeader(BinaryReader r)
|
||||
{
|
||||
var modelHeader = r.ReadStructure<ModelHeader>();
|
||||
Radius = modelHeader.Radius;
|
||||
Flags1 = modelHeader.Flags1;
|
||||
Flags2 = modelHeader.Flags2;
|
||||
ModelClipOutDistance = modelHeader.ModelClipOutDistance;
|
||||
ShadowClipOutDistance = modelHeader.ShadowClipOutDistance;
|
||||
CullingGridCount = modelHeader.CullingGridCount;
|
||||
Flags3 = modelHeader.Flags3;
|
||||
Unknown6 = modelHeader.Unknown6;
|
||||
Unknown8 = modelHeader.Unknown8;
|
||||
Unknown9 = modelHeader.Unknown9;
|
||||
BgChangeMaterialIndex = modelHeader.BGChangeMaterialIndex;
|
||||
BgCrestChangeMaterialIndex = modelHeader.BGCrestChangeMaterialIndex;
|
||||
|
||||
return modelHeader;
|
||||
}
|
||||
|
||||
private static (uint[], string[]) LoadStrings(BinaryReader r)
|
||||
{
|
||||
var stringCount = r.ReadUInt16();
|
||||
r.ReadUInt16();
|
||||
var stringSize = (int)r.ReadUInt32();
|
||||
var stringData = r.ReadBytes(stringSize);
|
||||
var start = 0;
|
||||
var strings = new string[stringCount];
|
||||
var offsets = new uint[stringCount];
|
||||
for (var i = 0; i < stringCount; ++i)
|
||||
{
|
||||
var span = stringData.AsSpan(start);
|
||||
var idx = span.IndexOf((byte)'\0');
|
||||
strings[i] = Encoding.UTF8.GetString(span[..idx]);
|
||||
offsets[i] = (uint)start;
|
||||
start = start + idx + 1;
|
||||
}
|
||||
|
||||
return (offsets, strings);
|
||||
}
|
||||
|
||||
public unsafe struct ModelHeader
|
||||
{
|
||||
// MeshHeader
|
||||
public float Radius;
|
||||
public ushort MeshCount;
|
||||
public ushort AttributeCount;
|
||||
public ushort SubmeshCount;
|
||||
public ushort MaterialCount;
|
||||
public ushort BoneCount;
|
||||
public ushort BoneTableCount;
|
||||
public ushort ShapeCount;
|
||||
public ushort ShapeMeshCount;
|
||||
public ushort ShapeValueCount;
|
||||
public byte LodCount;
|
||||
public ModelFlags1 Flags1;
|
||||
public ushort ElementIdCount;
|
||||
public byte TerrainShadowMeshCount;
|
||||
public ModelFlags2 Flags2;
|
||||
public float ModelClipOutDistance;
|
||||
public float ShadowClipOutDistance;
|
||||
public ushort CullingGridCount;
|
||||
public ushort TerrainShadowSubmeshCount;
|
||||
public byte Flags3;
|
||||
public byte BGChangeMaterialIndex;
|
||||
public byte BGCrestChangeMaterialIndex;
|
||||
public byte Unknown6;
|
||||
public ushort BoneTableArrayCountTotal;
|
||||
public ushort Unknown8;
|
||||
public ushort Unknown9;
|
||||
private fixed byte _padding[6];
|
||||
}
|
||||
|
||||
public struct ShapeStruct
|
||||
{
|
||||
public uint StringOffset;
|
||||
public ushort[] ShapeMeshStartIndex;
|
||||
public ushort[] ShapeMeshCount;
|
||||
|
||||
public static ShapeStruct Read(LuminaBinaryReader br)
|
||||
{
|
||||
ShapeStruct ret = new ShapeStruct();
|
||||
ret.StringOffset = br.ReadUInt32();
|
||||
ret.ShapeMeshStartIndex = br.ReadUInt16Array(3);
|
||||
ret.ShapeMeshCount = br.ReadUInt16Array(3);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ModelFlags1 : byte
|
||||
{
|
||||
DustOcclusionEnabled = 0x80,
|
||||
SnowOcclusionEnabled = 0x40,
|
||||
RainOcclusionEnabled = 0x20,
|
||||
Unknown1 = 0x10,
|
||||
LightingReflectionEnabled = 0x08,
|
||||
WavingAnimationDisabled = 0x04,
|
||||
LightShadowDisabled = 0x02,
|
||||
ShadowDisabled = 0x01,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ModelFlags2 : byte
|
||||
{
|
||||
Unknown2 = 0x80,
|
||||
BgUvScrollEnabled = 0x40,
|
||||
EnableForceNonResident = 0x20,
|
||||
ExtraLodEnabled = 0x10,
|
||||
ShadowMaskEnabled = 0x08,
|
||||
ForceLodRangeEnabled = 0x04,
|
||||
EdgeGeometryEnabled = 0x02,
|
||||
Unknown3 = 0x01
|
||||
}
|
||||
|
||||
public struct VertexDeclarationStruct
|
||||
{
|
||||
// There are always 17, but stop when stream = -1
|
||||
public VertexElement[] VertexElements;
|
||||
|
||||
public static VertexDeclarationStruct Read(LuminaBinaryReader br)
|
||||
{
|
||||
VertexDeclarationStruct ret = new VertexDeclarationStruct();
|
||||
|
||||
var elems = new List<VertexElement>();
|
||||
|
||||
// Read the vertex elements that we need
|
||||
var thisElem = br.ReadStructure<VertexElement>();
|
||||
do
|
||||
{
|
||||
elems.Add(thisElem);
|
||||
thisElem = br.ReadStructure<VertexElement>();
|
||||
} while (thisElem.Stream != 255);
|
||||
|
||||
// Skip the number of bytes that we don't need to read
|
||||
// We skip elems.Count * 9 because we had to read the invalid element
|
||||
int toSeek = 17 * 8 - (elems.Count + 1) * 8;
|
||||
br.Seek(br.BaseStream.Position + toSeek);
|
||||
|
||||
ret.VertexElements = elems.ToArray();
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore S1104 // Fields should not have public accessibility
|
||||
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();
|
||||
}
|
||||
146
MareSynchronos/Interop/Ipc/IpcCallerBrio.cs
Normal file
146
MareSynchronos/Interop/Ipc/IpcCallerBrio.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
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);
|
||||
//_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);
|
||||
}
|
||||
}
|
||||
217
MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs
Normal file
217
MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
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 IDalamudPluginInterface _pi;
|
||||
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 _shownGlamourerUnavailable = false;
|
||||
private readonly uint LockCode = 0x6D617265;
|
||||
|
||||
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;
|
||||
_pi = pi;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
_redrawManager = redrawManager;
|
||||
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 = (_pi.InstalledPlugins
|
||||
.FirstOrDefault(p => string.Equals(p.InternalName, "Glamourer", StringComparison.OrdinalIgnoreCase))
|
||||
?.Version ?? new Version(0, 0, 0, 0)) >= new Version(1, 3, 0, 10);
|
||||
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 Mare. 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 fireAndForget = false)
|
||||
{
|
||||
if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) 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: GlamourerUnlockName", 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 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: >= 1 };
|
||||
}
|
||||
catch
|
||||
{
|
||||
APIAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||
}
|
||||
}
|
||||
132
MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs
Normal file
132
MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
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: >= 1 };
|
||||
}
|
||||
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;
|
||||
string title = await _dalamudUtil.RunOnFrameworkThread(() => _honorificGetLocalCharacterTitle.InvokeFunc()).ConfigureAwait(false);
|
||||
return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
343
MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs
Normal file
343
MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs
Normal file
@@ -0,0 +1,343 @@
|
||||
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 IDalamudPluginInterface _pi;
|
||||
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;
|
||||
|
||||
public IpcCallerPenumbra(ILogger<IpcCallerPenumbra> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||
MareMediator mareMediator, RedrawManager redrawManager) : base(logger, mareMediator)
|
||||
{
|
||||
_pi = pi;
|
||||
_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);
|
||||
|
||||
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
|
||||
{
|
||||
var penumbraVersion = (_pi.InstalledPlugins
|
||||
.FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase))
|
||||
?.Version ?? new Version(0, 0, 0, 0));
|
||||
penumbraAvailable = penumbraVersion >= new Version(1, 2, 0, 22);
|
||||
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 Mare. 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 = "Mare_" + 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 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);
|
||||
}
|
||||
}
|
||||
62
MareSynchronos/Interop/Ipc/IpcManager.cs
Normal file
62
MareSynchronos/Interop/Ipc/IpcManager.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
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 void PeriodicApiStateCheck()
|
||||
{
|
||||
Penumbra.CheckAPI();
|
||||
Penumbra.CheckModDirectory();
|
||||
Glamourer.CheckAPI();
|
||||
Heels.CheckAPI();
|
||||
CustomizePlus.CheckAPI();
|
||||
Honorific.CheckAPI();
|
||||
Moodles.CheckAPI();
|
||||
PetNames.CheckAPI();
|
||||
Brio.CheckAPI();
|
||||
}
|
||||
}
|
||||
92
MareSynchronos/Interop/Ipc/IpcProvider.cs
Normal file
92
MareSynchronos/Interop/Ipc/IpcProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
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 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 = [];
|
||||
|
||||
public MareMediator Mediator { get; init; }
|
||||
|
||||
public IpcProvider(ILogger<IpcProvider> logger, IDalamudPluginInterface pi,
|
||||
CharaDataManager charaDataManager, MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_pi = pi;
|
||||
_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);
|
||||
});
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Starting IpcProviderService");
|
||||
_loadFileProvider = _pi.GetIpcProvider<string, IGameObject, bool>("MareSynchronos.LoadMcdf");
|
||||
_loadFileProvider.RegisterFunc(LoadMcdf);
|
||||
_loadFileAsyncProvider = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("MareSynchronos.LoadMcdfAsync");
|
||||
_loadFileAsyncProvider.RegisterFunc(LoadMcdfAsync);
|
||||
_handledGameAddresses = _pi.GetIpcProvider<List<nint>>("MareSynchronos.GetHandledAddresses");
|
||||
_handledGameAddresses.RegisterFunc(GetHandledAddresses);
|
||||
_logger.LogInformation("Started IpcProviderService");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("Stopping IpcProvider Service");
|
||||
_loadFileProvider?.UnregisterFunc();
|
||||
_loadFileAsyncProvider?.UnregisterFunc();
|
||||
_handledGameAddresses?.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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
199
MareSynchronos/Interop/VfxSpawnManager.cs
Normal file
199
MareSynchronos/Interop/VfxSpawnManager.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Code for spawning mostly taken from https://git.anna.lgbt/anna/OrangeGuidanceTomestone/src/branch/main/client/Vfx.cs
|
||||
/// </summary>
|
||||
public unsafe class VfxSpawnManager : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private static readonly byte[] _pool = "Client.System.Scheduler.Instance.VfxObject\0"u8.ToArray();
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? F3 0F 10 35 ?? ?? ?? ?? 48 89 43 08")]
|
||||
private readonly delegate* unmanaged<byte*, byte*, VfxStruct*> _staticVfxCreate;
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? ?? ?? ?? 8B 4A ?? 85 C9")]
|
||||
private readonly delegate* unmanaged<VfxStruct*, float, int, ulong> _staticVfxRun;
|
||||
|
||||
[Signature("40 53 48 83 EC 20 48 8B D9 48 8B 89 ?? ?? ?? ?? 48 85 C9 74 28 33 D2 E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9")]
|
||||
private readonly delegate* unmanaged<VfxStruct*, nint> _staticVfxRemove;
|
||||
|
||||
public VfxSpawnManager(ILogger<VfxSpawnManager> logger, IGameInteropProvider gameInteropProvider, MareMediator mareMediator)
|
||||
: base(logger, mareMediator)
|
||||
{
|
||||
gameInteropProvider.InitializeFromAttributes(this);
|
||||
mareMediator.Subscribe<GposeStartMessage>(this, (msg) =>
|
||||
{
|
||||
ChangeSpawnVisibility(0f);
|
||||
});
|
||||
mareMediator.Subscribe<GposeEndMessage>(this, (msg) =>
|
||||
{
|
||||
RestoreSpawnVisiblity();
|
||||
});
|
||||
mareMediator.Subscribe<CutsceneStartMessage>(this, (msg) =>
|
||||
{
|
||||
ChangeSpawnVisibility(0f);
|
||||
});
|
||||
mareMediator.Subscribe<CutsceneEndMessage>(this, (msg) =>
|
||||
{
|
||||
RestoreSpawnVisiblity();
|
||||
});
|
||||
}
|
||||
|
||||
private unsafe void RestoreSpawnVisiblity()
|
||||
{
|
||||
foreach (var vfx in _spawnedObjects)
|
||||
{
|
||||
((VfxStruct*)vfx.Value.Address)->Alpha = vfx.Value.Visibility;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void ChangeSpawnVisibility(float visibility)
|
||||
{
|
||||
foreach (var vfx in _spawnedObjects)
|
||||
{
|
||||
((VfxStruct*)vfx.Value.Address)->Alpha = visibility;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Guid, (nint Address, float Visibility)> _spawnedObjects = [];
|
||||
|
||||
private VfxStruct* SpawnStatic(string path, Vector3 pos, Quaternion rotation, float r, float g, float b, float a, Vector3 scale)
|
||||
{
|
||||
VfxStruct* vfx;
|
||||
fixed (byte* terminatedPath = Encoding.UTF8.GetBytes(path).NullTerminate())
|
||||
{
|
||||
fixed (byte* pool = _pool)
|
||||
{
|
||||
vfx = _staticVfxCreate(terminatedPath, pool);
|
||||
}
|
||||
}
|
||||
|
||||
if (vfx == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
vfx->Position = new Vector3(pos.X, pos.Y + 1, pos.Z);
|
||||
vfx->Rotation = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W);
|
||||
|
||||
vfx->SomeFlags &= 0xF7;
|
||||
vfx->Flags |= 2;
|
||||
vfx->Red = r;
|
||||
vfx->Green = g;
|
||||
vfx->Blue = b;
|
||||
vfx->Scale = scale;
|
||||
|
||||
vfx->Alpha = a;
|
||||
|
||||
_staticVfxRun(vfx, 0.0f, -1);
|
||||
|
||||
return vfx;
|
||||
}
|
||||
|
||||
public Guid? SpawnObject(Vector3 position, Quaternion rotation, Vector3 scale, float r = 1f, float g = 1f, float b = 1f, float a = 0.5f)
|
||||
{
|
||||
Logger.LogDebug("Trying to Spawn orb VFX at {pos}, {rot}", position, rotation);
|
||||
var vfx = SpawnStatic("bgcommon/world/common/vfx_for_event/eff/b0150_eext_y.avfx", position, rotation, r, g, b, a, scale);
|
||||
if (vfx == null || (nint)vfx == nint.Zero)
|
||||
{
|
||||
Logger.LogDebug("Failed to Spawn VFX at {pos}, {rot}", position, rotation);
|
||||
return null;
|
||||
}
|
||||
Guid guid = Guid.NewGuid();
|
||||
Logger.LogDebug("Spawned VFX at {pos}, {rot}: 0x{ptr:X}", position, rotation, (nint)vfx);
|
||||
|
||||
_spawnedObjects[guid] = ((nint)vfx, a);
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
public unsafe void MoveObject(Guid id, Vector3 newPosition)
|
||||
{
|
||||
if (_spawnedObjects.TryGetValue(id, out var vfxValue))
|
||||
{
|
||||
if (vfxValue.Address == nint.Zero) return;
|
||||
var vfx = (VfxStruct*)vfxValue.Address;
|
||||
vfx->Position = newPosition with { Y = newPosition.Y + 1 };
|
||||
vfx->Flags |= 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void DespawnObject(Guid? id)
|
||||
{
|
||||
if (id == null) return;
|
||||
if (_spawnedObjects.Remove(id.Value, out var value))
|
||||
{
|
||||
Logger.LogDebug("Despawning {obj:X}", value.Address);
|
||||
_staticVfxRemove((VfxStruct*)value.Address);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllVfx()
|
||||
{
|
||||
foreach (var obj in _spawnedObjects.Values)
|
||||
{
|
||||
Logger.LogDebug("Despawning {obj:X}", obj);
|
||||
_staticVfxRemove((VfxStruct*)obj.Address);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
{
|
||||
RemoveAllVfx();
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct VfxStruct
|
||||
{
|
||||
[FieldOffset(0x38)]
|
||||
public byte Flags;
|
||||
|
||||
[FieldOffset(0x50)]
|
||||
public Vector3 Position;
|
||||
|
||||
[FieldOffset(0x60)]
|
||||
public Quaternion Rotation;
|
||||
|
||||
[FieldOffset(0x70)]
|
||||
public Vector3 Scale;
|
||||
|
||||
[FieldOffset(0x128)]
|
||||
public int ActorCaster;
|
||||
|
||||
[FieldOffset(0x130)]
|
||||
public int ActorTarget;
|
||||
|
||||
[FieldOffset(0x1B8)]
|
||||
public int StaticCaster;
|
||||
|
||||
[FieldOffset(0x1C0)]
|
||||
public int StaticTarget;
|
||||
|
||||
[FieldOffset(0x248)]
|
||||
public byte SomeFlags;
|
||||
|
||||
[FieldOffset(0x260)]
|
||||
public float Red;
|
||||
|
||||
[FieldOffset(0x264)]
|
||||
public float Green;
|
||||
|
||||
[FieldOffset(0x268)]
|
||||
public float Blue;
|
||||
|
||||
[FieldOffset(0x26C)]
|
||||
public float Alpha;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user