Skip to content

Commit

Permalink
Core: RequirementFactory: Added CanRun Requirement
Browse files Browse the repository at this point in the history
Core: KeyAction: Added CancelOnInterrupt

Core: ConfigurableInput: Added PressESC.

Json: Added Shaman_60_elemental profile to demonstrate castbar based spell interruption.
  • Loading branch information
Xian55 committed Dec 29, 2024
1 parent 10975d2 commit d2dd75e
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 1 deletion.
1 change: 1 addition & 0 deletions Core/ClassConfig/ActionMask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 7 additions & 0 deletions Core/ClassConfig/KeyAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
8 changes: 8 additions & 0 deletions Core/GoalsComponent/CastingHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,14 @@ public bool Cast(KeyAction item, Func<bool> 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;
}
Expand Down
4 changes: 4 additions & 0 deletions Core/Input/ConfigurableInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ public void PressDismount()

public void PressFollowTarget() => PressRandom(FollowTarget);

public void PressESC()
{
input.PressRandom(ConsoleKey.Escape, InputDuration.VeryFastPress);
}

#region Logging

Expand Down
38 changes: 37 additions & 1 deletion Core/Requirement/RequirementFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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<ICurrentAction> currentAction)
{
Expand Down Expand Up @@ -1103,6 +1115,30 @@ private Requirement CreateUsable(ReadOnlySpan<char> requirement)
$"related named '{name}' {nameof(Core.KeyAction)} not found!");
}

private Requirement CreateCanRun(ReadOnlySpan<char> requirement)
{
// 'CanRun:_KeyAction_Name_'
int sep = requirement.IndexOf(SEP1);
ReadOnlySpan<char> name = requirement[(sep + 1)..].Trim();

List<(string _, KeyActions)> groups = classConfig.GetByType<KeyActions>();

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<char> requirement)
{
return CreateArithmetic(greaterThen, requirement, intVariables);
Expand Down
98 changes: 98 additions & 0 deletions Json/class/Shaman_60_elemental_interrupt.json
Original file line number Diff line number Diff line change
@@ -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%"
}
]
}
}
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, ... | --- |
Expand Down Expand Up @@ -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**

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit d2dd75e

Please sign in to comment.