ILogger<GatheringController> logger,
         ICondition condition,
         IServiceProvider serviceProvider,
+        InterruptHandler interruptHandler,
         IDataManager dataManager,
         IPluginLog pluginLog)
-        : base(chatGui, condition, serviceProvider, dataManager, logger)
+        : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
     {
         _movementController = movementController;
         _gatheringPointRegistry = gatheringPointRegistry;
 
--- /dev/null
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using Dalamud.Game;
+using Dalamud.Hooking;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Character;
+using FFXIVClientStructs.FFXIV.Common.Math;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Logging;
+using Questionable.Data;
+
+namespace Questionable.Controller;
+
+internal sealed unsafe class InterruptHandler : IDisposable
+{
+    private readonly Hook<ProcessActionEffect> _processActionEffectHook;
+    private readonly IClientState _clientState;
+    private readonly TerritoryData _territoryData;
+    private readonly ILogger<InterruptHandler> _logger;
+
+    private delegate void ProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos,
+        EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail);
+
+    public InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState,
+        TerritoryData territoryData, ILogger<InterruptHandler> logger)
+    {
+        _clientState = clientState;
+        _territoryData = territoryData;
+        _logger = logger;
+        _processActionEffectHook =
+            gameInteropProvider.HookFromSignature<ProcessActionEffect>(Signatures.ActionEffect,
+                HandleProcessActionEffect);
+        _processActionEffectHook.Enable();
+    }
+
+    public event EventHandler? Interrupted;
+
+    private void HandleProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos,
+        EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail)
+    {
+        try
+        {
+            if (!_territoryData.IsDutyInstance(_clientState.TerritoryType))
+            {
+                for (int i = 0; i < effectHeader->TargetCount; i++)
+                {
+                    uint targetId = (uint)(effectTail[i] & uint.MaxValue);
+                    EffectEntry* effect = effectArray + 8 * i;
+
+                    if (targetId == _clientState.LocalPlayer?.GameObjectId &&
+                        effect->Type is EActionEffectType.Damage or EActionEffectType.BlockedDamage
+                            or EActionEffectType.ParriedDamage)
+                    {
+                        _logger.LogTrace("Damage action effect on self, from {SourceId} ({EffectType})", sourceId,
+                            effect->Type);
+                        Interrupted?.Invoke(this, EventArgs.Empty);
+                        break;
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            _logger.LogWarning(e, "Unable to process action effect");
+        }
+        finally
+        {
+            _processActionEffectHook.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTail);
+        }
+    }
+
+    public void Dispose()
+    {
+        _processActionEffectHook.Disable();
+        _processActionEffectHook.Dispose();
+    }
+
+    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 uint Damage => Mult == 0 ? Value : Value + ((uint)ushort.MaxValue + 1) * Mult;
+
+        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;
+    }
+
+    [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+    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, //Here is an issue bout knockback. some is 32 some is 33.
+        Attract2 = 34,
+        Mount = 40,
+        FullResistStatus = 52,
+        FullResistStatus2 = 55,
+        VFX = 59,
+        Gauge = 60,
+        JobGauge = 61,
+        SetModelState = 72,
+        SetHP = 73,
+        PartialInvulnerable = 74,
+        Interrupt = 75,
+    }
+}
 
 
 namespace Questionable.Controller;
 
-internal abstract class MiniTaskController<T>
+internal abstract class MiniTaskController<T> : IDisposable
 {
     protected readonly TaskQueue _taskQueue = new();
 
     private readonly IChatGui _chatGui;
     private readonly ICondition _condition;
     private readonly IServiceProvider _serviceProvider;
+    private readonly InterruptHandler _interruptHandler;
     private readonly ILogger<T> _logger;
 
     private readonly string _actionCanceledText;
 
     protected MiniTaskController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider,
-        IDataManager dataManager, ILogger<T> logger)
+        InterruptHandler interruptHandler, IDataManager dataManager, ILogger<T> logger)
     {
         _chatGui = chatGui;
         _logger = logger;
         _serviceProvider = serviceProvider;
+        _interruptHandler = interruptHandler;
         _condition = condition;
 
         _actionCanceledText = dataManager.GetString<LogMessage>(1314, x => x.Text)!;
+        _interruptHandler.Interrupted += HandleInterruption;
     }
 
     protected virtual void UpdateCurrentTask()
         if (!isHandled)
         {
             if (GameFunctions.GameStringEquals(_actionCanceledText, message.TextValue) &&
-                !_condition[ConditionFlag.InFlight])
+                !_condition[ConditionFlag.InFlight] &&
+                _taskQueue.CurrentTaskExecutor?.ShouldInterruptOnDamage() == true)
                 InterruptQueueWithCombat();
         }
     }
+
+    protected virtual void HandleInterruption(object? sender, EventArgs e)
+    {
+        if (!_condition[ConditionFlag.InFlight] &&
+            _taskQueue.CurrentTaskExecutor?.ShouldInterruptOnDamage() == true)
+            InterruptQueueWithCombat();
+    }
+
+    public virtual void Dispose()
+    {
+        _interruptHandler.Interrupted -= HandleInterruption;
+    }
 }
 
         YesAlreadyIpc yesAlreadyIpc,
         TaskCreator taskCreator,
         IServiceProvider serviceProvider,
+        InterruptHandler interruptHandler,
         IDataManager dataManager)
-        : base(chatGui, condition, serviceProvider, dataManager, logger)
+        : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
     {
         _clientState = clientState;
         _gameFunctions = gameFunctions;
         _gatheringController.OnNormalToast(message);
     }
 
-    public void Dispose()
+    protected override void HandleInterruption(object? sender, EventArgs e)
+    {
+        if (!IsRunning)
+            return;
+
+        if (AutomationType == EAutomationType.Manual)
+            return;
+
+        base.HandleInterruption(sender, e);
+    }
+
+    public override void Dispose()
     {
         _toastGui.ErrorToast -= OnErrorToast;
         _toastGui.Toast -= OnNormalToast;
         _condition.ConditionChange -= OnConditionChange;
+        base.Dispose();
     }
 
     public sealed record StepProgress(
 
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal enum MountResult
 
             return false;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     public enum EMountIf
 
         }
 
         public override ETaskResult Update() => ETaskResult.TaskComplete;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
         }
 
         public override ETaskResult Update() => ETaskResult.TaskComplete;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
 
             return DateTime.Now >= _continueAt ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             EAction action = PickAction(minerAction, botanistAction);
             return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
 
             else
                 return botanistAction;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
 
 
         public override ETaskResult Update() => moveExecutor.Update();
         public bool OnErrorToast(SeString message) => moveExecutor.OnErrorToast(message);
+        public override bool ShouldInterruptOnDamage() => moveExecutor.ShouldInterruptOnDamage();
     }
 }
 
             addon->FireCallback(2, pickGatheringItem);
             return ETaskResult.StillRunning;
         }
+
+        // not even sure if any turn-in npcs are NEAR mobs; but we also need to be on a gathering/crafting job
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
 
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record UseMudraOnObject(uint DataId, EAction Action) : ITask
             logger.LogError("Unable to find relevant combo for {Action}", Task.Action);
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             gameFunctions.IsAetherCurrentUnlocked(Task.AetherCurrentId)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
             aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
             aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
                 return ETaskResult.TaskComplete;
             }
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             return base.Update();
         }
 
+        public override bool ShouldInterruptOnDamage() => false;
+
         protected override ETaskResult UpdateInternal()
         {
             if (condition[ConditionFlag.Diving])
 
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask
         }
 
         public override ETaskResult Update() => ETaskResult.TaskComplete;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             chatFunctions.UseEmote(Task.DataId, Task.Emote);
             return true;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 
     internal sealed record UseOnSelf(EEmote Emote) : ITask
             chatFunctions.UseEmote(Task.Emote);
             return true;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
 
             return false;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
 
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
             }
         }
 
+        public override bool ShouldInterruptOnDamage() => true;
+
         private enum EInteractionState
         {
             None,
 
 
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 
     internal sealed class DoSingleJump(
 
             chatFunctions.ExecuteCommand($"/say {Task.ChatMessage}");
             return true;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
         {
             return gameFunctions.HasStatus(Task.Status) ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             else
                 return TimeSpan.FromSeconds(5);
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 
     internal sealed record UseOnGround(
 
 
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record OpenJournal(ElementId ElementId) : ITask
 
             return ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record Initiate(ElementId ElementId) : ITask
 
             return ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed class SelectDifficulty : ITask
 
             return ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
 
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
         }
 
         public override bool WasInterrupted() => condition[ConditionFlag.InCombat] || base.WasInterrupted();
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 
     internal sealed record MoveAwayFromAetheryte(EAetheryteLocation TargetAetheryte) : ITask
         }
 
         public override ETaskResult Update() => moveExecutor.Update();
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
             return inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: false, checkEquipped: false)
                    + inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: true, checkEquipped: false);
         }
+
+        // we're on a crafting class, so combat doesn't make much sense (we also can't change classes in combat...)
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
                        minCollectability: (short)itemToGather.Collectability) >=
                    itemToGather.ItemCount;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record GatheringTask(
             gatheringController.OnErrorToast(ref message, ref isHandled);
             return isHandled;
         }
+
+        // we're on a gathering class, so combat doesn't make much sense (we also can't change classes in combat...)
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     /// <summary>
     {
         protected override bool Start() => true;
         public override ETaskResult Update() => ETaskResult.TaskComplete;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             return base.WasInterrupted();
         }
 
+        public override bool ShouldInterruptOnDamage() => false;
+
         public bool OnErrorToast(SeString message)
         {
             if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue))
         protected override bool Start() => true;
 
         public override ETaskResult Update() => ETaskResult.TaskComplete;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record MoveTask(
 
             return ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed class LandTask : ITask
 
             return false;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
 
             return DateTime.Now <= _continueAt ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
         }
+
+        public override bool ShouldInterruptOnDamage() => true;
     }
 }
 
         }
 
         public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             logger.LogInformation("Skipping step, as it is disabled");
             return ETaskResult.SkipRemainingTasksForStep;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
         }
 
         protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
+
+        // can we even take damage while switching jobs? we should be out of combat...
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             Delay = Task.Delay;
             return true;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed class WaitNextStepOrSequence : ITask
         protected override bool Start() => true;
 
         public override ETaskResult Update() => ETaskResult.StillRunning;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record WaitForCompletionFlags(QuestId Quest, QuestStep Step) : ITask
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record WaitObjectAtPosition(
             gameFunctions.IsObjectAtPosition(Task.DataId, Task.Destination, Task.Distance)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask
         {
             return questFunctions.IsQuestComplete(Task.ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
         }
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask
         protected override bool Start() => true;
 
         public override ETaskResult Update() => ETaskResult.NextStep;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 
     internal sealed class EndAutomation : ILastTask
         protected override bool Start() => true;
 
         public override ETaskResult Update() => ETaskResult.End;
+
+        public override bool ShouldInterruptOnDamage() => false;
     }
 }
 
             Delay = Task.Delay;
             return true;
         }
-    }
 
+        public override bool ShouldInterruptOnDamage() => false;
+    }
 }
 
 
     bool Start(ITask task);
 
+    bool ShouldInterruptOnDamage();
+
     bool WasInterrupted();
 
     ETaskResult Update();
     }
 
     public abstract ETaskResult Update();
+
+    public abstract bool ShouldInterruptOnDamage();
 }
 
         serviceCollection.AddSingleton<GatheringController>();
         serviceCollection.AddSingleton<ContextMenuController>();
         serviceCollection.AddSingleton<ShopController>();
+        serviceCollection.AddSingleton<InterruptHandler>();
 
         serviceCollection.AddSingleton<CraftworksSupplyController>();
         serviceCollection.AddSingleton<CreditsController>();