Channel Cancel checks for Channeled AOE DPS Spells

Hello everyone,

I liked the channel cancel I did on mage recently, where they cancel blizzard if there is only 1 enemy left. I decided to do the same for the following classes:

Druid: Hurricane
Hunter: Volley
Priest: Mind Sear
Warlock: Rain of Fire

I moved the "ChannelCancel" action to it's own file, so other classes could benefit from it. I removed it from the mageactions.
This commit is contained in:
ThePenguinMan96
2025-07-28 01:35:56 -07:00
parent eef2e8c1ef
commit e40c2b21f2
22 changed files with 241 additions and 25 deletions

View File

@@ -63,6 +63,7 @@
#include "WorldBuffAction.h"
#include "XpGainAction.h"
#include "NewRpgAction.h"
#include "CancelChannelAction.h"
class PlayerbotAI;
@@ -189,6 +190,7 @@ public:
creators["buy tabard"] = &ActionContext::buy_tabard;
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
creators["clean quest log"] = &ActionContext::clean_quest_log;
creators["cancel channel"] = &ActionContext::cancel_channel;
// BG Tactics
creators["bg tactics"] = &ActionContext::bg_tactics;
@@ -298,6 +300,7 @@ private:
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
static Action* emote(PlayerbotAI* botAI) { return new EmoteAction(botAI); }
static Action* talk(PlayerbotAI* botAI) { return new TalkAction(botAI); }

View File

@@ -0,0 +1,18 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "CancelChannelAction.h"
#include "Player.h"
#include "PlayerbotAI.h"
bool CancelChannelAction::Execute(Event event)
{
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
bot->InterruptSpell(CURRENT_CHANNELED_SPELL);
return true;
}
return false;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CANCELCHANNELACTION_H
#define _PLAYERBOT_CANCELCHANNELACTION_H
#include "Action.h"
class PlayerbotAI;
class CancelChannelAction : public Action
{
public:
CancelChannelAction(PlayerbotAI* botAI) : Action(botAI, "cancel channel") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -151,6 +151,8 @@ void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("hurricane channel check", NextAction::array(0, new NextAction("cancel channel", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(

View File

@@ -111,6 +111,7 @@ public:
creators["eclipse (lunar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_lunar_cooldown;
creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat;
creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time;
creators["hurricane channel check"] = &DruidTriggerFactoryInternal::hurricane_channel_check;
}
private:
@@ -147,6 +148,7 @@ private:
static Trigger* eclipse_lunar_cooldown(PlayerbotAI* ai) { return new EclipseLunarCooldownTrigger(ai); }
static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); }
static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); }
static Trigger* hurricane_channel_check(PlayerbotAI* ai) { return new HurricaneChannelCheckTrigger(ai); }
};
class DruidAiObjectContextInternal : public NamedObjectContext<Action>

View File

@@ -4,7 +4,7 @@
*/
#include "DruidTriggers.h"
#include "Player.h"
#include "Playerbots.h"
bool MarkOfTheWildOnPartyTrigger::IsActive()
@@ -34,3 +34,30 @@ bool BearFormTrigger::IsActive() { return !botAI->HasAnyAuraOf(bot, "bear form",
bool TreeFormTrigger::IsActive() { return !botAI->HasAura(33891, bot); }
bool CatFormTrigger::IsActive() { return !botAI->HasAura("cat form", bot); }
const std::set<uint32> HurricaneChannelCheckTrigger::HURRICANE_SPELL_IDS = {
16914, // Hurricane Rank 1
17401, // Hurricane Rank 2
17402, // Hurricane Rank 3
27012, // Hurricane Rank 4
48467 // Hurricane Rank 5
};
bool HurricaneChannelCheckTrigger::IsActive()
{
Player* bot = botAI->GetBot();
// Check if the bot is channeling a spell
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
// Only trigger if the spell being channeled is Hurricane
if (HURRICANE_SPELL_IDS.count(spell->m_spellInfo->Id))
{
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
}
// Not channeling Hurricane
return false;
}

View File

@@ -12,6 +12,8 @@
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "SharedDefines.h"
#include "Trigger.h"
#include <set>
class PlayerbotAI;
@@ -263,4 +265,19 @@ public:
}
};
class HurricaneChannelCheckTrigger : public Trigger
{
public:
HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
: Trigger(botAI, "hurricane channel check"), minEnemies(minEnemies)
{
}
bool IsActive() override;
protected:
uint32 minEnemies;
static const std::set<uint32> HURRICANE_SPELL_IDS;
};
#endif

View File

@@ -136,9 +136,9 @@ AoEHunterStrategy::AoEHunterStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI)
void AoEHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("volley channel check", NextAction::array(0, new NextAction("cancel channel", 23.0f), nullptr)));
triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0, new NextAction("volley", 22.0f), nullptr)));
triggers.push_back(
new TriggerNode("light aoe", NextAction::array(0, new NextAction("multi-shot", 21.0f), nullptr)));
triggers.push_back(new TriggerNode("light aoe", NextAction::array(0, new NextAction("multi-shot", 21.0f), nullptr)));
}
void HunterBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -101,6 +101,7 @@ public:
creators["lock and load"] = &HunterTriggerFactoryInternal::lock_and_load;
creators["silencing shot"] = &HunterTriggerFactoryInternal::silencing_shot;
creators["intimidation"] = &HunterTriggerFactoryInternal::intimidation;
creators["volley channel check"] = &HunterTriggerFactoryInternal::volley_channel_check;
}
private:
@@ -141,6 +142,7 @@ private:
static Trigger* lock_and_load(PlayerbotAI* botAI) { return new LockAndLoadTrigger(botAI); }
static Trigger* silencing_shot(PlayerbotAI* botAI) { return new SilencingShotTrigger(botAI); }
static Trigger* intimidation(PlayerbotAI* botAI) { return new IntimidationTrigger(botAI); }
static Trigger* volley_channel_check(PlayerbotAI* botAI) { return new VolleyChannelCheckTrigger(botAI); }
};
class HunterAiObjectContextInternal : public NamedObjectContext<Action>

View File

@@ -12,6 +12,7 @@
#include "Playerbots.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "Player.h"
bool KillCommandTrigger::IsActive()
{
@@ -139,3 +140,31 @@ bool SerpentStingOnAttackerTrigger::IsActive()
!botAI->HasAura("viper sting", target, false, true);
return BuffTrigger::IsActive();
}
const std::set<uint32> VolleyChannelCheckTrigger::VOLLEY_SPELL_IDS = {
1510, // Volley Rank 1
14294, // Volley Rank 2
14295, // Volley Rank 3
27022, // Volley Rank 4
58431, // Volley Rank 5
58434 // Volley Rank 6
};
bool VolleyChannelCheckTrigger::IsActive()
{
Player* bot = botAI->GetBot();
// Check if the bot is channeling a spell
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
// Only trigger if the spell being channeled is Volley
if (VOLLEY_SPELL_IDS.count(spell->m_spellInfo->Id))
{
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
}
// Not channeling Volley
return false;
}

View File

@@ -10,6 +10,7 @@
#include "GenericTriggers.h"
#include "Trigger.h"
#include "PlayerbotAI.h"
#include <set>
class PlayerbotAI;
@@ -244,4 +245,19 @@ END_TRIGGER()
BEGIN_TRIGGER(HunterPetNotHappy, Trigger)
END_TRIGGER()
class VolleyChannelCheckTrigger : public Trigger
{
public:
VolleyChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
: Trigger(botAI, "volley channel check"), minEnemies(minEnemies)
{
}
bool IsActive() override;
protected:
uint32 minEnemies;
static const std::set<uint32> VOLLEY_SPELL_IDS;
};
#endif

View File

@@ -142,13 +142,3 @@ bool CastBlinkBackAction::Execute(Event event)
bot->SetOrientation(bot->GetAngle(target) + M_PI);
return CastSpellAction::Execute(event);
}
bool CancelChannelAction::Execute(Event event)
{
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
bot->InterruptSpell(CURRENT_CHANNELED_SPELL);
return true;
}
return false;
}

View File

@@ -404,12 +404,4 @@ public:
bool isUseful() override;
};
class CancelChannelAction : public Action
{
public:
CancelChannelAction(PlayerbotAI* botAI) : Action(botAI, "cancel channel") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -237,7 +237,6 @@ public:
creators["use mana citrine"] = &MageAiObjectContextInternal::use_mana_citrine;
creators["use mana jade"] = &MageAiObjectContextInternal::use_mana_jade;
creators["use mana agate"] = &MageAiObjectContextInternal::use_mana_agate;
creators["cancel channel"] = &MageAiObjectContextInternal::cancel_channel;
creators["mana shield"] = &MageAiObjectContextInternal::mana_shield;
}
@@ -299,7 +298,6 @@ private:
static Action* use_mana_citrine(PlayerbotAI* botAI) { return new UseManaCitrineAction(botAI); }
static Action* use_mana_jade(PlayerbotAI* botAI) { return new UseManaJadeAction(botAI); }
static Action* use_mana_agate(PlayerbotAI* botAI) { return new UseManaAgateAction(botAI); }
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
static Action* mana_shield(PlayerbotAI* botAI) { return new CastManaShieldAction(botAI); }
};

View File

@@ -105,6 +105,7 @@ public:
creators["silence"] = &PriestTriggerFactoryInternal::silence;
creators["silence on enemy healer"] = &PriestTriggerFactoryInternal::silence_on_enemy_healer;
creators["shadowfiend"] = &PriestTriggerFactoryInternal::shadowfiend;
creators["mind sear channel check"] = &PriestTriggerFactoryInternal::mind_sear_channel_check;
}
private:
@@ -148,6 +149,7 @@ private:
static Trigger* silence(PlayerbotAI* botAI) { return new SilenceTrigger(botAI); }
static Trigger* chastise(PlayerbotAI* botAI) { return new ChastiseTrigger(botAI); }
static Trigger* binding_heal(PlayerbotAI* botAI) { return new BindingHealTrigger(botAI); }
static Trigger* mind_sear_channel_check(PlayerbotAI* botAI) { return new MindSearChannelCheckTrigger(botAI); }
};
class PriestAiObjectContextInternal : public NamedObjectContext<Action>

View File

@@ -4,7 +4,8 @@
*/
#include "PriestTriggers.h"
#include "PlayerbotAI.h"
#include "Player.h"
#include "Playerbots.h"
bool PowerWordFortitudeOnPartyTrigger::IsActive()
@@ -76,3 +77,27 @@ bool BindingHealTrigger::IsActive()
return PartyMemberLowHealthTrigger::IsActive() &&
AI_VALUE2(uint8, "health", "self target") < sPlayerbotAIConfig->mediumHealth;
}
const std::set<uint32> MindSearChannelCheckTrigger::MIND_SEAR_SPELL_IDS = {
48045, // Mind Sear Rank 1
53023 // Mind Sear Rank 2
};
bool MindSearChannelCheckTrigger::IsActive()
{
Player* bot = botAI->GetBot();
// Check if the bot is channeling a spell
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
// Only trigger if the spell being channeled is Mind Sear
if (MIND_SEAR_SPELL_IDS.count(spell->m_spellInfo->Id))
{
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
}
// Not channeling Mind Sear
return false;
}

View File

@@ -8,6 +8,8 @@
#include "CureTriggers.h"
#include "SharedDefines.h"
#include "Trigger.h"
#include <set>
class PlayerbotAI;
@@ -100,4 +102,19 @@ public:
bool IsActive() override;
};
class MindSearChannelCheckTrigger : public Trigger
{
public:
MindSearChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
: Trigger(botAI, "mind sear channel check"), minEnemies(minEnemies)
{
}
bool IsActive() override;
protected:
uint32 minEnemies;
static const std::set<uint32> MIND_SEAR_SPELL_IDS;
};
#endif

View File

@@ -54,6 +54,8 @@ void ShadowPriestAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"vampiric touch on attacker",
NextAction::array(0, new NextAction("vampiric touch on attacker", ACTION_NORMAL + 4), nullptr)));
triggers.push_back(
new TriggerNode("mind sear channel check", NextAction::array(0, new NextAction("cancel channel", ACTION_HIGH + 5), nullptr)));
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("mind sear", ACTION_HIGH + 4), nullptr)));
}

View File

@@ -58,6 +58,9 @@ void AoEWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new NextAction("seed of corruption on attacker", 22.0f),
new NextAction("seed of corruption", 21.5f),
new NextAction("rain of fire", 21.0f), nullptr)));
triggers.push_back(
new TriggerNode("rain of fire channel check", NextAction::array(0, new NextAction("cancel channel", 21.5f), nullptr)));
}
void WarlockBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -172,6 +172,7 @@ public:
creators["curse of tongues"] = &WarlockTriggerFactoryInternal::curse_of_tongues;
creators["curse of weakness"] = &WarlockTriggerFactoryInternal::curse_of_weakness;
creators["wrong pet"] = &WarlockTriggerFactoryInternal::wrong_pet;
creators["rain of fire channel check"] = &WarlockTriggerFactoryInternal::rain_of_fire_channel_check;
}
private:
@@ -217,6 +218,7 @@ private:
static Trigger* curse_of_tongues(PlayerbotAI* ai) { return new CurseOfTonguesTrigger(ai); }
static Trigger* curse_of_weakness(PlayerbotAI* ai) { return new CurseOfWeaknessTrigger(ai); }
static Trigger* wrong_pet(PlayerbotAI* ai) { return new WrongPetTrigger(ai); }
static Trigger* rain_of_fire_channel_check(PlayerbotAI* ai) { return new RainOfFireChannelCheckTrigger(ai); }
};
class WarlockAiObjectContextInternal : public NamedObjectContext<Action>

View File

@@ -6,6 +6,8 @@
#include "WarlockTriggers.h"
#include "GenericTriggers.h"
#include "Playerbots.h"
#include "PlayerbotAI.h"
#include "Player.h"
static const uint32 SOUL_SHARD_ITEM_ID = 6265;
@@ -228,3 +230,32 @@ bool WrongPetTrigger::IsActive()
// Step 6: If we get here, the bot doesn't know the spell required to support the active pet strategy
return false;
}
const std::set<uint32> RainOfFireChannelCheckTrigger::RAIN_OF_FIRE_SPELL_IDS = {
5740, // Rain of Fire Rank 1
6219, // Rain of Fire Rank 2
11677, // Rain of Fire Rank 3
11678, // Rain of Fire Rank 4
27212, // Rain of Fire Rank 5
47819, // Rain of Fire Rank 6
47820 // Rain of Fire Rank 7
};
bool RainOfFireChannelCheckTrigger::IsActive()
{
Player* bot = botAI->GetBot();
// Check if the bot is channeling a spell
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
// Only trigger if the spell being channeled is Rain of Fire
if (RAIN_OF_FIRE_SPELL_IDS.count(spell->m_spellInfo->Id))
{
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
}
// Not channeling Rain of Fire
return false;
}

View File

@@ -10,6 +10,8 @@
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "CureTriggers.h"
#include "Trigger.h"
#include <set>
class PlayerbotAI;
@@ -332,4 +334,19 @@ public:
: TwoTriggers(ai, "enemy too close for spell", "metamorphosis not active") {}
};
class RainOfFireChannelCheckTrigger : public Trigger
{
public:
RainOfFireChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
: Trigger(botAI, "rain of fire channel check"), minEnemies(minEnemies)
{
}
bool IsActive() override;
protected:
uint32 minEnemies;
static const std::set<uint32> RAIN_OF_FIRE_SPELL_IDS;
};
#endif