New experimental interrupt handler

This commit is contained in:
Liza 2025-01-21 19:32:28 +01:00
parent f12b777d12
commit 3a763d625a
Signed by: liza
GPG Key ID: 2C41B84815CF6445
41 changed files with 321 additions and 7 deletions

View File

@ -49,9 +49,10 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
ILogger<GatheringController> logger, ILogger<GatheringController> logger,
ICondition condition, ICondition condition,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
InterruptHandler interruptHandler,
IDataManager dataManager, IDataManager dataManager,
IPluginLog pluginLog) IPluginLog pluginLog)
: base(chatGui, condition, serviceProvider, dataManager, logger) : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{ {
_movementController = movementController; _movementController = movementController;
_gatheringPointRegistry = gatheringPointRegistry; _gatheringPointRegistry = gatheringPointRegistry;

View File

@ -0,0 +1,165 @@
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,
}
}

View File

@ -17,26 +17,29 @@ using Mount = Questionable.Controller.Steps.Common.Mount;
namespace Questionable.Controller; namespace Questionable.Controller;
internal abstract class MiniTaskController<T> internal abstract class MiniTaskController<T> : IDisposable
{ {
protected readonly TaskQueue _taskQueue = new(); protected readonly TaskQueue _taskQueue = new();
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly InterruptHandler _interruptHandler;
private readonly ILogger<T> _logger; private readonly ILogger<T> _logger;
private readonly string _actionCanceledText; private readonly string _actionCanceledText;
protected MiniTaskController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider, protected MiniTaskController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider,
IDataManager dataManager, ILogger<T> logger) InterruptHandler interruptHandler, IDataManager dataManager, ILogger<T> logger)
{ {
_chatGui = chatGui; _chatGui = chatGui;
_logger = logger; _logger = logger;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_interruptHandler = interruptHandler;
_condition = condition; _condition = condition;
_actionCanceledText = dataManager.GetString<LogMessage>(1314, x => x.Text)!; _actionCanceledText = dataManager.GetString<LogMessage>(1314, x => x.Text)!;
_interruptHandler.Interrupted += HandleInterruption;
} }
protected virtual void UpdateCurrentTask() protected virtual void UpdateCurrentTask()
@ -198,8 +201,21 @@ internal abstract class MiniTaskController<T>
if (!isHandled) if (!isHandled)
{ {
if (GameFunctions.GameStringEquals(_actionCanceledText, message.TextValue) && if (GameFunctions.GameStringEquals(_actionCanceledText, message.TextValue) &&
!_condition[ConditionFlag.InFlight]) !_condition[ConditionFlag.InFlight] &&
_taskQueue.CurrentTaskExecutor?.ShouldInterruptOnDamage() == true)
InterruptQueueWithCombat(); 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;
}
} }

View File

@ -75,8 +75,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
YesAlreadyIpc yesAlreadyIpc, YesAlreadyIpc yesAlreadyIpc,
TaskCreator taskCreator, TaskCreator taskCreator,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
InterruptHandler interruptHandler,
IDataManager dataManager) IDataManager dataManager)
: base(chatGui, condition, serviceProvider, dataManager, logger) : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{ {
_clientState = clientState; _clientState = clientState;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
@ -801,11 +802,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
_gatheringController.OnNormalToast(message); _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.ErrorToast -= OnErrorToast;
_toastGui.Toast -= OnNormalToast; _toastGui.Toast -= OnNormalToast;
_condition.ConditionChange -= OnConditionChange; _condition.ConditionChange -= OnConditionChange;
base.Dispose();
} }
public sealed record StepProgress( public sealed record StepProgress(

View File

@ -110,6 +110,8 @@ internal static class Mount
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal enum MountResult internal enum MountResult
@ -197,6 +199,8 @@ internal static class Mount
return false; return false;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
public enum EMountIf public enum EMountIf

View File

@ -61,5 +61,7 @@ internal static class NextQuest
} }
public override ETaskResult Update() => ETaskResult.TaskComplete; public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -104,5 +104,7 @@ internal static class SendNotification
} }
public override ETaskResult Update() => ETaskResult.TaskComplete; public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -25,5 +25,7 @@ internal static class WaitCondition
return DateTime.Now >= _continueAt ? ETaskResult.TaskComplete : ETaskResult.StillRunning; return DateTime.Now >= _continueAt ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -236,6 +236,8 @@ internal static class DoGather
EAction action = PickAction(minerAction, botanistAction); EAction action = PickAction(minerAction, botanistAction);
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0; return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")] [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]

View File

@ -198,6 +198,8 @@ internal static class DoGatherCollectable
else else
return botanistAction; return botanistAction;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")] [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]

View File

@ -59,5 +59,6 @@ internal static class MoveToLandingLocation
public override ETaskResult Update() => moveExecutor.Update(); public override ETaskResult Update() => moveExecutor.Update();
public bool OnErrorToast(SeString message) => moveExecutor.OnErrorToast(message); public bool OnErrorToast(SeString message) => moveExecutor.OnErrorToast(message);
public override bool ShouldInterruptOnDamage() => moveExecutor.ShouldInterruptOnDamage();
} }
} }

View File

@ -80,5 +80,8 @@ internal static class TurnInDelivery
addon->FireCallback(2, pickGatheringItem); addon->FireCallback(2, pickGatheringItem);
return ETaskResult.StillRunning; 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;
} }
} }

View File

@ -124,6 +124,8 @@ internal static class Action
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record UseMudraOnObject(uint DataId, EAction Action) : ITask internal sealed record UseMudraOnObject(uint DataId, EAction Action) : ITask
@ -187,5 +189,7 @@ internal static class Action
logger.LogError("Unable to find relevant combo for {Action}", Task.Action); logger.LogError("Unable to find relevant combo for {Action}", Task.Action);
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -65,5 +65,7 @@ internal static class AetherCurrent
gameFunctions.IsAetherCurrentUnlocked(Task.AetherCurrentId) gameFunctions.IsAetherCurrentUnlocked(Task.AetherCurrentId)
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -53,5 +53,7 @@ internal static class AethernetShard
aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation) aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation)
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -52,5 +52,7 @@ internal static class Aetheryte
aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation) aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation)
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -190,5 +190,7 @@ internal static class Combat
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -71,6 +71,8 @@ internal static class Dive
return base.Update(); return base.Update();
} }
public override bool ShouldInterruptOnDamage() => false;
protected override ETaskResult UpdateInternal() protected override ETaskResult UpdateInternal()
{ {
if (condition[ConditionFlag.Diving]) if (condition[ConditionFlag.Diving])

View File

@ -93,6 +93,8 @@ internal static class Duty
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask
@ -117,6 +119,8 @@ internal static class Duty
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask
@ -138,5 +142,7 @@ internal static class Duty
} }
public override ETaskResult Update() => ETaskResult.TaskComplete; public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -51,6 +51,8 @@ internal static class Emote
chatFunctions.UseEmote(Task.DataId, Task.Emote); chatFunctions.UseEmote(Task.DataId, Task.Emote);
return true; return true;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
internal sealed record UseOnSelf(EEmote Emote) : ITask internal sealed record UseOnSelf(EEmote Emote) : ITask
@ -65,5 +67,7 @@ internal static class Emote
chatFunctions.UseEmote(Task.Emote); chatFunctions.UseEmote(Task.Emote);
return true; return true;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -183,5 +183,7 @@ internal static class EquipItem
return false; return false;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -98,5 +98,7 @@ internal static class EquipRecommended
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -228,6 +228,8 @@ internal static class Interact
} }
} }
public override bool ShouldInterruptOnDamage() => true;
private enum EInteractionState private enum EInteractionState
{ {
None, None,

View File

@ -80,6 +80,8 @@ internal static class Jump
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
internal sealed class DoSingleJump( internal sealed class DoSingleJump(

View File

@ -48,5 +48,7 @@ internal static class Say
chatFunctions.ExecuteCommand($"/say {Task.ChatMessage}"); chatFunctions.ExecuteCommand($"/say {Task.ChatMessage}");
return true; return true;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -43,5 +43,7 @@ internal static class StatusOff
{ {
return gameFunctions.HasStatus(Task.Status) ? ETaskResult.StillRunning : ETaskResult.TaskComplete; return gameFunctions.HasStatus(Task.Status) ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -205,6 +205,8 @@ internal static class UseItem
else else
return TimeSpan.FromSeconds(5); return TimeSpan.FromSeconds(5);
} }
public override bool ShouldInterruptOnDamage() => true;
} }
internal sealed record UseOnGround( internal sealed record UseOnGround(

View File

@ -50,6 +50,8 @@ internal static class InitiateLeve
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record OpenJournal(ElementId ElementId) : ITask internal sealed record OpenJournal(ElementId ElementId) : ITask
@ -85,6 +87,8 @@ internal static class InitiateLeve
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record Initiate(ElementId ElementId) : ITask internal sealed record Initiate(ElementId ElementId) : ITask
@ -111,6 +115,8 @@ internal static class InitiateLeve
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed class SelectDifficulty : ITask internal sealed class SelectDifficulty : ITask
@ -138,5 +144,7 @@ internal static class InitiateLeve
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -269,5 +269,7 @@ internal static class AethernetShortcut
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -221,6 +221,8 @@ internal static class AetheryteShortcut
} }
public override bool WasInterrupted() => condition[ConditionFlag.InCombat] || base.WasInterrupted(); public override bool WasInterrupted() => condition[ConditionFlag.InCombat] || base.WasInterrupted();
public override bool ShouldInterruptOnDamage() => true;
} }
internal sealed record MoveAwayFromAetheryte(EAetheryteLocation TargetAetheryte) : ITask internal sealed record MoveAwayFromAetheryte(EAetheryteLocation TargetAetheryte) : ITask
@ -264,5 +266,7 @@ internal static class AetheryteShortcut
} }
public override ETaskResult Update() => moveExecutor.Update(); public override ETaskResult Update() => moveExecutor.Update();
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -133,5 +133,8 @@ internal static class Craft
return inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: false, checkEquipped: false) return inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: false, checkEquipped: false)
+ inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: true, 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;
} }
} }

View File

@ -100,6 +100,8 @@ internal static class Gather
minCollectability: (short)itemToGather.Collectability) >= minCollectability: (short)itemToGather.Collectability) >=
itemToGather.ItemCount; itemToGather.ItemCount;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record GatheringTask( internal sealed record GatheringTask(
@ -140,6 +142,9 @@ internal static class Gather
gatheringController.OnErrorToast(ref message, ref isHandled); gatheringController.OnErrorToast(ref message, ref isHandled);
return 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> /// <summary>
@ -154,5 +159,7 @@ internal static class Gather
{ {
protected override bool Start() => true; protected override bool Start() => true;
public override ETaskResult Update() => ETaskResult.TaskComplete; public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -286,6 +286,8 @@ internal static class MoveTo
return base.WasInterrupted(); return base.WasInterrupted();
} }
public override bool ShouldInterruptOnDamage() => false;
public bool OnErrorToast(SeString message) public bool OnErrorToast(SeString message)
{ {
if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue)) if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue))
@ -302,6 +304,8 @@ internal static class MoveTo
protected override bool Start() => true; protected override bool Start() => true;
public override ETaskResult Update() => ETaskResult.TaskComplete; public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record MoveTask( internal sealed record MoveTask(
@ -361,6 +365,8 @@ internal static class MoveTo
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed class LandTask : ITask internal sealed class LandTask : ITask
@ -421,5 +427,7 @@ internal static class MoveTo
return false; return false;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -74,5 +74,7 @@ internal static class RedeemRewardItems
return DateTime.Now <= _continueAt ? ETaskResult.StillRunning : ETaskResult.TaskComplete; return DateTime.Now <= _continueAt ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
} }
public override bool ShouldInterruptOnDamage() => true;
} }
} }

View File

@ -315,5 +315,7 @@ internal static class SkipCondition
} }
public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep; public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep;
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -31,5 +31,7 @@ internal static class StepDisabled
logger.LogInformation("Skipping step, as it is disabled"); logger.LogInformation("Skipping step, as it is disabled");
return ETaskResult.SkipRemainingTasksForStep; return ETaskResult.SkipRemainingTasksForStep;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -52,5 +52,8 @@ internal static class SwitchClassJob
} }
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete; 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;
} }
} }

View File

@ -157,6 +157,8 @@ internal static class WaitAtEnd
Delay = Task.Delay; Delay = Task.Delay;
return true; return true;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed class WaitNextStepOrSequence : ITask internal sealed class WaitNextStepOrSequence : ITask
@ -169,6 +171,8 @@ internal static class WaitAtEnd
protected override bool Start() => true; protected override bool Start() => true;
public override ETaskResult Update() => ETaskResult.StillRunning; public override ETaskResult Update() => ETaskResult.StillRunning;
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record WaitForCompletionFlags(QuestId Quest, QuestStep Step) : ITask internal sealed record WaitForCompletionFlags(QuestId Quest, QuestStep Step) : ITask
@ -190,6 +194,8 @@ internal static class WaitAtEnd
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record WaitObjectAtPosition( internal sealed record WaitObjectAtPosition(
@ -209,6 +215,8 @@ internal static class WaitAtEnd
gameFunctions.IsObjectAtPosition(Task.DataId, Task.Destination, Task.Distance) gameFunctions.IsObjectAtPosition(Task.DataId, Task.Destination, Task.Distance)
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask
@ -226,6 +234,8 @@ internal static class WaitAtEnd
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask
@ -241,6 +251,8 @@ internal static class WaitAtEnd
{ {
return questFunctions.IsQuestComplete(Task.ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; return questFunctions.IsQuestComplete(Task.ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
} }
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask
@ -253,6 +265,8 @@ internal static class WaitAtEnd
protected override bool Start() => true; protected override bool Start() => true;
public override ETaskResult Update() => ETaskResult.NextStep; public override ETaskResult Update() => ETaskResult.NextStep;
public override bool ShouldInterruptOnDamage() => false;
} }
internal sealed class EndAutomation : ILastTask internal sealed class EndAutomation : ILastTask
@ -268,5 +282,7 @@ internal static class WaitAtEnd
protected override bool Start() => true; protected override bool Start() => true;
public override ETaskResult Update() => ETaskResult.End; public override ETaskResult Update() => ETaskResult.End;
public override bool ShouldInterruptOnDamage() => false;
} }
} }

View File

@ -31,6 +31,7 @@ internal static class WaitAtStart
Delay = Task.Delay; Delay = Task.Delay;
return true; return true;
} }
}
public override bool ShouldInterruptOnDamage() => false;
}
} }

View File

@ -13,6 +13,8 @@ internal interface ITaskExecutor
bool Start(ITask task); bool Start(ITask task);
bool ShouldInterruptOnDamage();
bool WasInterrupted(); bool WasInterrupted();
ETaskResult Update(); ETaskResult Update();
@ -56,4 +58,6 @@ internal abstract class TaskExecutor<T> : ITaskExecutor
} }
public abstract ETaskResult Update(); public abstract ETaskResult Update();
public abstract bool ShouldInterruptOnDamage();
} }

View File

@ -247,6 +247,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<GatheringController>(); serviceCollection.AddSingleton<GatheringController>();
serviceCollection.AddSingleton<ContextMenuController>(); serviceCollection.AddSingleton<ContextMenuController>();
serviceCollection.AddSingleton<ShopController>(); serviceCollection.AddSingleton<ShopController>();
serviceCollection.AddSingleton<InterruptHandler>();
serviceCollection.AddSingleton<CraftworksSupplyController>(); serviceCollection.AddSingleton<CraftworksSupplyController>();
serviceCollection.AddSingleton<CreditsController>(); serviceCollection.AddSingleton<CreditsController>();