using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Memory; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Info; using Dalamud.Bindings.ImGui; using System; namespace HSUI.Interface.Party { public enum ReadyCheckStatus { Ready = 0, NotReady = 1, None = 2 } public class PartyReadyCheckHelper : IDisposable { private delegate void ReadyCheckDelegate(IntPtr ptr); private Hook? _onReadyCheckStartHook; private Hook? _onReadyCheckEndHook; private delegate void ActorControlDelegate(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12); private Hook? _actorControlHook; private bool _readyCheckOngoing = false; private double _lastReadyCheckEndTime = -1; public unsafe PartyReadyCheckHelper() { try { _onReadyCheckStartHook = Plugin.GameInteropProvider.HookFromAddress( AgentReadyCheck.MemberFunctionPointers.InitiateReadyCheck, OnReadyCheckStart ); _onReadyCheckStartHook?.Enable(); _onReadyCheckEndHook = Plugin.GameInteropProvider.HookFromAddress( AgentReadyCheck.MemberFunctionPointers.EndReadyCheck, OnReadycheckEnd ); _onReadyCheckEndHook?.Enable(); _actorControlHook = Plugin.GameInteropProvider.HookFromSignature( "E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64", OnActorControl ); _actorControlHook?.Enable(); } catch (Exception e) { Plugin.Logger.Error("Error initiating ready check sigs!!!\n" + e.Message); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposing) { return; } _onReadyCheckStartHook?.Disable(); _onReadyCheckStartHook?.Dispose(); _onReadyCheckEndHook?.Disable(); _onReadyCheckEndHook?.Dispose(); _actorControlHook?.Disable(); _actorControlHook?.Dispose(); } private unsafe void OnReadyCheckStart(AgentReadyCheck *ptr) { _onReadyCheckStartHook?.Original(ptr); _readyCheckOngoing = true; _lastReadyCheckEndTime = -1; } private unsafe void OnReadycheckEnd(AgentReadyCheck *ptr) { _onReadyCheckEndHook?.Original(ptr); _lastReadyCheckEndTime = ImGui.GetTime(); } private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12) { _actorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg7, arg8, arg9, arg10, targetId, arg12); // I'm not exactly sure what id == 503 means, but its always triggered when the fight starts // which is all I care about if (type == 503) { _readyCheckOngoing = false; } } public void Update(double maxDuration) { if (_readyCheckOngoing && _lastReadyCheckEndTime != -1 && ImGui.GetTime() - _lastReadyCheckEndTime >= maxDuration) { _readyCheckOngoing = false; } } public unsafe ReadyCheckStatus GetStatusForContentId(ulong contentId) { if (!_readyCheckOngoing) { return ReadyCheckStatus.None; } try { for (int i = 0; i < 8; i++) { ReadyCheckEntry entry = AgentReadyCheck.Instance()->ReadyCheckEntries[i]; if (entry.ContentId == contentId) { return ParseStatus(entry); } } } catch { } return ReadyCheckStatus.None; } private ReadyCheckStatus ParseStatus(ReadyCheckEntry entry) { if (entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.Ready) { return ReadyCheckStatus.Ready; } else if (entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.NotReady || entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.MemberNotPresent) { return ReadyCheckStatus.NotReady; } return ReadyCheckStatus.None; } } }