From 3a763d625a31ef1781b914cdd649e41b2c2d629d Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Tue, 21 Jan 2025 19:32:28 +0100 Subject: [PATCH] New experimental interrupt handler --- .../Controller/GatheringController.cs | 3 +- Questionable/Controller/InterruptHandler.cs | 165 ++++++++++++++++++ Questionable/Controller/MiniTaskController.cs | 22 ++- Questionable/Controller/QuestController.cs | 17 +- Questionable/Controller/Steps/Common/Mount.cs | 4 + .../Controller/Steps/Common/NextQuest.cs | 2 + .../Steps/Common/SendNotification.cs | 2 + .../Steps/Common/WaitConditionTask.cs | 2 + .../Controller/Steps/Gathering/DoGather.cs | 2 + .../Steps/Gathering/DoGatherCollectable.cs | 2 + .../Steps/Gathering/MoveToLandingLocation.cs | 1 + .../Steps/Gathering/TurnInDelivery.cs | 3 + .../Controller/Steps/Interactions/Action.cs | 4 + .../Steps/Interactions/AetherCurrent.cs | 2 + .../Steps/Interactions/AethernetShard.cs | 2 + .../Steps/Interactions/Aetheryte.cs | 2 + .../Controller/Steps/Interactions/Combat.cs | 2 + .../Controller/Steps/Interactions/Dive.cs | 2 + .../Controller/Steps/Interactions/Duty.cs | 6 + .../Controller/Steps/Interactions/Emote.cs | 4 + .../Steps/Interactions/EquipItem.cs | 2 + .../Steps/Interactions/EquipRecommended.cs | 2 + .../Controller/Steps/Interactions/Interact.cs | 2 + .../Controller/Steps/Interactions/Jump.cs | 2 + .../Controller/Steps/Interactions/Say.cs | 2 + .../Steps/Interactions/StatusOff.cs | 2 + .../Controller/Steps/Interactions/UseItem.cs | 2 + .../Controller/Steps/Leves/InitiateLeve.cs | 8 + .../Steps/Shared/AethernetShortcut.cs | 2 + .../Steps/Shared/AetheryteShortcut.cs | 4 + Questionable/Controller/Steps/Shared/Craft.cs | 3 + .../Controller/Steps/Shared/Gather.cs | 7 + .../Controller/Steps/Shared/MoveTo.cs | 8 + .../Steps/Shared/RedeemRewardItems.cs | 2 + .../Controller/Steps/Shared/SkipCondition.cs | 2 + .../Controller/Steps/Shared/StepDisabled.cs | 2 + .../Controller/Steps/Shared/SwitchClassJob.cs | 3 + .../Controller/Steps/Shared/WaitAtEnd.cs | 16 ++ .../Controller/Steps/Shared/WaitAtStart.cs | 3 +- Questionable/Controller/Steps/TaskExecutor.cs | 4 + Questionable/QuestionablePlugin.cs | 1 + 41 files changed, 321 insertions(+), 7 deletions(-) create mode 100644 Questionable/Controller/InterruptHandler.cs diff --git a/Questionable/Controller/GatheringController.cs b/Questionable/Controller/GatheringController.cs index bbe6d2e4..28de31f9 100644 --- a/Questionable/Controller/GatheringController.cs +++ b/Questionable/Controller/GatheringController.cs @@ -49,9 +49,10 @@ internal sealed unsafe class GatheringController : MiniTaskController 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; diff --git a/Questionable/Controller/InterruptHandler.cs b/Questionable/Controller/InterruptHandler.cs new file mode 100644 index 00000000..9171432f --- /dev/null +++ b/Questionable/Controller/InterruptHandler.cs @@ -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 _processActionEffectHook; + private readonly IClientState _clientState; + private readonly TerritoryData _territoryData; + private readonly ILogger _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 logger) + { + _clientState = clientState; + _territoryData = territoryData; + _logger = logger; + _processActionEffectHook = + gameInteropProvider.HookFromSignature(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, + } +} diff --git a/Questionable/Controller/MiniTaskController.cs b/Questionable/Controller/MiniTaskController.cs index 6055a68c..c9aec197 100644 --- a/Questionable/Controller/MiniTaskController.cs +++ b/Questionable/Controller/MiniTaskController.cs @@ -17,26 +17,29 @@ using Mount = Questionable.Controller.Steps.Common.Mount; namespace Questionable.Controller; -internal abstract class MiniTaskController +internal abstract class MiniTaskController : 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 _logger; private readonly string _actionCanceledText; protected MiniTaskController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider, - IDataManager dataManager, ILogger logger) + InterruptHandler interruptHandler, IDataManager dataManager, ILogger logger) { _chatGui = chatGui; _logger = logger; _serviceProvider = serviceProvider; + _interruptHandler = interruptHandler; _condition = condition; _actionCanceledText = dataManager.GetString(1314, x => x.Text)!; + _interruptHandler.Interrupted += HandleInterruption; } protected virtual void UpdateCurrentTask() @@ -198,8 +201,21 @@ internal abstract class MiniTaskController 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; + } } diff --git a/Questionable/Controller/QuestController.cs b/Questionable/Controller/QuestController.cs index 9d17fed1..d514f267 100644 --- a/Questionable/Controller/QuestController.cs +++ b/Questionable/Controller/QuestController.cs @@ -75,8 +75,9 @@ internal sealed class QuestController : MiniTaskController, IDi 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; @@ -801,11 +802,23 @@ internal sealed class QuestController : MiniTaskController, IDi _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( diff --git a/Questionable/Controller/Steps/Common/Mount.cs b/Questionable/Controller/Steps/Common/Mount.cs index 1e03d8e9..4f35f4d9 100644 --- a/Questionable/Controller/Steps/Common/Mount.cs +++ b/Questionable/Controller/Steps/Common/Mount.cs @@ -110,6 +110,8 @@ internal static class Mount ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal enum MountResult @@ -197,6 +199,8 @@ internal static class Mount return false; } + + public override bool ShouldInterruptOnDamage() => false; } public enum EMountIf diff --git a/Questionable/Controller/Steps/Common/NextQuest.cs b/Questionable/Controller/Steps/Common/NextQuest.cs index 7f4b0261..3ac7758d 100644 --- a/Questionable/Controller/Steps/Common/NextQuest.cs +++ b/Questionable/Controller/Steps/Common/NextQuest.cs @@ -61,5 +61,7 @@ internal static class NextQuest } public override ETaskResult Update() => ETaskResult.TaskComplete; + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Common/SendNotification.cs b/Questionable/Controller/Steps/Common/SendNotification.cs index cf116028..6d8bbcec 100644 --- a/Questionable/Controller/Steps/Common/SendNotification.cs +++ b/Questionable/Controller/Steps/Common/SendNotification.cs @@ -104,5 +104,7 @@ internal static class SendNotification } public override ETaskResult Update() => ETaskResult.TaskComplete; + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Common/WaitConditionTask.cs b/Questionable/Controller/Steps/Common/WaitConditionTask.cs index 367fdfec..8203c056 100644 --- a/Questionable/Controller/Steps/Common/WaitConditionTask.cs +++ b/Questionable/Controller/Steps/Common/WaitConditionTask.cs @@ -25,5 +25,7 @@ internal static class WaitCondition return DateTime.Now >= _continueAt ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Gathering/DoGather.cs b/Questionable/Controller/Steps/Gathering/DoGather.cs index 0f4c8c7f..bf4ab4aa 100644 --- a/Questionable/Controller/Steps/Gathering/DoGather.cs +++ b/Questionable/Controller/Steps/Gathering/DoGather.cs @@ -236,6 +236,8 @@ internal static class DoGather EAction action = PickAction(minerAction, botanistAction); return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0; } + + public override bool ShouldInterruptOnDamage() => false; } [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")] diff --git a/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs index 2b91f353..fcd5efad 100644 --- a/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs +++ b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs @@ -198,6 +198,8 @@ internal static class DoGatherCollectable else return botanistAction; } + + public override bool ShouldInterruptOnDamage() => false; } [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")] diff --git a/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs b/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs index 38fa30cd..bc183013 100644 --- a/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs +++ b/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs @@ -59,5 +59,6 @@ internal static class MoveToLandingLocation public override ETaskResult Update() => moveExecutor.Update(); public bool OnErrorToast(SeString message) => moveExecutor.OnErrorToast(message); + public override bool ShouldInterruptOnDamage() => moveExecutor.ShouldInterruptOnDamage(); } } diff --git a/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs b/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs index caf2b0f4..0483605b 100644 --- a/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs +++ b/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs @@ -80,5 +80,8 @@ internal static class TurnInDelivery 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; } } diff --git a/Questionable/Controller/Steps/Interactions/Action.cs b/Questionable/Controller/Steps/Interactions/Action.cs index 7255fa0b..f7f97502 100644 --- a/Questionable/Controller/Steps/Interactions/Action.cs +++ b/Questionable/Controller/Steps/Interactions/Action.cs @@ -124,6 +124,8 @@ internal static class Action return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => false; } 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); return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Interactions/AetherCurrent.cs b/Questionable/Controller/Steps/Interactions/AetherCurrent.cs index b244bbea..7632e574 100644 --- a/Questionable/Controller/Steps/Interactions/AetherCurrent.cs +++ b/Questionable/Controller/Steps/Interactions/AetherCurrent.cs @@ -65,5 +65,7 @@ internal static class AetherCurrent gameFunctions.IsAetherCurrentUnlocked(Task.AetherCurrentId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/AethernetShard.cs b/Questionable/Controller/Steps/Interactions/AethernetShard.cs index b1af7fe9..db2c5212 100644 --- a/Questionable/Controller/Steps/Interactions/AethernetShard.cs +++ b/Questionable/Controller/Steps/Interactions/AethernetShard.cs @@ -53,5 +53,7 @@ internal static class AethernetShard aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/Aetheryte.cs b/Questionable/Controller/Steps/Interactions/Aetheryte.cs index d9754776..dd40fc69 100644 --- a/Questionable/Controller/Steps/Interactions/Aetheryte.cs +++ b/Questionable/Controller/Steps/Interactions/Aetheryte.cs @@ -52,5 +52,7 @@ internal static class Aetheryte aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/Combat.cs b/Questionable/Controller/Steps/Interactions/Combat.cs index 463c32ce..e63058a8 100644 --- a/Questionable/Controller/Steps/Interactions/Combat.cs +++ b/Questionable/Controller/Steps/Interactions/Combat.cs @@ -190,5 +190,7 @@ internal static class Combat return ETaskResult.TaskComplete; } } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Interactions/Dive.cs b/Questionable/Controller/Steps/Interactions/Dive.cs index b5389774..eea9cd87 100644 --- a/Questionable/Controller/Steps/Interactions/Dive.cs +++ b/Questionable/Controller/Steps/Interactions/Dive.cs @@ -71,6 +71,8 @@ internal static class Dive return base.Update(); } + public override bool ShouldInterruptOnDamage() => false; + protected override ETaskResult UpdateInternal() { if (condition[ConditionFlag.Diving]) diff --git a/Questionable/Controller/Steps/Interactions/Duty.cs b/Questionable/Controller/Steps/Interactions/Duty.cs index 5e20accf..b59f8ce7 100644 --- a/Questionable/Controller/Steps/Interactions/Duty.cs +++ b/Questionable/Controller/Steps/Interactions/Duty.cs @@ -93,6 +93,8 @@ internal static class Duty ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask @@ -117,6 +119,8 @@ internal static class Duty ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask @@ -138,5 +142,7 @@ internal static class Duty } public override ETaskResult Update() => ETaskResult.TaskComplete; + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Interactions/Emote.cs b/Questionable/Controller/Steps/Interactions/Emote.cs index 085b0356..d6dd7146 100644 --- a/Questionable/Controller/Steps/Interactions/Emote.cs +++ b/Questionable/Controller/Steps/Interactions/Emote.cs @@ -51,6 +51,8 @@ internal static class Emote chatFunctions.UseEmote(Task.DataId, Task.Emote); return true; } + + public override bool ShouldInterruptOnDamage() => true; } internal sealed record UseOnSelf(EEmote Emote) : ITask @@ -65,5 +67,7 @@ internal static class Emote chatFunctions.UseEmote(Task.Emote); return true; } + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/EquipItem.cs b/Questionable/Controller/Steps/Interactions/EquipItem.cs index f5cd4e11..d761926d 100644 --- a/Questionable/Controller/Steps/Interactions/EquipItem.cs +++ b/Questionable/Controller/Steps/Interactions/EquipItem.cs @@ -183,5 +183,7 @@ internal static class EquipItem return false; } + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/EquipRecommended.cs b/Questionable/Controller/Steps/Interactions/EquipRecommended.cs index 3b2be0f1..295bb8cc 100644 --- a/Questionable/Controller/Steps/Interactions/EquipRecommended.cs +++ b/Questionable/Controller/Steps/Interactions/EquipRecommended.cs @@ -98,5 +98,7 @@ internal static class EquipRecommended return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/Interact.cs b/Questionable/Controller/Steps/Interactions/Interact.cs index 0ca70c11..0073349b 100644 --- a/Questionable/Controller/Steps/Interactions/Interact.cs +++ b/Questionable/Controller/Steps/Interactions/Interact.cs @@ -228,6 +228,8 @@ internal static class Interact } } + public override bool ShouldInterruptOnDamage() => true; + private enum EInteractionState { None, diff --git a/Questionable/Controller/Steps/Interactions/Jump.cs b/Questionable/Controller/Steps/Interactions/Jump.cs index f7b9892d..3238405c 100644 --- a/Questionable/Controller/Steps/Interactions/Jump.cs +++ b/Questionable/Controller/Steps/Interactions/Jump.cs @@ -80,6 +80,8 @@ internal static class Jump return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => true; } internal sealed class DoSingleJump( diff --git a/Questionable/Controller/Steps/Interactions/Say.cs b/Questionable/Controller/Steps/Interactions/Say.cs index f13ab4ab..ffb56215 100644 --- a/Questionable/Controller/Steps/Interactions/Say.cs +++ b/Questionable/Controller/Steps/Interactions/Say.cs @@ -48,5 +48,7 @@ internal static class Say chatFunctions.ExecuteCommand($"/say {Task.ChatMessage}"); return true; } + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Interactions/StatusOff.cs b/Questionable/Controller/Steps/Interactions/StatusOff.cs index 746f7394..c9b2b4ca 100644 --- a/Questionable/Controller/Steps/Interactions/StatusOff.cs +++ b/Questionable/Controller/Steps/Interactions/StatusOff.cs @@ -43,5 +43,7 @@ internal static class StatusOff { return gameFunctions.HasStatus(Task.Status) ? ETaskResult.StillRunning : ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Interactions/UseItem.cs b/Questionable/Controller/Steps/Interactions/UseItem.cs index bf779a02..abc427ad 100644 --- a/Questionable/Controller/Steps/Interactions/UseItem.cs +++ b/Questionable/Controller/Steps/Interactions/UseItem.cs @@ -205,6 +205,8 @@ internal static class UseItem else return TimeSpan.FromSeconds(5); } + + public override bool ShouldInterruptOnDamage() => true; } internal sealed record UseOnGround( diff --git a/Questionable/Controller/Steps/Leves/InitiateLeve.cs b/Questionable/Controller/Steps/Leves/InitiateLeve.cs index ab584cef..31cf4705 100644 --- a/Questionable/Controller/Steps/Leves/InitiateLeve.cs +++ b/Questionable/Controller/Steps/Leves/InitiateLeve.cs @@ -50,6 +50,8 @@ internal static class InitiateLeve return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record OpenJournal(ElementId ElementId) : ITask @@ -85,6 +87,8 @@ internal static class InitiateLeve return ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record Initiate(ElementId ElementId) : ITask @@ -111,6 +115,8 @@ internal static class InitiateLeve return ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed class SelectDifficulty : ITask @@ -138,5 +144,7 @@ internal static class InitiateLeve return ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Shared/AethernetShortcut.cs b/Questionable/Controller/Steps/Shared/AethernetShortcut.cs index e63b69c0..460aa440 100644 --- a/Questionable/Controller/Steps/Shared/AethernetShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AethernetShortcut.cs @@ -269,5 +269,7 @@ internal static class AethernetShortcut return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs index af575333..b2748b18 100644 --- a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs @@ -221,6 +221,8 @@ internal static class AetheryteShortcut } public override bool WasInterrupted() => condition[ConditionFlag.InCombat] || base.WasInterrupted(); + + public override bool ShouldInterruptOnDamage() => true; } internal sealed record MoveAwayFromAetheryte(EAetheryteLocation TargetAetheryte) : ITask @@ -264,5 +266,7 @@ internal static class AetheryteShortcut } public override ETaskResult Update() => moveExecutor.Update(); + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Shared/Craft.cs b/Questionable/Controller/Steps/Shared/Craft.cs index 26493ca0..d986368f 100644 --- a/Questionable/Controller/Steps/Shared/Craft.cs +++ b/Questionable/Controller/Steps/Shared/Craft.cs @@ -133,5 +133,8 @@ internal static class Craft 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; } } diff --git a/Questionable/Controller/Steps/Shared/Gather.cs b/Questionable/Controller/Steps/Shared/Gather.cs index 73dd8d12..f4aad9c9 100644 --- a/Questionable/Controller/Steps/Shared/Gather.cs +++ b/Questionable/Controller/Steps/Shared/Gather.cs @@ -100,6 +100,8 @@ internal static class Gather minCollectability: (short)itemToGather.Collectability) >= itemToGather.ItemCount; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record GatheringTask( @@ -140,6 +142,9 @@ internal static class Gather 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; } /// @@ -154,5 +159,7 @@ internal static class Gather { protected override bool Start() => true; public override ETaskResult Update() => ETaskResult.TaskComplete; + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Shared/MoveTo.cs b/Questionable/Controller/Steps/Shared/MoveTo.cs index 4df2870f..65689108 100644 --- a/Questionable/Controller/Steps/Shared/MoveTo.cs +++ b/Questionable/Controller/Steps/Shared/MoveTo.cs @@ -286,6 +286,8 @@ internal static class MoveTo return base.WasInterrupted(); } + public override bool ShouldInterruptOnDamage() => false; + public bool OnErrorToast(SeString message) { if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue)) @@ -302,6 +304,8 @@ internal static class MoveTo protected override bool Start() => true; public override ETaskResult Update() => ETaskResult.TaskComplete; + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record MoveTask( @@ -361,6 +365,8 @@ internal static class MoveTo return ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed class LandTask : ITask @@ -421,5 +427,7 @@ internal static class MoveTo return false; } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Shared/RedeemRewardItems.cs b/Questionable/Controller/Steps/Shared/RedeemRewardItems.cs index 408b92f7..c7abcae7 100644 --- a/Questionable/Controller/Steps/Shared/RedeemRewardItems.cs +++ b/Questionable/Controller/Steps/Shared/RedeemRewardItems.cs @@ -74,5 +74,7 @@ internal static class RedeemRewardItems return DateTime.Now <= _continueAt ? ETaskResult.StillRunning : ETaskResult.TaskComplete; } + + public override bool ShouldInterruptOnDamage() => true; } } diff --git a/Questionable/Controller/Steps/Shared/SkipCondition.cs b/Questionable/Controller/Steps/Shared/SkipCondition.cs index 5abab059..ebf1dc1f 100644 --- a/Questionable/Controller/Steps/Shared/SkipCondition.cs +++ b/Questionable/Controller/Steps/Shared/SkipCondition.cs @@ -315,5 +315,7 @@ internal static class SkipCondition } public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep; + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Shared/StepDisabled.cs b/Questionable/Controller/Steps/Shared/StepDisabled.cs index f7065359..de58cac3 100644 --- a/Questionable/Controller/Steps/Shared/StepDisabled.cs +++ b/Questionable/Controller/Steps/Shared/StepDisabled.cs @@ -31,5 +31,7 @@ internal static class StepDisabled logger.LogInformation("Skipping step, as it is disabled"); return ETaskResult.SkipRemainingTasksForStep; } + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Shared/SwitchClassJob.cs b/Questionable/Controller/Steps/Shared/SwitchClassJob.cs index 59477fec..18bfef7e 100644 --- a/Questionable/Controller/Steps/Shared/SwitchClassJob.cs +++ b/Questionable/Controller/Steps/Shared/SwitchClassJob.cs @@ -52,5 +52,8 @@ internal static class SwitchClassJob } 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; } } diff --git a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs index 0b3a02ba..d39c7c2a 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs @@ -157,6 +157,8 @@ internal static class WaitAtEnd Delay = Task.Delay; return true; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed class WaitNextStepOrSequence : ITask @@ -169,6 +171,8 @@ internal static class WaitAtEnd 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 @@ -190,6 +194,8 @@ internal static class WaitAtEnd ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record WaitObjectAtPosition( @@ -209,6 +215,8 @@ internal static class WaitAtEnd gameFunctions.IsObjectAtPosition(Task.DataId, Task.Destination, Task.Distance) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask @@ -226,6 +234,8 @@ internal static class WaitAtEnd ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask @@ -241,6 +251,8 @@ internal static class WaitAtEnd { return questFunctions.IsQuestComplete(Task.ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } + + public override bool ShouldInterruptOnDamage() => false; } internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask @@ -253,6 +265,8 @@ internal static class WaitAtEnd protected override bool Start() => true; public override ETaskResult Update() => ETaskResult.NextStep; + + public override bool ShouldInterruptOnDamage() => false; } internal sealed class EndAutomation : ILastTask @@ -268,5 +282,7 @@ internal static class WaitAtEnd protected override bool Start() => true; public override ETaskResult Update() => ETaskResult.End; + + public override bool ShouldInterruptOnDamage() => false; } } diff --git a/Questionable/Controller/Steps/Shared/WaitAtStart.cs b/Questionable/Controller/Steps/Shared/WaitAtStart.cs index c2c304b4..8386e636 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtStart.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtStart.cs @@ -31,6 +31,7 @@ internal static class WaitAtStart Delay = Task.Delay; return true; } - } + public override bool ShouldInterruptOnDamage() => false; + } } diff --git a/Questionable/Controller/Steps/TaskExecutor.cs b/Questionable/Controller/Steps/TaskExecutor.cs index d0315dbc..30e10b64 100644 --- a/Questionable/Controller/Steps/TaskExecutor.cs +++ b/Questionable/Controller/Steps/TaskExecutor.cs @@ -13,6 +13,8 @@ internal interface ITaskExecutor bool Start(ITask task); + bool ShouldInterruptOnDamage(); + bool WasInterrupted(); ETaskResult Update(); @@ -56,4 +58,6 @@ internal abstract class TaskExecutor : ITaskExecutor } public abstract ETaskResult Update(); + + public abstract bool ShouldInterruptOnDamage(); } diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index a4b5bae9..e0794c2d 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -247,6 +247,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton();