using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using Penumbra.Collections; using Penumbra.GameData; using Penumbra.GameData.Interop; using Penumbra.Interop.PathResolving; using Penumbra.Meta; namespace Penumbra.Interop.Hooks.PostProcessing; /// /// Triggered whenever a model recomputes its attribute masks. /// /// Parameter is the game object that recomputed its attributes. /// Parameter is the draw object on which the recomputation was called. /// Parameter is the collection associated with the game object. /// Parameter is the slot that was recomputed. If this is Unknown, it is a general new update call. /// public sealed unsafe class AttributeHook : EventWrapper, Luna.IHookService { public enum Priority { /// ShapeAttributeManager = 0, } private readonly CollectionResolver _resolver; private readonly Configuration _config; public AttributeHook(Luna.HookManager hooks, Configuration config, CollectionResolver resolver) : base("Update Model Attributes") { _config = config; _resolver = resolver; _task = hooks.CreateHook(Name, Sigs.UpdateAttributes, Detour, config.EnableCustomShapes); } private readonly Task> _task; public nint Address => _task.Result.Address; public void Enable() => SetState(true); public void Disable() => SetState(false); public void SetState(bool enabled) { if (_config.EnableCustomShapes == enabled) return; _config.EnableCustomShapes = enabled; _config.Save(); if (enabled) _task.Result.Enable(); else _task.Result.Disable(); } public Task Awaiter => _task; public bool Finished => _task.IsCompletedSuccessfully; private delegate void Delegate(Human* human); private void Detour(Human* human) { _task.Result.Original(human); var resolveData = _resolver.IdentifyCollection((DrawObject*)human, true); var identifiedActor = resolveData.AssociatedGameObject; var identifiedCollection = resolveData.ModCollection; Penumbra.Log.Excessive($"[{Name}] Invoked on 0x{(ulong)human:X} (0x{identifiedActor:X})."); Invoke(identifiedActor, human, identifiedCollection); } protected override void Dispose(bool disposing) => _task.Result.Dispose(); }