qstbak/Questionable/Questionable.Controller/InterruptHandler.cs
2025-10-09 07:47:19 +10:00

195 lines
4.8 KiB
C#

using System;
using System.Runtime.InteropServices;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Common.Math;
using Microsoft.Extensions.Logging;
using Questionable.Data;
namespace Questionable.Controller;
internal sealed class InterruptHandler : IDisposable
{
private unsafe delegate void ProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos, EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail);
private static class Signatures
{
internal const string ActionEffect = "40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48";
}
[StructLayout(LayoutKind.Explicit)]
private struct EffectEntry
{
[FieldOffset(0)]
public EActionEffectType Type;
[FieldOffset(1)]
public byte Param0;
[FieldOffset(2)]
public byte Param1;
[FieldOffset(3)]
public byte Param2;
[FieldOffset(4)]
public byte Mult;
[FieldOffset(5)]
public byte Flags;
[FieldOffset(6)]
public ushort Value;
public byte AttackType => (byte)(Param1 & 0xF);
public override string ToString()
{
return $"Type: {Type}, p0: {Param0:D3}, p1: {Param1:D3}, p2: {Param2:D3} 0x{Param2:X2} '{Convert.ToString(Param2, 2).PadLeft(8, '0')}', mult: {Mult:D3}, flags: {Flags:D3} | {Convert.ToString(Flags, 2).PadLeft(8, '0')}, value: {Value:D6} ATTACK TYPE: {AttackType}";
}
}
[StructLayout(LayoutKind.Explicit)]
private struct EffectHeader
{
[FieldOffset(0)]
public ulong AnimationTargetId;
[FieldOffset(8)]
public uint ActionID;
[FieldOffset(12)]
public uint GlobalEffectCounter;
[FieldOffset(16)]
public float AnimationLockTime;
[FieldOffset(20)]
public uint SomeTargetID;
[FieldOffset(24)]
public ushort SourceSequence;
[FieldOffset(26)]
public ushort Rotation;
[FieldOffset(28)]
public ushort AnimationId;
[FieldOffset(30)]
public byte Variation;
[FieldOffset(31)]
public ActionType ActionType;
[FieldOffset(33)]
public byte TargetCount;
}
private enum EActionEffectType : byte
{
None = 0,
Miss = 1,
FullResist = 2,
Damage = 3,
Heal = 4,
BlockedDamage = 5,
ParriedDamage = 6,
Invulnerable = 7,
NoEffectText = 8,
Unknown0 = 9,
MpLoss = 10,
MpGain = 11,
TpLoss = 12,
TpGain = 13,
ApplyStatusEffectTarget = 14,
ApplyStatusEffectSource = 15,
RecoveredFromStatusEffect = 16,
LoseStatusEffectTarget = 17,
LoseStatusEffectSource = 18,
StatusNoEffect = 20,
ThreatPosition = 24,
EnmityAmountUp = 25,
EnmityAmountDown = 26,
StartActionCombo = 27,
ComboSucceed = 28,
Retaliation = 29,
Knockback = 32,
Attract1 = 33,
Attract2 = 34,
Mount = 40,
FullResistStatus = 52,
FullResistStatus2 = 55,
VFX = 59,
Gauge = 60,
JobGauge = 61,
SetModelState = 72,
SetHP = 73,
PartialInvulnerable = 74,
Interrupt = 75
}
private readonly Hook<ProcessActionEffect> _processActionEffectHook;
private readonly IClientState _clientState;
private readonly TerritoryData _territoryData;
private readonly ILogger<InterruptHandler> _logger;
public event EventHandler? Interrupted;
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, TerritoryData territoryData, ILogger<InterruptHandler> logger)
{
_clientState = clientState;
_territoryData = territoryData;
_logger = logger;
_processActionEffectHook = gameInteropProvider.HookFromSignature<ProcessActionEffect>("40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48", HandleProcessActionEffect);
_processActionEffectHook.Enable();
}
private unsafe void HandleProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos, EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail)
{
try
{
if (_territoryData.IsDutyInstance(_clientState.TerritoryType))
{
return;
}
for (int i = 0; i < effectHeader->TargetCount; i++)
{
int num = (int)(effectTail[i] & 0xFFFFFFFFu);
EffectEntry* ptr = effectArray + 8 * i;
bool flag = (uint)num == _clientState.LocalPlayer?.GameObjectId;
if (flag)
{
EActionEffectType type = ptr->Type;
bool flag2 = ((type == EActionEffectType.Damage || type - 5 <= EActionEffectType.Miss) ? true : false);
flag = flag2;
}
if (flag)
{
_logger.LogTrace("Damage action effect on self, from {SourceId} ({EffectType})", sourceId, ptr->Type);
this.Interrupted?.Invoke(this, EventArgs.Empty);
break;
}
}
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Unable to process action effect");
}
finally
{
_processActionEffectHook.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTail);
}
}
public void Dispose()
{
_processActionEffectHook.Disable();
_processActionEffectHook.Dispose();
}
}