From d2dd75e7571aa8ee316eada3100a5fe79635e5ca Mon Sep 17 00:00:00 2001 From: Xian55 <367101+Xian55@users.noreply.github.com> Date: Sun, 29 Dec 2024 01:43:14 +0100 Subject: [PATCH] Core: RequirementFactory: Added CanRun Requirement Core: KeyAction: Added CancelOnInterrupt Core: ConfigurableInput: Added PressESC. Json: Added Shaman_60_elemental profile to demonstrate castbar based spell interruption. --- Core/ClassConfig/ActionMask.cs | 1 + Core/ClassConfig/KeyAction.cs | 7 ++ Core/GoalsComponent/CastingHandler.cs | 8 ++ Core/Input/ConfigurableInput.cs | 4 + Core/Requirement/RequirementFactory.cs | 38 ++++++- Json/class/Shaman_60_elemental_interrupt.json | 98 +++++++++++++++++++ README.md | 50 ++++++++++ 7 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 Json/class/Shaman_60_elemental_interrupt.json diff --git a/Core/ClassConfig/ActionMask.cs b/Core/ClassConfig/ActionMask.cs index 8305e530..718a2847 100644 --- a/Core/ClassConfig/ActionMask.cs +++ b/Core/ClassConfig/ActionMask.cs @@ -21,4 +21,5 @@ public static class ActionMask public const int AfterCastWaitCombat = 1 << 14; public const int AfterCastWaitGCD = 1 << 15; public const int AfterCastAuraExpected = 1 << 16; + public const int CancelOnInterrupt = 1 << 17; } diff --git a/Core/ClassConfig/KeyAction.cs b/Core/ClassConfig/KeyAction.cs index 85b06596..d919acd1 100644 --- a/Core/ClassConfig/KeyAction.cs +++ b/Core/ClassConfig/KeyAction.cs @@ -144,6 +144,13 @@ public bool AfterCastAuraExpected get => features[ActionMask.AfterCastAuraExpected]; set => features[ActionMask.AfterCastAuraExpected] = value; } + + public bool CancelOnInterrupt + { + get => features[ActionMask.CancelOnInterrupt]; + set => features[ActionMask.CancelOnInterrupt] = value; + } + public int AfterCastStepBack { get; set; } public string InCombat { get; set; } = "false"; diff --git a/Core/GoalsComponent/CastingHandler.cs b/Core/GoalsComponent/CastingHandler.cs index de5621c2..faae1fe1 100644 --- a/Core/GoalsComponent/CastingHandler.cs +++ b/Core/GoalsComponent/CastingHandler.cs @@ -664,6 +664,14 @@ public bool Cast(KeyAction item, Func interrupt) { if (result != CastResult.UIError) { + if (result == CastResult.TokenInterrupted && + item.CancelOnInterrupt) + { + input.PressESC(); + wait.Fixed(playerReader.NetworkLatency); + wait.Update(); + } + LogFailedDueReason(logger, item.Name, result.ToStringF()); return false; } diff --git a/Core/Input/ConfigurableInput.cs b/Core/Input/ConfigurableInput.cs index a6d6bb20..c3df82f9 100644 --- a/Core/Input/ConfigurableInput.cs +++ b/Core/Input/ConfigurableInput.cs @@ -148,6 +148,10 @@ public void PressDismount() public void PressFollowTarget() => PressRandom(FollowTarget); + public void PressESC() + { + input.PressRandom(ConsoleKey.Escape, InputDuration.VeryFastPress); + } #region Logging diff --git a/Core/Requirement/RequirementFactory.cs b/Core/Requirement/RequirementFactory.cs index cffaeb86..a74309fa 100644 --- a/Core/Requirement/RequirementFactory.cs +++ b/Core/Requirement/RequirementFactory.cs @@ -131,7 +131,8 @@ public RequirementFactory(IServiceProvider sp, ClassConfiguration classConfig) { "Spell", CreateSpell }, { "Talent", CreateTalent }, { "Trigger:", CreateTrigger }, - { "Usable:", CreateUsable } + { "Usable:", CreateUsable }, + { "CanRun:", CreateCanRun } }; this.requirementMap = requirementMap.ToFrozenDictionary(); @@ -780,6 +781,17 @@ string s() => }; } + private Requirement CreateActionCanRun(KeyAction item) + { + bool f() => item.CanRun(); + string s() => $"CanRun:{item.Name}"; + return new Requirement + { + HasRequirement = f, + LogMessage = s + }; + } + private Requirement CreateActionCurrent(KeyAction item, ActionBarBits currentAction) { @@ -1103,6 +1115,30 @@ private Requirement CreateUsable(ReadOnlySpan requirement) $"related named '{name}' {nameof(Core.KeyAction)} not found!"); } + private Requirement CreateCanRun(ReadOnlySpan requirement) + { + // 'CanRun:_KeyAction_Name_' + int sep = requirement.IndexOf(SEP1); + ReadOnlySpan name = requirement[(sep + 1)..].Trim(); + + List<(string _, KeyActions)> groups = classConfig.GetByType(); + + foreach ((string _, KeyActions keyActions) in groups) + { + foreach (KeyAction keyAction in keyActions.Sequence) + { + if (name.SequenceEqual(keyAction.Name)) + { + return CreateActionCanRun(keyAction); + } + } + } + + throw new InvalidOperationException($"'{requirement}' " + + $"related named '{name}' {nameof(Core.KeyAction)} not found!"); + } + + private Requirement CreateGreaterThen(ReadOnlySpan requirement) { return CreateArithmetic(greaterThen, requirement, intVariables); diff --git a/Json/class/Shaman_60_elemental_interrupt.json b/Json/class/Shaman_60_elemental_interrupt.json new file mode 100644 index 00000000..51347913 --- /dev/null +++ b/Json/class/Shaman_60_elemental_interrupt.json @@ -0,0 +1,98 @@ +{ + "ClassName": "Shaman", + "Mode": "AttendedGrind", + "Loot": true, + "Skin": false, + "PathFilename": "_pack\\50-60\\Azshara\\50 - 53 Southridge Beach (mermaids).json", + "PathThereAndBack": false, + "PathReduceSteps": true, + "NPCMaxLevels_Below": 20, + "NPCMaxLevels_Above": 2, + "Mount": { + "Key": "N0" + }, + "IntVariables":{ + "Earth_Shock_MIN_HP%": 25, + "Healing_Wave_MIN_HP%": 20, + "Drink_MIN_MANA%": 20, + "Food_MIN_HP%": 20 + }, + "Pull": { + "Sequence": [ + { + "Name": "Chain Lightning", + "Key": "2", + "HasCastBar": true, + "Requirement": "Clearcasting" + }, + { + "Name": "Lightning Bolt", + "Key": "1", + "HasCastBar": true, + "Requirement": "!Clearcasting", + "Interrupt": "Clearcasting", + "AfterCastWaitCastbar": true, + "CancelOnInterrupt": true + } + ] + }, + "Combat": { + "Sequence": [ + { + "Name": "Chain Lightning", + "Key": "2", + "HasCastBar": true, + "Requirement": "Clearcasting" + }, + { + "Name": "Earth Shock", + "Key": "3", + "Requirements": [ + "SpellInRange:1", + "TargetAlive && TargetHealth% < Earth_Shock_MIN_HP%" + ] + }, + { + "Name": "Lightning Bolt", + "Key": "1", + "HasCastBar": true, + "AfterCastWaitCastbar": true, + "Requirement": "!Clearcasting", + "Interrupt": "Clearcasting || CanRun:Earth Shock", + "CancelOnInterrupt": true + } + ] + }, + "Adhoc": { + "Sequence": [ + { + "Name": "Lightning Shield", + "Key": "7", + "Requirement": "!Lightning Shield" + }, + { + "Cost": 3, + "Name": "Healing Wave", + "HasCastBar": true, + "Key": "F1", + "Requirements": [ + "Health% < Healing_Wave_MIN_HP%" + ] + } + ] + }, + "Parallel": { + "Sequence": [ + { + "Name": "Drink", + "Key": "-", + "Requirement": "Mana% < Drink_MIN_MANA%" + }, + { + "Name": "Food", + "Key": "=", + "Requirement": "Health% < Food_MIN_HP%" + } + ] + } +} \ No newline at end of file diff --git a/README.md b/README.md index cc8ecc57..46a8b136 100644 --- a/README.md +++ b/README.md @@ -698,6 +698,7 @@ Can specify conditions with [Requirement(s)](#requirement) in order to create a | `"Requirements"` | List of [Requirement](#requirement) | `false` | | `"Interrupt"` | Single [Requirement](#requirement) | `false` | | `"Interrupts"` | List of [Requirement](#requirement) | `false` | +| `"CancelOnInterrupt"` | If the [Interrupt](#interrupt-requirement) [Requirement](#requirement) has met, shall **Cancel** current castbar spellcast (sending ESC) | `false` | | `"ResetOnNewTarget"` | Reset the Cooldown if the target changes | `false` | | `"Log"` | Related events should appear in the logs | `true` | | --- | Before keypress cast, ... | --- | @@ -1624,6 +1625,25 @@ e.g. "FBuff_Mount": 132239 }, ``` +--- +### **CanRun requirements** + +Formula: `CanRun:_KeyAction_Name_` + +Where the `_KeyAction_Name_` is one of the [KeyAction.Name](#keyaction). + +It is suitable for [Interrupt Requirement](#interrupt-requirement), for such scenario: +* The player already casting a `Castbar` based spell which has a long cast time like 2-3 seconds +* However meanwhile a higher priority action can be used, which would be beneficial. +* Like: A mob is in low hp, so an instant cast spell such as `Earth Shock`, `Fireblast` would execute the enemy. + +e.g. +```json +"Requirement": "CanRun:Frost Shock" +"Requirement": "CanRun:Fire Blast" +"Requirement": "!CanRun:Heartstone" +``` + --- ### **Trigger requirements** @@ -2038,6 +2058,36 @@ This **500**ms duration is the reload animation time, while the player has to st } ``` --- + +Execute low hp enemy instead of awaitning the currently casted spell. + +The key takeaway here is that the `Earth Shock` spell has higher priority. + +```json +"Combat": { + "Sequence": [ + //... + { + "Name": "Earth Shock", + "Key": "3", + "Requirements": [ + "SpellInRange:1", + "TargetAlive && TargetHealth% < 20" + ] + }, + { + "Name": "Lightning Bolt", + "Key": "1", + "HasCastBar": true, + "AfterCastWaitCastbar": true, + "Requirement": "!Clearcasting", + "Interrupt": "Clearcasting || CanRun:Earth Shock", + "CancelOnInterrupt": true + } + ] +} +``` +--- # Modes The default mode for the bot is to grind, but there are other modes. The mode is set in the root of the class file.