using System; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using KamiLib.Hooking; namespace KamiLib.GameState; public unsafe class DutyState : IDisposable { private delegate byte DutyEventDelegate(void* a1, void* a2, ushort* a3); [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8B D9 49 8B F8 41 0F B7 08", DetourName = nameof(DutyEventFunction))] private readonly Hook? dutyEventHook = null; public bool IsDutyStarted { get; private set; } public delegate void DutyStateEvent(uint duty); public event DutyStateEvent? DutyStarted; public event DutyStateEvent? DutyWiped; public event DutyStateEvent? DutyRecommenced; public event DutyStateEvent? DutyCompleted; private bool completedThisTerritory; private static DutyState? _instance; public static DutyState Instance => _instance ??= new DutyState(); private DutyState() { Service.GameInteropProvider.InitializeFromAttributes(this); dutyEventHook?.Enable(); if (Condition.IsBoundByDuty()) { IsDutyStarted = true; } Service.Framework.Update += FrameworkUpdate; Service.ClientState.TerritoryChanged += TerritoryChanged; } public static void Cleanup() { _instance?.Dispose(); } public void Dispose() { dutyEventHook?.Dispose(); Service.Framework.Update -= FrameworkUpdate; Service.ClientState.TerritoryChanged -= TerritoryChanged; } private void FrameworkUpdate(IFramework framework) { if (!IsDutyStarted && !completedThisTerritory) { if (Condition.IsBoundByDuty() && Condition.IsInCombat()) { IsDutyStarted = true; } } else if (!Condition.IsBoundByDuty()) { IsDutyStarted = false; } } private void TerritoryChanged(ushort e) { if (IsDutyStarted) { IsDutyStarted = false; } completedThisTerritory = false; } private byte DutyEventFunction(void* a1, void* a2, ushort* a3) { Safety.ExecuteSafe(() => { var category = *(a3); var type = *(uint*)(a3 + 4); // DirectorUpdate Category if (category == 0x6D) { switch (type) { // Duty Commenced case 0x40000001: IsDutyStarted = true; DutyStarted?.Invoke(Service.ClientState.TerritoryType); break; // Party Wipe case 0x40000005: IsDutyStarted = false; DutyWiped?.Invoke(Service.ClientState.TerritoryType); break; // Duty Recommence case 0x40000006: IsDutyStarted = true; DutyRecommenced?.Invoke(Service.ClientState.TerritoryType); break; // Duty Completed case 0x40000003: IsDutyStarted = false; completedThisTerritory = true; DutyCompleted?.Invoke(Service.ClientState.TerritoryType); break; } } }); return dutyEventHook!.Original(a1, a2, a3); } }