OrangeGuidanceTomestone/client/Vfx.cs

194 lines
5.7 KiB
C#
Raw Normal View History

using System.Diagnostics;
2022-09-03 23:45:16 +00:00
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
2024-07-05 21:24:50 +00:00
using Dalamud.Memory;
using Dalamud.Plugin.Services;
2022-09-03 23:45:16 +00:00
using Dalamud.Utility.Signatures;
2024-07-07 18:49:45 +00:00
using OrangeGuidanceTomestone.Util;
2022-09-03 23:45:16 +00:00
namespace OrangeGuidanceTomestone;
internal unsafe class Vfx : IDisposable {
2024-07-05 21:24:50 +00:00
private static readonly byte[] Pool = "Client.System.Scheduler.Instance.VfxObject\0"u8.ToArray();
2022-09-03 23:45:16 +00:00
[Signature("E8 ?? ?? ?? ?? F3 0F 10 35 ?? ?? ?? ?? 48 89 43 08")]
2024-07-07 18:49:45 +00:00
private readonly delegate* unmanaged<byte*, byte*, VfxStruct*> _staticVfxCreate;
2022-09-03 23:45:16 +00:00
[Signature("E8 ?? ?? ?? ?? 8B 4B 7C 85 C9")]
2024-07-07 18:49:45 +00:00
private readonly delegate* unmanaged<VfxStruct*, float, int, ulong> _staticVfxRun;
2022-09-03 23:45:16 +00:00
[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")]
2024-07-07 18:49:45 +00:00
private readonly delegate* unmanaged<VfxStruct*, nint> _staticVfxRemove;
2022-09-03 23:45:16 +00:00
2024-07-05 21:24:50 +00:00
private Plugin Plugin { get; }
internal SemaphoreSlim Mutex { get; } = new(1, 1);
internal Dictionary<Guid, nint> Spawned { get; } = [];
2024-07-07 18:49:45 +00:00
private Queue<IQueueAction> Queue { get; } = [];
2024-07-05 21:24:50 +00:00
private bool _disposed;
private readonly Stopwatch _queueTimer = Stopwatch.StartNew();
2024-07-07 18:49:45 +00:00
2023-09-29 00:53:50 +00:00
internal Vfx(Plugin plugin) {
2024-07-05 21:24:50 +00:00
this.Plugin = plugin;
this.Plugin.GameInteropProvider.InitializeFromAttributes(this);
2024-07-07 18:49:45 +00:00
this.Plugin.Framework.Update += this.HandleQueues;
2022-09-03 23:45:16 +00:00
}
public void Dispose() {
2024-07-05 21:24:50 +00:00
if (this._disposed) {
return;
}
this._disposed = true;
2024-07-07 18:49:45 +00:00
this.Plugin.Framework.Update -= this.HandleQueues;
this.RemoveAllSync();
2022-09-03 23:45:16 +00:00
}
2024-07-07 18:49:45 +00:00
private void HandleQueues(IFramework framework) {
this._queueTimer.Restart();
2022-09-03 23:45:16 +00:00
while (this._queueTimer.Elapsed < TimeSpan.FromMilliseconds(1)) {
if (!this.Queue.TryDequeue(out var action)) {
return;
2024-07-07 18:49:45 +00:00
}
switch (action) {
case AddQueueAction add: {
using var guard = this.Mutex.With();
Plugin.Log.Debug($"adding vfx for {add.Id}");
if (this.Spawned.Remove(add.Id, out var existing)) {
Plugin.Log.Warning($"vfx for {add.Id} already exists, queuing remove");
this.Queue.Enqueue(new RemoveRawQueueAction(existing));
}
var vfx = this.SpawnStatic(add.Path, add.Position, add.Rotation);
this.Spawned[add.Id] = (nint) vfx;
2024-07-07 18:49:45 +00:00
break;
}
case RemoveQueueAction remove: {
using var guard = this.Mutex.With();
Plugin.Log.Debug($"removing vfx for {remove.Id}");
if (!this.Spawned.Remove(remove.Id, out var ptr)) {
break;
}
2024-07-07 18:49:45 +00:00
this.RemoveStatic((VfxStruct*) ptr);
break;
}
;
case RemoveRawQueueAction remove: {
Plugin.Log.Debug($"removing raw vfx at {remove.Pointer:X}");
this.RemoveStatic((VfxStruct*) remove.Pointer);
break;
}
2024-07-07 18:49:45 +00:00
}
2024-07-05 21:24:50 +00:00
}
2024-07-07 18:49:45 +00:00
}
internal void RemoveAllSync() {
using var guard = this.Mutex.With();
2024-07-05 21:24:50 +00:00
2024-07-07 18:49:45 +00:00
foreach (var spawned in this.Spawned.Values.ToArray()) {
this.RemoveStatic((VfxStruct*) spawned);
2024-07-05 21:24:50 +00:00
}
2024-07-07 18:49:45 +00:00
this.Spawned.Clear();
}
internal void QueueSpawn(Guid id, string path, Vector3 pos, Quaternion rotation) {
using var guard = this.Mutex.With();
this.Queue.Enqueue(new AddQueueAction(id, path, pos, rotation));
}
internal void QueueRemove(Guid id) {
using var guard = this.Mutex.With();
this.Queue.Enqueue(new RemoveQueueAction(id));
2024-07-05 21:24:50 +00:00
}
2024-07-07 18:49:45 +00:00
internal void QueueRemoveAll() {
using var guard = this.Mutex.With();
foreach (var id in this.Spawned.Keys) {
this.Queue.Enqueue(new RemoveQueueAction(id));
2024-07-05 21:24:50 +00:00
}
2022-09-03 23:45:16 +00:00
}
private VfxStruct* SpawnStatic(string path, Vector3 pos, Quaternion rotation) {
2022-09-03 23:45:16 +00:00
VfxStruct* vfx;
2024-07-05 21:24:50 +00:00
fixed (byte* p = Encoding.UTF8.GetBytes(path).NullTerminate()) {
2022-09-03 23:45:16 +00:00
fixed (byte* pool = Pool) {
vfx = this._staticVfxCreate(p, pool);
}
}
if (vfx == null) {
return null;
}
// update position
vfx->Position = new Vector3(pos.X, pos.Y, pos.Z);
2022-09-04 21:02:13 +00:00
// update rotation
vfx->Rotation = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W);
2022-09-03 23:45:16 +00:00
// remove flag that sometimes causes vfx to not appear?
vfx->SomeFlags &= 0xF7;
2022-09-03 23:45:16 +00:00
// update
vfx->Flags |= 2;
2024-07-05 21:24:50 +00:00
this._staticVfxRun(vfx, 0.0f, -1);
2022-09-03 23:45:16 +00:00
return vfx;
}
2024-07-07 18:49:45 +00:00
private void RemoveStatic(VfxStruct* vfx) {
this._staticVfxRemove(vfx);
2022-09-04 05:15:27 +00:00
}
2022-09-03 23:45:16 +00:00
[StructLayout(LayoutKind.Explicit)]
internal struct VfxStruct {
[FieldOffset(0x38)]
public byte Flags;
[FieldOffset(0x50)]
public Vector3 Position;
2022-09-04 21:02:13 +00:00
[FieldOffset(0x60)]
public Quaternion Rotation;
2022-09-03 23:45:16 +00:00
[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;
2022-09-03 23:45:16 +00:00
}
}
2024-07-07 18:49:45 +00:00
internal interface IQueueAction;
internal sealed record AddQueueAction(
Guid Id,
string Path,
Vector3 Position,
Quaternion Rotation
) : IQueueAction;
internal sealed record RemoveQueueAction(Guid Id) : IQueueAction;
internal sealed record RemoveRawQueueAction(nint Pointer) : IQueueAction;