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; /// /// Code for spawning mostly taken from https://git.anna.lgbt/anna/OrangeGuidanceTomestone/src/branch/main/client/Vfx.cs /// public unsafe class VfxSpawnManager : DisposableMediatorSubscriberBase { private static readonly byte[] _pool = "Client.System.Scheduler.Instance.VfxObject\0"u8.ToArray(); #region signatures #pragma warning disable CS0649 [Signature("E8 ?? ?? ?? ?? F3 0F 10 35 ?? ?? ?? ?? 48 89 43 08")] private readonly delegate* unmanaged _staticVfxCreate; [Signature("E8 ?? ?? ?? ?? ?? ?? ?? 8B 4A ?? 85 C9")] private readonly delegate* unmanaged _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 _staticVfxRemove; #pragma warning restore CS0649 #endregion public VfxSpawnManager(ILogger logger, IGameInteropProvider gameInteropProvider, MareMediator mareMediator) : base(logger, mareMediator) { gameInteropProvider.InitializeFromAttributes(this); mareMediator.Subscribe(this, (msg) => { ChangeSpawnVisibility(0f); }); mareMediator.Subscribe(this, (msg) => { RestoreSpawnVisiblity(); }); mareMediator.Subscribe(this, (msg) => { ChangeSpawnVisibility(0f); }); mareMediator.Subscribe(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 _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; } }