Warlock Curse/Spellstone and Firestone Strategies, Soul Shard Replenish

This PR aims to achieve 4 main things:

1. Manual Curse Strategies - Each curse has it's own toggleable combat strategy, with curse of agony being the default for affliction and demonology, and curse of the elements being default for destro. The other curses that are available are curse of exhaustion (if specced for it), curse of doom, curse of weakness, and curse of tongues (6 total). You can add these by typing "co +curse of weakness", or similar. Note: Curses are part of the WarlockCurseStrategyFactoryInternal(), so there can only be one active.

2. Firestone/Spellstone Non-Combat Strategies - Players requested to me that they can decide if they want to use a spellstone or firestone for their weapon enchant, so I added them as non-combat strategies. Spellstone is the default for affliction and demonology, firestone is the default for destro. To add these, type "nc +firestone" or similar.

3. Soul Shard Replenishment - I noticed after hours of running a server (15+ hours) that altbots and rndbots would only cast imp and not use their soul shards. This is because they were actually running out of soul shards, without the ability to maintain themselves accordingly. I added a trigger (no soul shard) and action (create soul shard) that triggers if they are out of soul shards, creating only 1 soul shard (to not clog up the inventory). This way, you should never have a warlock using the wrong pet, or failing to cast shadowburn, failing to create soulstone/spellstone/firestone/healthstone, or failing to cast soul shatter.

4. Tidying up the code -

I removed the built-in curse code from the DPS strategies, and migrated it to the manual curse strategies.

I clumped the curse triggers and actions together in the associated files.

I added logic for Curse of Weakness to check for conflicting debuffs.

I moved the summoning strategies and curse strategies to their own strategy factories - WarlockPetStrategyFactoryInternal, and WarlockCurseStrategyFactoryInternal. This way they can only have one curse and one pet strategy active at once. I also renamed the "NonCombatBuffStrategyFactoryInternal" to "WarlockSoulstoneStrategyFactoryInternal", which was more accurate.

I changed a single talent point in the Affliction Warlock PVE spec, taking one from destructive reach and adding it into nightfall for those sweet, sweet free shadowbolts.

I added "ss self" as the default non-combat soulstone strategy, as before, they didn't have one assigned. The player can still of course remove that NC strategy and apply another, such as "ss master", "ss tank", or "ss healer".
This commit is contained in:
ThePenguinMan96
2025-07-07 18:51:33 -07:00
parent 8b9bcce3bc
commit 393a65a15b
14 changed files with 446 additions and 185 deletions

View File

@@ -1440,7 +1440,7 @@ AiPlayerbot.PremadeSpecName.9.0 = affli pve
AiPlayerbot.PremadeSpecGlyph.9.0 = 45785,43390,50077,43394,43393,45779
AiPlayerbot.PremadeSpecLink.9.0.60 = 2350022001113510053500131151
AiPlayerbot.PremadeSpecLink.9.0.70 = 2350022001113510053500131151--55
AiPlayerbot.PremadeSpecLink.9.0.80 = 2350022001113510253500331151--5500000501
AiPlayerbot.PremadeSpecLink.9.0.80 = 2350022001123510253500331151--55000005
AiPlayerbot.PremadeSpecName.9.1 = demo pve
AiPlayerbot.PremadeSpecGlyph.9.1 = 45785,43390,50077,43394,43393,42459
AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051

View File

@@ -386,9 +386,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break;
case CLASS_WARLOCK:
if (tab == 0) // Affliction
engine->addStrategiesNoInit("affli", "affli aoe", nullptr);
engine->addStrategiesNoInit("affli", "affli aoe", "curse of agony", nullptr);
else if (tab == 1) // Demonology
engine->addStrategiesNoInit("demo", "demo aoe", "meta melee", nullptr);
engine->addStrategiesNoInit("demo", "demo aoe", "curse of agony", "meta melee", nullptr);
else if (tab == 2) // Destruction
engine->addStrategiesNoInit("destro", "destro aoe", "curse of elements", nullptr);
@@ -596,17 +596,17 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
case CLASS_WARLOCK:
if (tab == WARLOCK_TAB_AFFLICATION)
{
nonCombatEngine->addStrategiesNoInit("felhunter", nullptr);
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DEMONOLOGY)
{
nonCombatEngine->addStrategiesNoInit("felguard", nullptr);
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DESTRUCTION)
{
nonCombatEngine->addStrategiesNoInit("imp", nullptr);
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
}
nonCombatEngine->addStrategiesNoInit("dps assist", nullptr);
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
break;
case CLASS_DEATH_KNIGHT:
if (tab == 0)

View File

@@ -16,8 +16,6 @@ public:
creators["corruption on attacker"] = &corruption;
creators["unstable affliction"] = &unstable_affliction;
creators["unstable affliction on attacker"] = &unstable_affliction;
creators["curse of agony"] = &curse_of_agony;
creators["curse of agony on attacker"] = &curse_of_agony;
creators["haunt"] = &haunt;
creators["shadow bolt"] = &shadow_bolt;
creators["drain soul"] = &drain_soul;
@@ -33,8 +31,6 @@ private:
static ActionNode* corruption_on_attacker(PlayerbotAI*) { return new ActionNode("corruption on attacker", nullptr, nullptr, nullptr); }
static ActionNode* unstable_affliction(PlayerbotAI*) { return new ActionNode("unstable affliction", nullptr, nullptr, nullptr); }
static ActionNode* unstable_affliction_on_attacker(PlayerbotAI*) { return new ActionNode("unstable affliction on attacker", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony(PlayerbotAI*) { return new ActionNode("curse of agony", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony_on_attacker(PlayerbotAI*) { return new ActionNode("curse of agony on attacker", nullptr, nullptr, nullptr); }
static ActionNode* haunt(PlayerbotAI*) { return new ActionNode("haunt", nullptr, nullptr, nullptr); }
static ActionNode* shadow_bolt(PlayerbotAI*) { return new ActionNode("shadow bolt", nullptr, nullptr, nullptr); }
static ActionNode* drain_soul(PlayerbotAI*) { return new ActionNode("drain soul", nullptr, nullptr, nullptr); }
@@ -55,9 +51,8 @@ AfflictionWarlockStrategy::AfflictionWarlockStrategy(PlayerbotAI* botAI) : Gener
NextAction** AfflictionWarlockStrategy::getDefaultActions()
{
return NextAction::array( 0,
new NextAction("corruption", 5.6f),
new NextAction("unstable affliction", 5.5f),
new NextAction("curse of agony", 5.4f),
new NextAction("corruption", 5.5f),
new NextAction("unstable affliction", 5.4f),
new NextAction("haunt", 5.3f),
new NextAction("shadow bolt", 5.2f),
new NextAction("shoot", 5.0f), nullptr);
@@ -71,10 +66,8 @@ void AfflictionWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
// Main DoT triggers for high uptime
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 19.5f), nullptr)));
triggers.push_back(new TriggerNode("unstable affliction on attacker", NextAction::array(0, new NextAction("unstable affliction on attacker", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 18.5f), nullptr)));
triggers.push_back(new TriggerNode("corruption", NextAction::array(0, new NextAction("corruption", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("unstable affliction", NextAction::array(0, new NextAction("unstable affliction", 17.5f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony", NextAction::array(0, new NextAction("curse of agony", 17.0f), nullptr)));
triggers.push_back(new TriggerNode("haunt", NextAction::array(0, new NextAction("haunt", 16.5f), nullptr)));
// Drain Soul as execute if target is low HP // Shadow Trance for free casts
@@ -82,7 +75,7 @@ void AfflictionWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
triggers.push_back(new TriggerNode("target critical health", NextAction::array(0, new NextAction("drain soul", 15.5f), nullptr)));
// Life Tap glyph buff, and Life Tap as filler
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.5f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 5.1f), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell", NextAction::array(0, new NextAction("flee", 39.0f), nullptr)));

View File

@@ -14,8 +14,6 @@ public:
{
creators["metamorphosis"] = &metamorphosis;
creators["demonic empowerment"] = &demonic_empowerment;
creators["curse of agony"] = &curse_of_agony;
creators["curse of agony on attacker"] = &curse_of_agony_on_attacker;
creators["corruption"] = &corruption;
creators["corruption on attacker"] = &corruption_on_attacker;
creators["immolate"] = &immolate;
@@ -35,8 +33,6 @@ public:
private:
static ActionNode* metamorphosis(PlayerbotAI*) { return new ActionNode("metamorphosis", nullptr, nullptr, nullptr); }
static ActionNode* demonic_empowerment(PlayerbotAI*) { return new ActionNode("demonic empowerment", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony(PlayerbotAI*) { return new ActionNode("curse of agony", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony_on_attacker(PlayerbotAI*) { return new ActionNode("curse of agony on attacker", nullptr, nullptr, nullptr); }
static ActionNode* corruption(PlayerbotAI*) { return new ActionNode("corruption", nullptr, nullptr, nullptr); }
static ActionNode* corruption_on_attacker(PlayerbotAI*) { return new ActionNode("corruption on attacker", nullptr, nullptr, nullptr); }
static ActionNode* immolate(PlayerbotAI*) { return new ActionNode("immolate", nullptr, nullptr, nullptr); }
@@ -63,8 +59,7 @@ DemonologyWarlockStrategy::DemonologyWarlockStrategy(PlayerbotAI* botAI) : Gener
NextAction** DemonologyWarlockStrategy::getDefaultActions()
{
return NextAction::array(0,
new NextAction("corruption", 5.6f),
new NextAction("curse of agony", 5.5f),
new NextAction("corruption", 5.5f),
new NextAction("immolate", 5.4f),
new NextAction("shadow bolt", 5.3f),
new NextAction("incinerate", 5.2f),
@@ -81,11 +76,9 @@ void DemonologyWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
triggers.push_back(new TriggerNode("demonic empowerment", NextAction::array(0, new NextAction("demonic empowerment", 28.0f), nullptr)));
// Main DoT triggers for high uptime
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 20.0f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 19.5f), nullptr)));
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 19.5f), nullptr)));
triggers.push_back(new TriggerNode("immolate on attacker", NextAction::array(0, new NextAction("immolate on attacker", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("corruption", NextAction::array(0, new NextAction("corruption", 18.5f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony", NextAction::array(0, new NextAction("curse of agony", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("corruption", NextAction::array(0, new NextAction("corruption", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("immolate", NextAction::array(0, new NextAction("immolate", 17.5f), nullptr)));
// Procs
@@ -93,7 +86,7 @@ void DemonologyWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
triggers.push_back(new TriggerNode("molten core", NextAction::array(0, new NextAction("incinerate", 16.5f), nullptr)));
// Life Tap glyph buff, and Life Tap as filler
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.5f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 5.1f), nullptr)));
triggers.push_back(new TriggerNode("meta melee flee check", NextAction::array(0, new NextAction("flee", 39.0f), nullptr)));

View File

@@ -18,8 +18,6 @@ public:
creators["incinerate"] = &incinerate;
creators["corruption"] = &corruption;
creators["corruption on attacker"] = &corruption_on_attacker;
creators["curse of agony"] = &curse_of_agony;
creators["curse of agony on attacker"] = &curse_of_agony_on_attacker;
creators["shadow bolt"] = &shadow_bolt;
creators["shadowburn"] = &shadowburn;
creators["life tap"] = &life_tap;
@@ -37,8 +35,6 @@ private:
static ActionNode* incinerate(PlayerbotAI*) { return new ActionNode("incinerate", nullptr, nullptr, nullptr); }
static ActionNode* corruption(PlayerbotAI*) { return new ActionNode("corruption", nullptr, nullptr, nullptr); }
static ActionNode* corruption_on_attacker(PlayerbotAI*) { return new ActionNode("corruption on attacker", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony(PlayerbotAI*) { return new ActionNode("curse of agony", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony_on_attacker(PlayerbotAI*) { return new ActionNode("curse of agony on attacker", nullptr, nullptr, nullptr); }
static ActionNode* shadow_bolt(PlayerbotAI*) { return new ActionNode("shadow bolt", nullptr, nullptr, nullptr); }
static ActionNode* shadowburn(PlayerbotAI*) { return new ActionNode("shadowburn", nullptr, nullptr, nullptr); }
static ActionNode* life_tap(PlayerbotAI*) { return new ActionNode("life tap", nullptr, nullptr, nullptr); }
@@ -59,12 +55,11 @@ DestructionWarlockStrategy::DestructionWarlockStrategy(PlayerbotAI* botAI) : Gen
NextAction** DestructionWarlockStrategy::getDefaultActions()
{
return NextAction::array( 0,
new NextAction("immolate", 6.1f),
new NextAction("conflagrate", 6.0f),
new NextAction("chaos bolt", 5.9f),
new NextAction("incinerate", 5.8f),
new NextAction("corruption", 5.4f), // Note: Corruption, Curse of Agony, and Shadow Bolt won't be used after
new NextAction("curse of agony", 5.3f), // the character learns Incinerate at level 64
new NextAction("immolate", 5.9f),
new NextAction("conflagrate", 5.8f),
new NextAction("chaos bolt", 5.7f),
new NextAction("incinerate", 5.6f),
new NextAction("corruption", 5.3f), // Note: Corruption and Shadow Bolt won't be used after the character learns Incinerate at level 64
new NextAction("shadow bolt", 5.2f),
new NextAction("shoot", 5.0f), nullptr);
}
@@ -79,17 +74,15 @@ void DestructionWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("conflagrate", NextAction::array(0, new NextAction("conflagrate", 19.5f), nullptr)));
triggers.push_back(new TriggerNode("chaos bolt", NextAction::array(0, new NextAction("chaos bolt", 19.0f), nullptr)));
// Note: These DoTs won't be used after the character learns Incinerate at level 64
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 5.6f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 5.5f), nullptr)));
// Note: Corruption won't be used after the character learns Incinerate at level 64
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 5.5f), nullptr)));
triggers.push_back(new TriggerNode("corruption", NextAction::array(0, new NextAction("corruption", 5.4f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony", NextAction::array(0, new NextAction("curse of agony", 5.3f), nullptr)));
// Shadowburn as execute if target is low HP
triggers.push_back(new TriggerNode("target critical health", NextAction::array(0, new NextAction("shadowburn", 18.0f), nullptr)));
// Life Tap glyph buff, and Life Tap as filler
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.5f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 5.1f), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell", NextAction::array(0, new NextAction("flee", 39.0f), nullptr)));

View File

@@ -22,7 +22,7 @@ public:
// Pet skills are setup in pass-through fashion, so if one fails, it attempts to cast the next one
// The order goes Felguard -> Felhunter -> Succubus -> Voidwalker -> Imp
// Pets are summoned based on the non-combat strategy you have active, the warlock's level, and if they have a soulstone available
// Pets are summoned based on the non-combat strategy you have active, the warlock's level, and if they have a soul shard available
private:
static ActionNode* fel_armor([[maybe_unused]] PlayerbotAI* botAI)
@@ -80,27 +80,12 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("fel domination", 30.0f), nullptr)));
triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 29.5f), nullptr)));
triggers.push_back(new TriggerNode("soul link", NextAction::array(0, new NextAction("soul link", 28.0f), nullptr)));
triggers.push_back(new TriggerNode("demon armor", NextAction::array(0, new NextAction("fel armor", 27.0f), nullptr)));
triggers.push_back(new TriggerNode("no healthstone", NextAction::array(0, new NextAction("create healthstone", 26.0f), nullptr)));
triggers.push_back(new TriggerNode("no soulstone", NextAction::array(0, new NextAction("create soulstone", 25.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 23.0f), nullptr)));
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
// Firestone/Spellstone triggers
if (tab == 2) // Destruction uses Firestone
{
triggers.push_back(new TriggerNode("no firestone", NextAction::array(0, new NextAction("create firestone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("firestone", NextAction::array(0, new NextAction("firestone", 24.0f), nullptr)));
}
else // Affliction and Demonology use Spellstone
{
triggers.push_back(new TriggerNode("no spellstone", NextAction::array(0, new NextAction("create spellstone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("spellstone", NextAction::array(0, new NextAction("spellstone", 24.0f), nullptr)));
}
}
// Non-combat strategy for summoning a Imp
@@ -177,8 +162,7 @@ SoulstoneMasterStrategy::SoulstoneMasterStrategy(PlayerbotAI* ai) : NonCombatStr
void SoulstoneMasterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("soulstone", NextAction::array(0, new NextAction("soulstone master", 24.0f), NULL)));
triggers.push_back(new TriggerNode("soulstone", NextAction::array(0, new NextAction("soulstone master", 24.0f), NULL)));
}
// Non-combat strategy for selecting tanks to receive soulstone
@@ -189,8 +173,7 @@ SoulstoneTankStrategy::SoulstoneTankStrategy(PlayerbotAI* ai) : NonCombatStrateg
void SoulstoneTankStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("soulstone", NextAction::array(0, new NextAction("soulstone tank", 24.0f), NULL)));
triggers.push_back(new TriggerNode("soulstone", NextAction::array(0, new NextAction("soulstone tank", 24.0f), NULL)));
}
// Non-combat strategy for selecting healers to receive soulstone
@@ -201,6 +184,29 @@ SoulstoneHealerStrategy::SoulstoneHealerStrategy(PlayerbotAI* ai) : NonCombatStr
void SoulstoneHealerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("soulstone", NextAction::array(0, new NextAction("soulstone healer", 24.0f), NULL)));
triggers.push_back(new TriggerNode("soulstone", NextAction::array(0, new NextAction("soulstone healer", 24.0f), NULL)));
}
// Non-combat strategy for using Spellstone
// Enabled by default for Affliction and Demonology specs
// To enable, type "nc +spellstone"
// To disable, type "nc -spellstone"
UseSpellstoneStrategy::UseSpellstoneStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {}
void UseSpellstoneStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no spellstone", NextAction::array(0, new NextAction("create spellstone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("spellstone", NextAction::array(0, new NextAction("spellstone", 24.0f), nullptr)));
}
// Non-combat strategy for using Firestone
// Enabled by default for the Destruction spec
// To enable, type "nc +firestone"
// To disable, type "nc -firestone"
UseFirestoneStrategy::UseFirestoneStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {}
void UseFirestoneStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no firestone", NextAction::array(0, new NextAction("create firestone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("firestone", NextAction::array(0, new NextAction("firestone", 24.0f), nullptr)));
}

View File

@@ -109,4 +109,24 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class UseSpellstoneStrategy : public NonCombatStrategy
{
public:
UseSpellstoneStrategy(PlayerbotAI* ai);
virtual std::string const getName() override { return "spellstone"; }
public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class UseFirestoneStrategy : public NonCombatStrategy
{
public:
UseFirestoneStrategy(PlayerbotAI* ai);
virtual std::string const getName() override { return "firestone"; }
public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
#endif

View File

@@ -38,11 +38,12 @@ void GenericWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
CombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("life tap", ACTION_EMERGENCY + 5), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("life tap", 95.0f), nullptr)));
triggers.push_back(new TriggerNode("medium threat", NextAction::array(0, new NextAction("soulshatter", 55.0f), nullptr)));
triggers.push_back(new TriggerNode("spell lock", NextAction::array(0, new NextAction("spell lock", ACTION_INTERRUPT), nullptr)));
triggers.push_back(new TriggerNode("devour magic purge", NextAction::array(0, new NextAction("devour magic purge", ACTION_DISPEL), nullptr)));
triggers.push_back(new TriggerNode("devour magic cleanse", NextAction::array(0, new NextAction("devour magic cleanse", ACTION_DISPEL), nullptr)));
triggers.push_back(new TriggerNode("spell lock", NextAction::array(0, new NextAction("spell lock", 40.0f), nullptr)));
triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("devour magic purge", NextAction::array(0, new NextAction("devour magic purge", 50.0f), nullptr)));
triggers.push_back(new TriggerNode("devour magic cleanse", NextAction::array(0, new NextAction("devour magic cleanse", 50.0f), nullptr)));
}
void WarlockBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -61,13 +62,57 @@ void WarlockCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("fear", NextAction::array(0, new NextAction("fear on cc", 32.0f), nullptr)));
}
// Combat strategy for using Curse of Agony
// Enabled by default for the Affliction spec
// To enable, type "co +curse of agony"
// To disable, type "co -curse of agony"
void WarlockCurseOfAgonyStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 18.5f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony", NextAction::array(0, new NextAction("curse of agony", 17.0f), nullptr)));
}
// Combat strategy for using Curse of the Elements
// Enabling this will turn off their use of Curse of Agony
// Enabled by default for the Destruction spec
// To enable, type "co +curse of elements"
// To disable, type "co -curse of elements"
void WarlockCurseOfTheElementsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("curse of the elements", NextAction::array(0, new NextAction("curse of the elements", 30.0f), nullptr)));
triggers.push_back(new TriggerNode("curse of the elements", NextAction::array(0, new NextAction("curse of the elements", 29.0f), nullptr)));
}
// Combat strategy for using Curse of Doom
// Disabled by default
// To enable, type "co +curse of doom"
// To disable, type "co -curse of doom"
void WarlockCurseOfDoomStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("curse of doom", NextAction::array(0, new NextAction("curse of doom", 29.0f), nullptr)));
}
// Combat strategy for using Curse of Exhaustion
// Disabled by default
// To enable, type "co +curse of exhaustion"
// To disable, type "co -curse of exhaustion"
void WarlockCurseOfExhaustionStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("curse of exhaustion", NextAction::array(0, new NextAction("curse of exhaustion", 29.0f), nullptr)));
}
// Combat strategy for using Curse of Tongues
// Disabled by default
// To enable, type "co +curse of tongues"
// To disable, type "co -curse of tongues"
void WarlockCurseOfTonguesStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("curse of tongues", NextAction::array(0, new NextAction("curse of tongues", 29.0f), nullptr)));
}
// Combat strategy for using Curse of Weakness
// Disabled by default
// To enable, type "co +curse of weakness"
// To disable, type "co -curse of weakness"
void WarlockCurseOfWeaknessStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("curse of weakness", NextAction::array(0, new NextAction("curse of weakness", 29.0f), nullptr)));
}

View File

@@ -48,6 +48,15 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class WarlockCurseOfAgonyStrategy : public Strategy
{
public:
WarlockCurseOfAgonyStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "curse of agony"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class WarlockCurseOfTheElementsStrategy : public Strategy
{
public:
@@ -57,4 +66,40 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class WarlockCurseOfDoomStrategy : public Strategy
{
public:
WarlockCurseOfDoomStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "curse of doom"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class WarlockCurseOfExhaustionStrategy : public Strategy
{
public:
WarlockCurseOfExhaustionStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "curse of exhaustion"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class WarlockCurseOfTonguesStrategy : public Strategy
{
public:
WarlockCurseOfTonguesStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "curse of tongues"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class WarlockCurseOfWeaknessStrategy : public Strategy
{
public:
WarlockCurseOfWeaknessStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "curse of weakness"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
#endif

View File

@@ -113,6 +113,28 @@ bool CastSoulshatterAction::isUseful()
return true;
}
// Checks if the bot has enough bag space to create a soul shard, then does so
bool CreateSoulShardAction::Execute(Event event)
{
Player* bot = botAI->GetBot();
if (!bot)
return false;
// Soul Shard item ID is 6265
uint32 soulShardId = 6265;
ItemPosCountVec dest;
uint32 count = 1;
if (bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, soulShardId, count) == EQUIP_ERR_OK)
{
bot->StoreNewItem(dest, soulShardId, true, Item::GenerateItemRandomPropertyId(soulShardId));
SQLTransaction trans = CharacterDatabase.BeginTransaction();
bot->SaveInventoryAndGoldToDB(trans);
CharacterDatabase.CommitTransaction(trans);
return true;
}
return false;
}
// Checks if the target has a soulstone aura
static bool HasSoulstoneAura(Unit* unit)
{

View File

@@ -40,6 +40,13 @@ public:
std::string const GetTargetName() override { return "pet target"; }
};
class CreateSoulShardAction : public Action
{
public:
CreateSoulShardAction(PlayerbotAI* botAI) : Action(botAI, "create soul shard") {}
bool Execute(Event event) override;
};
class CastCreateHealthstoneAction : public CastBuffSpellAction
{
public:
@@ -190,23 +197,6 @@ public:
bool isUseful() override;
};
class CastCurseOfWeaknessAction : public CastDebuffSpellAction
{
public:
CastCurseOfWeaknessAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of weakness") {}
};
class CastCurseOfTheElementsAction : public CastDebuffSpellAction
{
public:
CastCurseOfTheElementsAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "curse of the elements", true) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class DemonChargeAction : public CastSpellAction
{
public:
@@ -228,38 +218,7 @@ public:
std::string const GetTargetName() override { return "pet target"; }
};
// DoT Spells
class CastCurseOfAgonyAction : public CastDebuffSpellAction
{
public:
CastCurseOfAgonyAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of agony", true) {}
bool isUseful() override
{
if (botAI->HasStrategy(
"curse of elements", BOT_STATE_COMBAT)) // If Curse of the Elements strategy is active, do not cast Curse of Agony
return false;
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfAgonyOnAttackerAction : public CastDebuffSpellOnAttackerAction
{
public:
CastCurseOfAgonyOnAttackerAction(PlayerbotAI* botAI)
: CastDebuffSpellOnAttackerAction(botAI, "curse of agony", true)
{
}
bool isUseful() override
{
if (botAI->HasStrategy(
"curse of elements", BOT_STATE_COMBAT)) // If Curse of the Elements strategy is active, do not cast Curse of Agony
return false;
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
// DoT/Curse Spells
class CastCorruptionAction : public CastDebuffSpellAction
{
@@ -330,6 +289,86 @@ public:
}
};
class CastCurseOfAgonyAction : public CastDebuffSpellAction
{
public:
CastCurseOfAgonyAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of agony", true) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfAgonyOnAttackerAction : public CastDebuffSpellOnAttackerAction
{
public:
CastCurseOfAgonyOnAttackerAction(PlayerbotAI* botAI)
: CastDebuffSpellOnAttackerAction(botAI, "curse of agony", true)
{
}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfTheElementsAction : public CastDebuffSpellAction
{
public:
CastCurseOfTheElementsAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "curse of the elements", true) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfDoomAction : public CastDebuffSpellAction
{
public:
CastCurseOfDoomAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of doom", true, 0) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfExhaustionAction : public CastDebuffSpellAction
{
public:
CastCurseOfExhaustionAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of exhaustion") {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfTonguesAction : public CastDebuffSpellAction
{
public:
CastCurseOfTonguesAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of tongues") {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastCurseOfWeaknessAction : public CastDebuffSpellAction
{
public:
CastCurseOfWeaknessAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "curse of weakness") {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
// Damage Spells
class CastShadowBoltAction : public CastSpellAction

View File

@@ -28,34 +28,29 @@ public:
creators["boost"] = &WarlockStrategyFactoryInternal::boost;
creators["cc"] = &WarlockStrategyFactoryInternal::cc;
creators["pet"] = &WarlockStrategyFactoryInternal::pet;
creators["spellstone"] = &WarlockStrategyFactoryInternal::spellstone;
creators["firestone"] = &WarlockStrategyFactoryInternal::firestone;
creators["affli aoe"] = &WarlockStrategyFactoryInternal::affliction_aoe;
creators["demo aoe"] = &WarlockStrategyFactoryInternal::demonology_aoe;
creators["destro aoe"] = &WarlockStrategyFactoryInternal::destruction_aoe;
creators["meta melee"] = &WarlockStrategyFactoryInternal::meta_melee_aoe;
creators["curse of elements"] = &WarlockStrategyFactoryInternal::curse_of_elements;
creators["imp"] = &WarlockStrategyFactoryInternal::imp;
creators["voidwalker"] = &WarlockStrategyFactoryInternal::voidwalker;
creators["succubus"] = &WarlockStrategyFactoryInternal::succubus;
creators["felhunter"] = &WarlockStrategyFactoryInternal::felhunter;
creators["felguard"] = &WarlockStrategyFactoryInternal::felguard;
creators["tank"] = &WarlockStrategyFactoryInternal::tank;
}
private:
static Strategy* pet(PlayerbotAI* botAI) { return new WarlockPetStrategy(botAI); }
static Strategy* nc(PlayerbotAI* botAI) { return new GenericWarlockNonCombatStrategy(botAI); }
static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "shoot"); }
static Strategy* boost(PlayerbotAI* botAI) { return new WarlockBoostStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new WarlockCcStrategy(botAI); }
static Strategy* pet(PlayerbotAI* botAI) { return new WarlockPetStrategy(botAI); }
static Strategy* spellstone(PlayerbotAI* botAI) { return new UseSpellstoneStrategy(botAI); }
static Strategy* firestone(PlayerbotAI* botAI) { return new UseFirestoneStrategy(botAI); }
static Strategy* affliction_aoe(PlayerbotAI* botAI) { return new AfflictionWarlockAoeStrategy(botAI); }
static Strategy* demonology_aoe(PlayerbotAI* botAI) { return new DemonologyWarlockAoeStrategy(botAI); }
static Strategy* destruction_aoe(PlayerbotAI* botAI) { return new DestructionWarlockAoeStrategy(botAI); }
static Strategy* meta_melee_aoe(PlayerbotAI* botAI) { return new MetaMeleeAoeStrategy(botAI); }
static Strategy* curse_of_elements(PlayerbotAI* botAI) { return new WarlockCurseOfTheElementsStrategy(botAI); }
static Strategy* imp(PlayerbotAI* ai) { return new SummonImpStrategy(ai); }
static Strategy* voidwalker(PlayerbotAI* ai) { return new SummonVoidwalkerStrategy(ai); }
static Strategy* succubus(PlayerbotAI* ai) { return new SummonSuccubusStrategy(ai); }
static Strategy* felhunter(PlayerbotAI* ai) { return new SummonFelhunterStrategy(ai); }
static Strategy* felguard(PlayerbotAI* ai) { return new SummonFelguardStrategy(ai); }
static Strategy* tank(PlayerbotAI* botAI) { return new TankWarlockStrategy(botAI); }
};
class WarlockCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
@@ -63,28 +58,46 @@ class WarlockCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
public:
WarlockCombatStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["tank"] = &WarlockCombatStrategyFactoryInternal::tank;
creators["affli"] = &WarlockCombatStrategyFactoryInternal::affliction;
creators["demo"] = &WarlockCombatStrategyFactoryInternal::demonology;
creators["destro"] = &WarlockCombatStrategyFactoryInternal::destruction;
}
private:
static Strategy* tank(PlayerbotAI* botAI) { return new TankWarlockStrategy(botAI); }
static Strategy* affliction(PlayerbotAI* botAI) { return new AfflictionWarlockStrategy(botAI); }
static Strategy* demonology(PlayerbotAI* botAI) { return new DemonologyWarlockStrategy(botAI); }
static Strategy* destruction(PlayerbotAI* botAI) { return new DestructionWarlockStrategy(botAI); }
};
class NonCombatBuffStrategyFactoryInternal : public NamedObjectContext<Strategy>
class WarlockPetStrategyFactoryInternal : public NamedObjectContext<Strategy>
{
public:
NonCombatBuffStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
WarlockPetStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["ss self"] = &NonCombatBuffStrategyFactoryInternal::soulstone_self;
creators["ss master"] = &NonCombatBuffStrategyFactoryInternal::soulstone_master;
creators["ss tank"] = &NonCombatBuffStrategyFactoryInternal::soulstone_tank;
creators["ss healer"] = &NonCombatBuffStrategyFactoryInternal::soulstone_healer;
creators["imp"] = &WarlockPetStrategyFactoryInternal::imp;
creators["voidwalker"] = &WarlockPetStrategyFactoryInternal::voidwalker;
creators["succubus"] = &WarlockPetStrategyFactoryInternal::succubus;
creators["felhunter"] = &WarlockPetStrategyFactoryInternal::felhunter;
creators["felguard"] = &WarlockPetStrategyFactoryInternal::felguard;
}
private:
static Strategy* imp(PlayerbotAI* ai) { return new SummonImpStrategy(ai); }
static Strategy* voidwalker(PlayerbotAI* ai) { return new SummonVoidwalkerStrategy(ai); }
static Strategy* succubus(PlayerbotAI* ai) { return new SummonSuccubusStrategy(ai); }
static Strategy* felhunter(PlayerbotAI* ai) { return new SummonFelhunterStrategy(ai); }
static Strategy* felguard(PlayerbotAI* ai) { return new SummonFelguardStrategy(ai); }
};
class WarlockSoulstoneStrategyFactoryInternal : public NamedObjectContext<Strategy>
{
public:
WarlockSoulstoneStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["ss self"] = &WarlockSoulstoneStrategyFactoryInternal::soulstone_self;
creators["ss master"] = &WarlockSoulstoneStrategyFactoryInternal::soulstone_master;
creators["ss tank"] = &WarlockSoulstoneStrategyFactoryInternal::soulstone_tank;
creators["ss healer"] = &WarlockSoulstoneStrategyFactoryInternal::soulstone_healer;
}
private:
@@ -94,6 +107,28 @@ private:
static Strategy* soulstone_healer(PlayerbotAI* ai) { return new SoulstoneHealerStrategy(ai); }
};
class WarlockCurseStrategyFactoryInternal : public NamedObjectContext<Strategy>
{
public:
WarlockCurseStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["curse of agony"] = &WarlockCurseStrategyFactoryInternal::curse_of_agony;
creators["curse of elements"] = &WarlockCurseStrategyFactoryInternal::curse_of_elements;
creators["curse of doom"] = &WarlockCurseStrategyFactoryInternal::curse_of_doom;
creators["curse of exhaustion"] = &WarlockCurseStrategyFactoryInternal::curse_of_exhaustion;
creators["curse of tongues"] = &WarlockCurseStrategyFactoryInternal::curse_of_tongues;
creators["curse of weakness"] = &WarlockCurseStrategyFactoryInternal::curse_of_weakness;
}
private:
static Strategy* curse_of_agony(PlayerbotAI* botAI) { return new WarlockCurseOfAgonyStrategy(botAI); }
static Strategy* curse_of_elements(PlayerbotAI* botAI) { return new WarlockCurseOfTheElementsStrategy(botAI); }
static Strategy* curse_of_doom(PlayerbotAI* botAI) { return new WarlockCurseOfDoomStrategy(botAI); }
static Strategy* curse_of_exhaustion(PlayerbotAI* botAI) { return new WarlockCurseOfExhaustionStrategy(botAI); }
static Strategy* curse_of_tongues(PlayerbotAI* botAI) { return new WarlockCurseOfTonguesStrategy(botAI); }
static Strategy* curse_of_weakness(PlayerbotAI* botAI) { return new WarlockCurseOfWeaknessStrategy(botAI); }
};
class WarlockTriggerFactoryInternal : public NamedObjectContext<Trigger>
{
public:
@@ -102,6 +137,7 @@ public:
creators["shadow trance"] = &WarlockTriggerFactoryInternal::shadow_trance;
creators["demon armor"] = &WarlockTriggerFactoryInternal::demon_armor;
creators["soul link"] = &WarlockTriggerFactoryInternal::soul_link;
creators["no soul shard"] = &WarlockTriggerFactoryInternal::no_soul_shard;
creators["no healthstone"] = &WarlockTriggerFactoryInternal::HasHealthstone;
creators["no firestone"] = &WarlockTriggerFactoryInternal::HasFirestone;
creators["no spellstone"] = &WarlockTriggerFactoryInternal::HasSpellstone;
@@ -117,14 +153,11 @@ public:
creators["backlash"] = &WarlockTriggerFactoryInternal::backlash;
creators["corruption"] = &WarlockTriggerFactoryInternal::corruption;
creators["corruption on attacker"] = &WarlockTriggerFactoryInternal::corruption_on_attacker;
creators["curse of agony"] = &WarlockTriggerFactoryInternal::curse_of_agony;
creators["curse of agony on attacker"] = &WarlockTriggerFactoryInternal::curse_of_agony_on_attacker;
creators["immolate"] = &WarlockTriggerFactoryInternal::immolate;
creators["immolate on attacker"] = &WarlockTriggerFactoryInternal::immolate_on_attacker;
creators["unstable affliction"] = &WarlockTriggerFactoryInternal::unstable_affliction;
creators["unstable affliction on attacker"] = &WarlockTriggerFactoryInternal::unstable_affliction_on_attacker;
creators["haunt"] = &WarlockTriggerFactoryInternal::haunt;
creators["curse of the elements"] = &WarlockTriggerFactoryInternal::curse_of_the_elements;
creators["decimation"] = &WarlockTriggerFactoryInternal::decimation;
creators["life tap"] = &WarlockTriggerFactoryInternal::life_tap;
creators["life tap glyph buff"] = &WarlockTriggerFactoryInternal::life_tap_glyph_buff;
@@ -134,12 +167,20 @@ public:
creators["immolation aura active"] = &WarlockTriggerFactoryInternal::immolation_aura_active;
creators["metamorphosis not active"] = &WarlockTriggerFactoryInternal::metamorphosis_not_active;
creators["meta melee flee check"] = &WarlockTriggerFactoryInternal::meta_melee_flee_check;
creators["curse of agony"] = &WarlockTriggerFactoryInternal::curse_of_agony;
creators["curse of agony on attacker"] = &WarlockTriggerFactoryInternal::curse_of_agony_on_attacker;
creators["curse of the elements"] = &WarlockTriggerFactoryInternal::curse_of_the_elements;
creators["curse of doom"] = &WarlockTriggerFactoryInternal::curse_of_doom;
creators["curse of exhaustion"] = &WarlockTriggerFactoryInternal::curse_of_exhaustion;
creators["curse of tongues"] = &WarlockTriggerFactoryInternal::curse_of_tongues;
creators["curse of weakness"] = &WarlockTriggerFactoryInternal::curse_of_weakness;
}
private:
static Trigger* shadow_trance(PlayerbotAI* botAI) { return new ShadowTranceTrigger(botAI); }
static Trigger* demon_armor(PlayerbotAI* botAI) { return new DemonArmorTrigger(botAI); }
static Trigger* soul_link(PlayerbotAI* botAI) { return new SoulLinkTrigger(botAI); }
static Trigger* no_soul_shard(PlayerbotAI* botAI) { return new OutOfSoulShardsTrigger(botAI); }
static Trigger* HasHealthstone(PlayerbotAI* botAI) { return new HasHealthstoneTrigger(botAI); }
static Trigger* HasFirestone(PlayerbotAI* botAI) { return new HasFirestoneTrigger(botAI); }
static Trigger* HasSpellstone(PlayerbotAI* botAI) { return new HasSpellstoneTrigger(botAI); }
@@ -149,8 +190,6 @@ private:
static Trigger* soulstone(PlayerbotAI* botAI) { return new SoulstoneTrigger(botAI); }
static Trigger* corruption(PlayerbotAI* botAI) { return new CorruptionTrigger(botAI); }
static Trigger* corruption_on_attacker(PlayerbotAI* botAI) { return new CorruptionOnAttackerTrigger(botAI); }
static Trigger* curse_of_agony(PlayerbotAI* botAI) { return new CurseOfAgonyTrigger(botAI); }
static Trigger* curse_of_agony_on_attacker(PlayerbotAI* botAI) { return new CurseOfAgonyOnAttackerTrigger(botAI); }
static Trigger* banish(PlayerbotAI* botAI) { return new BanishTrigger(botAI); }
static Trigger* fear(PlayerbotAI* botAI) { return new FearTrigger(botAI); }
static Trigger* spell_lock(PlayerbotAI* botAI) { return new SpellLockInterruptSpellTrigger(botAI); }
@@ -162,7 +201,6 @@ private:
static Trigger* unstable_affliction(PlayerbotAI* ai) { return new UnstableAfflictionTrigger(ai); }
static Trigger* unstable_affliction_on_attacker(PlayerbotAI* ai) { return new UnstableAfflictionOnAttackerTrigger(ai); }
static Trigger* haunt(PlayerbotAI* ai) { return new HauntTrigger(ai); }
static Trigger* curse_of_the_elements(PlayerbotAI* ai) { return new CurseOfTheElementsTrigger(ai); }
static Trigger* decimation(PlayerbotAI* ai) { return new DecimationTrigger(ai); }
static Trigger* life_tap(PlayerbotAI* ai) { return new LifeTapTrigger(ai); }
static Trigger* life_tap_glyph_buff(PlayerbotAI* ai) { return new LifeTapGlyphBuffTrigger(ai); }
@@ -172,6 +210,13 @@ private:
static Trigger* immolation_aura_active(PlayerbotAI* ai) { return new ImmolationAuraActiveTrigger(ai); }
static Trigger* metamorphosis_not_active(PlayerbotAI* ai) { return new MetamorphosisNotActiveTrigger(ai); }
static Trigger* meta_melee_flee_check(PlayerbotAI* ai) { return new MetaMeleeEnemyTooCloseForSpellTrigger(ai); }
static Trigger* curse_of_agony(PlayerbotAI* botAI) { return new CurseOfAgonyTrigger(botAI); }
static Trigger* curse_of_agony_on_attacker(PlayerbotAI* botAI) { return new CurseOfAgonyOnAttackerTrigger(botAI); }
static Trigger* curse_of_the_elements(PlayerbotAI* ai) { return new CurseOfTheElementsTrigger(ai); }
static Trigger* curse_of_doom(PlayerbotAI* ai) { return new CurseOfDoomTrigger(ai); }
static Trigger* curse_of_exhaustion(PlayerbotAI* ai) { return new CurseOfExhaustionTrigger(ai); }
static Trigger* curse_of_tongues(PlayerbotAI* ai) { return new CurseOfTonguesTrigger(ai); }
static Trigger* curse_of_weakness(PlayerbotAI* ai) { return new CurseOfWeaknessTrigger(ai); }
};
class WarlockAiObjectContextInternal : public NamedObjectContext<Action>
@@ -183,6 +228,7 @@ public:
creators["demon armor"] = &WarlockAiObjectContextInternal::demon_armor;
creators["demon skin"] = &WarlockAiObjectContextInternal::demon_skin;
creators["soul link"] = &WarlockAiObjectContextInternal::soul_link;
creators["create soul shard"] = &WarlockAiObjectContextInternal::create_soul_shard;
creators["create healthstone"] = &WarlockAiObjectContextInternal::create_healthstone;
creators["create firestone"] = &WarlockAiObjectContextInternal::create_firestone;
creators["create spellstone"] = &WarlockAiObjectContextInternal::create_spellstone;
@@ -203,8 +249,6 @@ public:
creators["immolate on attacker"] = &WarlockAiObjectContextInternal::immolate_on_attacker;
creators["corruption"] = &WarlockAiObjectContextInternal::corruption;
creators["corruption on attacker"] = &WarlockAiObjectContextInternal::corruption_on_attacker;
creators["curse of agony"] = &WarlockAiObjectContextInternal::curse_of_agony;
creators["curse of agony on attacker"] = &WarlockAiObjectContextInternal::curse_of_agony_on_attacker;
creators["shadow bolt"] = &WarlockAiObjectContextInternal::shadow_bolt;
creators["drain soul"] = &WarlockAiObjectContextInternal::drain_soul;
creators["drain mana"] = &WarlockAiObjectContextInternal::drain_mana;
@@ -225,7 +269,6 @@ public:
creators["unstable affliction"] = &WarlockAiObjectContextInternal::unstable_affliction;
creators["unstable affliction on attacker"] = &WarlockAiObjectContextInternal::unstable_affliction_on_attacker;
creators["haunt"] = &WarlockAiObjectContextInternal::haunt;
creators["curse of the elements"] = &WarlockAiObjectContextInternal::curse_of_the_elements;
creators["demonic empowerment"] = &WarlockAiObjectContextInternal::demonic_empowerment;
creators["metamorphosis"] = &WarlockAiObjectContextInternal::metamorphosis;
creators["soul fire"] = &WarlockAiObjectContextInternal::soul_fire;
@@ -239,6 +282,13 @@ public:
creators["soulshatter"] = &WarlockAiObjectContextInternal::soulshatter;
creators["searing pain"] = WarlockAiObjectContextInternal::searing_pain;
creators["shadow ward"] = &WarlockAiObjectContextInternal::shadow_ward;
creators["curse of agony"] = &WarlockAiObjectContextInternal::curse_of_agony;
creators["curse of agony on attacker"] = &WarlockAiObjectContextInternal::curse_of_agony_on_attacker;
creators["curse of the elements"] = &WarlockAiObjectContextInternal::curse_of_the_elements;
creators["curse of doom"] = &WarlockAiObjectContextInternal::curse_of_doom;
creators["curse of exhaustion"] = &WarlockAiObjectContextInternal::curse_of_exhaustion;
creators["curse of tongues"] = &WarlockAiObjectContextInternal::curse_of_tongues;
creators["curse of weakness"] = &WarlockAiObjectContextInternal::curse_of_weakness;
}
private:
@@ -250,6 +300,7 @@ private:
static Action* demon_armor(PlayerbotAI* botAI) { return new CastDemonArmorAction(botAI); }
static Action* demon_skin(PlayerbotAI* botAI) { return new CastDemonSkinAction(botAI); }
static Action* soul_link(PlayerbotAI* botAI) { return new CastSoulLinkAction(botAI); }
static Action* create_soul_shard(PlayerbotAI* botAI) { return new CreateSoulShardAction(botAI); }
static Action* create_healthstone(PlayerbotAI* botAI) { return new CastCreateHealthstoneAction(botAI); }
static Action* create_firestone(PlayerbotAI* botAI) { return new CastCreateFirestoneAction(botAI); }
static Action* create_spellstone(PlayerbotAI* botAI) { return new CastCreateSpellstoneAction(botAI); }
@@ -268,8 +319,6 @@ private:
static Action* fel_domination(PlayerbotAI* botAI) { return new CastFelDominationAction(botAI); }
static Action* corruption(PlayerbotAI* botAI) { return new CastCorruptionAction(botAI); }
static Action* corruption_on_attacker(PlayerbotAI* botAI) { return new CastCorruptionOnAttackerAction(botAI); }
static Action* curse_of_agony(PlayerbotAI* botAI) { return new CastCurseOfAgonyAction(botAI); }
static Action* curse_of_agony_on_attacker(PlayerbotAI* botAI) { return new CastCurseOfAgonyOnAttackerAction(botAI); }
static Action* shadow_bolt(PlayerbotAI* botAI) { return new CastShadowBoltAction(botAI); }
static Action* drain_soul(PlayerbotAI* botAI) { return new CastDrainSoulAction(botAI); }
static Action* drain_mana(PlayerbotAI* botAI) { return new CastDrainManaAction(botAI); }
@@ -288,7 +337,6 @@ private:
static Action* unstable_affliction(PlayerbotAI* ai) { return new CastUnstableAfflictionAction(ai); }
static Action* unstable_affliction_on_attacker(PlayerbotAI* ai) { return new CastUnstableAfflictionOnAttackerAction(ai); }
static Action* haunt(PlayerbotAI* ai) { return new CastHauntAction(ai); }
static Action* curse_of_the_elements(PlayerbotAI* ai) { return new CastCurseOfTheElementsAction(ai); }
static Action* demonic_empowerment(PlayerbotAI* ai) { return new CastDemonicEmpowermentAction(ai); }
static Action* metamorphosis(PlayerbotAI* ai) { return new CastMetamorphosisAction(ai); }
static Action* soul_fire(PlayerbotAI* ai) { return new CastSoulFireAction(ai); }
@@ -301,13 +349,22 @@ private:
static Action* soulshatter(PlayerbotAI* botAI) { return new CastSoulshatterAction(botAI);}
static Action* searing_pain(PlayerbotAI* botAI) { return new CastSearingPainAction(botAI); }
static Action* shadow_ward(PlayerbotAI* botAI) { return new CastShadowWardAction(botAI); }
static Action* curse_of_agony(PlayerbotAI* botAI) { return new CastCurseOfAgonyAction(botAI); }
static Action* curse_of_agony_on_attacker(PlayerbotAI* botAI) { return new CastCurseOfAgonyOnAttackerAction(botAI); }
static Action* curse_of_the_elements(PlayerbotAI* ai) { return new CastCurseOfTheElementsAction(ai); }
static Action* curse_of_doom(PlayerbotAI* ai) { return new CastCurseOfDoomAction(ai); }
static Action* curse_of_exhaustion(PlayerbotAI* ai) { return new CastCurseOfExhaustionAction(ai); }
static Action* curse_of_tongues(PlayerbotAI* ai) { return new CastCurseOfTonguesAction(ai); }
static Action* curse_of_weakness(PlayerbotAI* ai) { return new CastCurseOfWeaknessAction(ai); }
};
WarlockAiObjectContext::WarlockAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI)
{
strategyContexts.Add(new WarlockStrategyFactoryInternal());
strategyContexts.Add(new WarlockCombatStrategyFactoryInternal());
strategyContexts.Add(new NonCombatBuffStrategyFactoryInternal());
strategyContexts.Add(new WarlockPetStrategyFactoryInternal());
strategyContexts.Add(new WarlockSoulstoneStrategyFactoryInternal());
strategyContexts.Add(new WarlockCurseStrategyFactoryInternal());
actionContexts.Add(new WarlockAiObjectContextInternal());
triggerContexts.Add(new WarlockTriggerFactoryInternal());
}

View File

@@ -107,3 +107,29 @@ bool CurseOfTheElementsTrigger::IsActive()
// Use default BuffTrigger logic for the rest (only trigger if debuff is missing or expiring)
return BuffTrigger::IsActive();
}
// Checks if the target has a conflicting debuff that is equal to Curse of Weakness
bool CurseOfWeaknessTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target || !target->IsAlive() || !target->IsInWorld())
return false;
// List of all spell IDs for Curse of Weakness, Demoralizing Roar, Demoralizing Shout, and Vindication
static const uint32 CurseOfWeaknessExclusiveDebuffs[] = {// Curse of Weakness
702, 1108, 6205, 7646, 11707, 11708, 27224, 30909, 50511,
// Demoralizing Roar
99, 1735, 9490, 9747, 9898, 26998, 48559, 48560,
// Demoralizing Shout
1160, 6190, 11554, 11555, 11556, 25202, 25203, 47437,
// Vindication
67, 26017};
// Check if target has any of the exclusive debuffs
for (uint32 spellId : CurseOfWeaknessExclusiveDebuffs)
{
if (target->HasAura(spellId))
return false;
}
// Use default BuffTrigger logic for the rest (only trigger if debuff is missing or expiring)
return BuffTrigger::IsActive();
}

View File

@@ -30,6 +30,13 @@ public:
bool IsActive() override;
};
class OutOfSoulShardsTrigger : public Trigger
{
public:
OutOfSoulShardsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no soul shard") {}
bool IsActive() override { return AI_VALUE2(uint32, "item count", "soul shard") == 0; }
};
class FirestoneTrigger : public BuffTrigger
{
public:
@@ -122,33 +129,7 @@ public:
DevourMagicCleanseTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "devour magic", DISPEL_MAGIC) {}
};
// DoT/Debuff Triggers
class CurseOfAgonyTrigger : public DebuffTrigger
{
public:
CurseOfAgonyTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of agony", 1, true, 0.5f) {}
bool IsActive() override
{
if (botAI->HasStrategy(
"curse of elements", BOT_STATE_COMBAT)) // If Curse of the Elements strategy is active, do not cast Curse of Agony
return false;
return BuffTrigger::IsActive();
}
};
class CurseOfAgonyOnAttackerTrigger : public DebuffOnAttackerTrigger
{
public:
CurseOfAgonyOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "curse of agony", true) {}
bool IsActive() override
{
if (botAI->HasStrategy(
"curse of elements", BOT_STATE_COMBAT)) // If Curse of the Elements strategy is active, do not cast Curse of Agony
return false;
return BuffTrigger::IsActive();
}
};
// DoT/Curse Triggers
class CorruptionTrigger : public DebuffTrigger
{
@@ -204,11 +185,52 @@ public:
HauntTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "haunt", 1, true, 0) {}
};
class CurseOfAgonyTrigger : public DebuffTrigger
{
public:
CurseOfAgonyTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of agony", 1, true, 0.5f) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
class CurseOfAgonyOnAttackerTrigger : public DebuffOnAttackerTrigger
{
public:
CurseOfAgonyOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "curse of agony", true) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
class CurseOfTheElementsTrigger : public DebuffTrigger
{
public:
CurseOfTheElementsTrigger(PlayerbotAI* botAI)
: DebuffTrigger(botAI, "curse of the elements", 1, true, 0.5f) {}
CurseOfTheElementsTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of the elements", 1, true, 0.5f) {}
bool IsActive() override;
};
class CurseOfDoomTrigger : public DebuffTrigger
{
public:
CurseOfDoomTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of doom", 1, true, 0.5f) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
class CurseOfExhaustionTrigger : public DebuffTrigger
{
public:
CurseOfExhaustionTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of exhaustion", 1, true, 0.5f) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
class CurseOfTonguesTrigger : public DebuffTrigger
{
public:
CurseOfTonguesTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of tongues", 1, true, 0.5f) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
class CurseOfWeaknessTrigger : public DebuffTrigger
{
public:
CurseOfWeaknessTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of weakness", 1, true, 0.5f) {}
bool IsActive() override;
};