using Brio.API; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using LightlessSync.API.Dto.CharaData; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.Services; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; using System.Numerics; using System.Text.Json.Nodes; namespace LightlessSync.Interop.Ipc; public sealed class IpcCallerBrio : IpcServiceBase { private static readonly IpcServiceDescriptor BrioDescriptor = new("Brio", "Brio", new Version(3, 0, 0, 0)); private readonly ILogger _logger; private readonly DalamudUtilService _dalamudUtilService; private readonly ApiVersion _apiVersion; private readonly SpawnActor _spawnActor; private readonly DespawnActor _despawnActor; private readonly SetModelTransform _setModelTransform; private readonly GetModelTransform _getModelTransform; private readonly GetPoseAsJson _getPoseAsJson; private readonly LoadPoseFromJson _setPoseFromJson; private readonly FreezeActor _freezeActor; private readonly FreezePhysics _freezePhysics; public IpcCallerBrio(ILogger logger, IDalamudPluginInterface dalamudPluginInterface, DalamudUtilService dalamudUtilService, LightlessMediator mediator) : base(logger, mediator, dalamudPluginInterface, BrioDescriptor) { _logger = logger; _dalamudUtilService = dalamudUtilService; _apiVersion = new ApiVersion(dalamudPluginInterface); _spawnActor = new SpawnActor(dalamudPluginInterface); _despawnActor = new DespawnActor(dalamudPluginInterface); _setModelTransform = new SetModelTransform(dalamudPluginInterface); _getModelTransform = new GetModelTransform(dalamudPluginInterface); _getPoseAsJson = new GetPoseAsJson(dalamudPluginInterface); _setPoseFromJson = new LoadPoseFromJson(dalamudPluginInterface); _freezeActor = new FreezeActor(dalamudPluginInterface); _freezePhysics = new FreezePhysics(dalamudPluginInterface); CheckAPI(); } public async Task SpawnActorAsync() { if (!APIAvailable) return null; _logger.LogDebug("Spawning Brio Actor"); return await _dalamudUtilService.RunOnFrameworkThread(() => _spawnActor.Invoke(Brio.API.Enums.SpawnFlags.Default, true)).ConfigureAwait(false); } public async Task 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(() => _despawnActor.Invoke(gameObject)).ConfigureAwait(false); } public async Task 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(() => _setModelTransform.Invoke(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 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(() => _getModelTransform.Invoke(gameObject)).ConfigureAwait(false); 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 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(() => _getPoseAsJson.Invoke(gameObject)).ConfigureAwait(false); } public async Task 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(() => _getPoseAsJson.Invoke(gameObject)).ConfigureAwait(false); applicablePose["ModelDifference"] = JsonNode.Parse(JsonNode.Parse(currentPose)!["ModelDifference"]!.ToJsonString()); await _dalamudUtilService.RunOnFrameworkThread(() => { _freezeActor.Invoke(gameObject); _freezePhysics.Invoke(); }).ConfigureAwait(false); return await _dalamudUtilService.RunOnFrameworkThread(() => _setPoseFromJson.Invoke(gameObject, applicablePose.ToJsonString(), false)).ConfigureAwait(false); } protected override IpcConnectionState EvaluateState() { var state = base.EvaluateState(); if (state != IpcConnectionState.Available) { return state; } try { var version = _apiVersion.Invoke(); return version.Item1 == 3 && version.Item2 >= 0 ? IpcConnectionState.Available : IpcConnectionState.VersionMismatch; } catch (Exception ex) { _logger.LogDebug(ex, "Failed to query Brio IPC version"); return IpcConnectionState.Error; } } protected override void Dispose(bool disposing) { base.Dispose(disposing); } }