From 59af34809c46f1b69a6fa360c7bb7da41bd84394 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 4 Jul 2025 03:32:29 -0700 Subject: [PATCH 1/3] Warlock Ranged Designation/DPS Strategy Cleanup Hello community, This PR focuses on 4 things: Recognizing the Warlock as a "ranged" bot, so they will follow ranged commands and strategies, in GenericWarlockStrategy.h: uint32 GetType() const override { return CombatStrategy::GetType() | STRATEGY_TYPE_RANGED | STRATEGY_TYPE_DPS; } Cleanup/deletion of the DpsWarlockStrategy.cpp and .h (no longer used or referenced anywhere) Fixes soulstone logic so multiple Warlocks don't soulstone the same target, and don't try to soulstone a target that is too far away or out of line of sight (WarlockActions.cpp) Moved summoning of pets to the main non-combat strategy inittriggers: // Pet-summoning triggers based on spec if (tab == 0) // Affliction { triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), nullptr))); } else if (tab == 1) // Demonology { triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), nullptr))); } else if (tab == 2) // Destruction { triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), nullptr))); } --- src/AiFactory.cpp | 12 -- src/strategy/warlock/DpsWarlockStrategy.cpp | 88 -------------- src/strategy/warlock/DpsWarlockStrategy.h | 33 ------ .../GenericWarlockNonCombatStrategy.cpp | 82 +++---------- .../warlock/GenericWarlockNonCombatStrategy.h | 50 -------- src/strategy/warlock/GenericWarlockStrategy.h | 1 + src/strategy/warlock/WarlockActions.cpp | 112 ++++++++++++++++-- .../warlock/WarlockAiObjectContext.cpp | 27 +---- 8 files changed, 128 insertions(+), 277 deletions(-) delete mode 100644 src/strategy/warlock/DpsWarlockStrategy.cpp delete mode 100644 src/strategy/warlock/DpsWarlockStrategy.h diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index adf53668..9589a8f2 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -594,18 +594,6 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const nonCombatEngine->addStrategy("dps assist", false); break; case CLASS_WARLOCK: - if (tab == WARLOCK_TAB_AFFLICATION) - { - nonCombatEngine->addStrategiesNoInit("felhunter", nullptr); - } - else if (tab == WARLOCK_TAB_DEMONOLOGY) - { - nonCombatEngine->addStrategiesNoInit("felguard", nullptr); - } - else if (tab == WARLOCK_TAB_DESTRUCTION) - { - nonCombatEngine->addStrategiesNoInit("imp", nullptr); - } nonCombatEngine->addStrategiesNoInit("dps assist", nullptr); break; case CLASS_DEATH_KNIGHT: diff --git a/src/strategy/warlock/DpsWarlockStrategy.cpp b/src/strategy/warlock/DpsWarlockStrategy.cpp deleted file mode 100644 index 49b6a2be..00000000 --- a/src/strategy/warlock/DpsWarlockStrategy.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , 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 "DpsWarlockStrategy.h" -#include "Playerbots.h" - -// This strategy is designed for low-level Warlocks without talents. -// All of the important spells/cooldowns have been migrated to -// their respective specs. - -// ===== Action Node Factory ===== -class DpsWarlockStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - DpsWarlockStrategyActionNodeFactory() - { - creators["corruption"] = &corruption; - creators["curse of agony"] = &curse_of_agony; - creators["immolate"] = &immolate; - creators["shadow bolt"] = &shadow_bolt; - creators["life tap"] = &life_tap; - creators["shadowflame"] = &shadowflame; - creators["seed of corruption"] = &seed_of_corruption; - creators["rain of fire"] = &rain_of_fire; - creators["drain soul"] = &drain_soul; - } - -private: - static ActionNode* corruption(PlayerbotAI*) { return new ActionNode("corruption", nullptr, nullptr, nullptr); } - static ActionNode* curse_of_agony(PlayerbotAI*){return new ActionNode("curse of agony", nullptr, nullptr, nullptr);} - static ActionNode* immolate(PlayerbotAI*) { return new ActionNode("immolate", nullptr, nullptr, nullptr); } - static ActionNode* shadow_bolt(PlayerbotAI*) { return new ActionNode("shadow bolt", nullptr, nullptr, nullptr); } - static ActionNode* life_tap(PlayerbotAI*) { return new ActionNode("life tap", nullptr, nullptr, nullptr); } - static ActionNode* shadowflame(PlayerbotAI*) { return new ActionNode("shadowflame", nullptr, nullptr, nullptr); } - static ActionNode* seed_of_corruption(PlayerbotAI*){return new ActionNode("seed of corruption", nullptr, nullptr, nullptr);} - static ActionNode* rain_of_fire(PlayerbotAI*) { return new ActionNode("rain of fire", nullptr, nullptr, nullptr); } - static ActionNode* drain_soul(PlayerbotAI*) { return new ActionNode("drain soul", nullptr, nullptr, nullptr); } -}; - -// ===== Single Target Strategy ===== -DpsWarlockStrategy::DpsWarlockStrategy(PlayerbotAI* botAI) : GenericWarlockStrategy(botAI) -{ - actionNodeFactories.Add(new DpsWarlockStrategyActionNodeFactory()); -} - -// ===== Default Actions ===== -NextAction** DpsWarlockStrategy::getDefaultActions() -{ - return NextAction::array(0, - new NextAction("immolate", 5.5f), - new NextAction("corruption", 5.4f), - new NextAction("curse of agony", 5.3f), - new NextAction("shadow bolt", 5.2f), - new NextAction("shoot", 5.0f), nullptr); -} - -// ===== Trigger Initialization === -void DpsWarlockStrategy::InitTriggers(std::vector& triggers) -{ - GenericWarlockStrategy::InitTriggers(triggers); - - // 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("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("immolate", NextAction::array(0, new NextAction("immolate", 17.5f), nullptr))); - - // Drain Soul as execute if target is low HP - triggers.push_back(new TriggerNode("target critical health", NextAction::array(0, new NextAction("drain soul", 17.0f), nullptr))); - - // Cast during movement or to activate glyph buff - triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", ACTION_DEFAULT + 0.1f), nullptr))); - triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 28.0f), NULL))); -} - -// ===== AoE Strategy, 3+ enemies ===== -void DpsAoeWarlockStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0, - new NextAction("shadowflame", 22.5f), - new NextAction("seed of corruption on attacker", 22.0f), - new NextAction("seed of corruption", 21.5f), - new NextAction("rain of fire", 21.0f), nullptr))); -} diff --git a/src/strategy/warlock/DpsWarlockStrategy.h b/src/strategy/warlock/DpsWarlockStrategy.h deleted file mode 100644 index 4ae63930..00000000 --- a/src/strategy/warlock/DpsWarlockStrategy.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , 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_DPSWARLOCKSTRATEGY_H -#define _PLAYERBOT_DPSWARLOCKSTRATEGY_H - -#include "GenericWarlockStrategy.h" -#include "Strategy.h" - -class PlayerbotAI; - -class DpsWarlockStrategy : public GenericWarlockStrategy -{ -public: - DpsWarlockStrategy(PlayerbotAI* botAI); - - std::string const getName() override { return "dps"; } - void InitTriggers(std::vector& triggers) override; - NextAction** getDefaultActions() override; - uint32 GetType() const override { return GenericWarlockStrategy::GetType() | STRATEGY_TYPE_DPS; } -}; - -class DpsAoeWarlockStrategy : public CombatStrategy -{ -public: - DpsAoeWarlockStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "aoe"; } -}; -#endif diff --git a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp index f575750b..d5baa276 100644 --- a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp +++ b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp @@ -89,77 +89,31 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector& tr 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))); + 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))); + 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 -// Enabled by default for the Destruction spec -// To enable, type "nc +imp" -// To disable, type "nc -imp" -SummonImpStrategy::SummonImpStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} - -void SummonImpStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), NULL))); -} - -// Non-combat strategy for summoning a Voidwalker -// Disabled by default -// To enable, type "nc +voidwalker" -// To disable, type "nc -voidwalker" -SummonVoidwalkerStrategy::SummonVoidwalkerStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} - -void SummonVoidwalkerStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode("no pet", NextAction::array(0, new NextAction("summon voidwalker", 29.0f), NULL))); -} - -// Non-combat strategy for summoning a Succubus -// Disabled by default -// To enable, type "nc +succubus" -// To disable, type "nc -succubus" -SummonSuccubusStrategy::SummonSuccubusStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} - -void SummonSuccubusStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon succubus", 29.0f), NULL))); -} - -// Non-combat strategy for summoning a Felhunter -// Enabled by default for the Affliction spec -// To enable, type "nc +felhunter" -// To disable, type "nc -felhunter" -SummonFelhunterStrategy::SummonFelhunterStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} - -void SummonFelhunterStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), NULL))); -} - -// Non-combat strategy for summoning a Felguard -// Enabled by default for the Demonology spec -// To enable, type "nc +felguard" -// To disable, type "nc -felguard" -SummonFelguardStrategy::SummonFelguardStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} - -void SummonFelguardStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), NULL))); + // Pet-summoning triggers based on spec + if (tab == 0) // Affliction + { + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), nullptr))); + } + else if (tab == 1) // Demonology + { + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), nullptr))); + } + else if (tab == 2) // Destruction + { + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), nullptr))); + } } // Non-combat strategy for selecting themselves to receive soulstone diff --git a/src/strategy/warlock/GenericWarlockNonCombatStrategy.h b/src/strategy/warlock/GenericWarlockNonCombatStrategy.h index ef226c50..19277dfd 100644 --- a/src/strategy/warlock/GenericWarlockNonCombatStrategy.h +++ b/src/strategy/warlock/GenericWarlockNonCombatStrategy.h @@ -19,56 +19,6 @@ public: void InitTriggers(std::vector& triggers) override; }; -class SummonImpStrategy : public NonCombatStrategy -{ -public: - SummonImpStrategy(PlayerbotAI* ai); - virtual std::string const getName() override { return "imp"; } - -public: - void InitTriggers(std::vector& triggers) override; -}; - -class SummonVoidwalkerStrategy : public NonCombatStrategy -{ -public: - SummonVoidwalkerStrategy(PlayerbotAI* ai); - virtual std::string const getName() override { return "voidwalker"; } - -public: - void InitTriggers(std::vector& triggers) override; -}; - -class SummonSuccubusStrategy : public NonCombatStrategy -{ -public: - SummonSuccubusStrategy(PlayerbotAI* ai); - virtual std::string const getName() override { return "succubus"; } - -public: - void InitTriggers(std::vector& triggers) override; -}; - -class SummonFelhunterStrategy : public NonCombatStrategy -{ -public: - SummonFelhunterStrategy(PlayerbotAI* ai); - virtual std::string const getName() override { return "felhunter"; } - -public: - void InitTriggers(std::vector& triggers) override; -}; - -class SummonFelguardStrategy : public NonCombatStrategy -{ -public: - SummonFelguardStrategy(PlayerbotAI* ai); - virtual std::string const getName() override { return "felguard"; } - -public: - void InitTriggers(std::vector& triggers) override; -}; - class SoulstoneSelfStrategy : public NonCombatStrategy { public: diff --git a/src/strategy/warlock/GenericWarlockStrategy.h b/src/strategy/warlock/GenericWarlockStrategy.h index 23fa739b..6b134b60 100644 --- a/src/strategy/warlock/GenericWarlockStrategy.h +++ b/src/strategy/warlock/GenericWarlockStrategy.h @@ -18,6 +18,7 @@ public: std::string const getName() override { return "warlock"; } void InitTriggers(std::vector& triggers) override; NextAction** getDefaultActions() override; + uint32 GetType() const override { return CombatStrategy::GetType() | STRATEGY_TYPE_RANGED | STRATEGY_TYPE_DPS; } }; class WarlockBoostStrategy : public Strategy diff --git a/src/strategy/warlock/WarlockActions.cpp b/src/strategy/warlock/WarlockActions.cpp index 8b362cd6..3bb1724b 100644 --- a/src/strategy/warlock/WarlockActions.cpp +++ b/src/strategy/warlock/WarlockActions.cpp @@ -15,6 +15,9 @@ #include "Playerbots.h" #include "ServerFacade.h" #include "Unit.h" +#include "Timer.h" +#include +#include // Checks if the bot has less than 32 soul shards, and if so, allows casting Drain Soul bool CastDrainSoulAction::isUseful() { return AI_VALUE2(uint32, "item count", "soul shard") < 32; } @@ -134,9 +137,29 @@ bool UseSoulstoneSelfAction::Execute(Event event) return UseItem(items[0], ObjectGuid::Empty, nullptr, bot); } +// Reservation map for soulstone targets (GUID -> reservation expiry in ms) +static std::unordered_map soulstoneReservations; +static std::mutex soulstoneReservationsMutex; + +// Helper to clean up expired reservations +void CleanupSoulstoneReservations() +{ + uint32 now = getMSTime(); + std::lock_guard lock(soulstoneReservationsMutex); + for (auto it = soulstoneReservations.begin(); it != soulstoneReservations.end();) + { + if (it->second <= now) + it = soulstoneReservations.erase(it); + else + ++it; + } +} + // Use the soulstone item on the bot's master with nc strategy "ss master" bool UseSoulstoneMasterAction::Execute(Event event) { + CleanupSoulstoneReservations(); + std::vector items = AI_VALUE2(std::vector, "inventory items", "soulstone"); if (items.empty()) return false; @@ -145,6 +168,23 @@ bool UseSoulstoneMasterAction::Execute(Event event) if (!master || HasSoulstoneAura(master)) return false; + uint32 now = getMSTime(); + + { + std::lock_guard lock(soulstoneReservationsMutex); + if (soulstoneReservations.count(master->GetGUID()) && soulstoneReservations[master->GetGUID()] > now) + return false; // Already being soulstoned + + soulstoneReservations[master->GetGUID()] = now + 2500; // Reserve for 2.5 seconds + } + + float distance = sServerFacade->GetDistance2d(bot, master); + if (distance >= 30.0f) + return false; + + if (!bot->IsWithinLOSInMap(master)) + return false; + bot->SetSelection(master->GetGUID()); return UseItem(items[0], ObjectGuid::Empty, nullptr, master); } @@ -152,41 +192,83 @@ bool UseSoulstoneMasterAction::Execute(Event event) // Use the soulstone item on a tank in the group with nc strategy "ss tank" bool UseSoulstoneTankAction::Execute(Event event) { + CleanupSoulstoneReservations(); + std::vector items = AI_VALUE2(std::vector, "inventory items", "soulstone"); if (items.empty()) return false; - Player* tank = nullptr; + Player* chosenTank = nullptr; Group* group = bot->GetGroup(); + uint32 now = getMSTime(); + + // First: Try to soulstone the main tank if (group) { for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if (member && member->IsAlive() && botAI->IsTank(member) && !HasSoulstoneAura(member)) + if (member && member->IsAlive() && botAI->IsTank(member) && botAI->IsMainTank(member) && + !HasSoulstoneAura(member)) { - tank = member; - break; + std::lock_guard lock(soulstoneReservationsMutex); + if (soulstoneReservations.count(member->GetGUID()) && soulstoneReservations[member->GetGUID()] > now) + continue; // Already being soulstoned + + float distance = sServerFacade->GetDistance2d(bot, member); + if (distance < 30.0f && bot->IsWithinLOSInMap(member)) + { + chosenTank = member; + soulstoneReservations[chosenTank->GetGUID()] = now + 2500; // Reserve for 2.5 seconds + break; + } + } + } + + // If no main tank found, soulstone another tank + if (!chosenTank) + { + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (member && member->IsAlive() && botAI->IsTank(member) && !HasSoulstoneAura(member)) + { + std::lock_guard lock(soulstoneReservationsMutex); + if (soulstoneReservations.count(member->GetGUID()) && + soulstoneReservations[member->GetGUID()] > now) + continue; // Already being soulstoned + + float distance = sServerFacade->GetDistance2d(bot, member); + if (distance < 30.0f && bot->IsWithinLOSInMap(member)) + { + chosenTank = member; + soulstoneReservations[chosenTank->GetGUID()] = now + 2500; // Reserve for 2.5 seconds + break; + } + } } } } - if (!tank) + if (!chosenTank) return false; - bot->SetSelection(tank->GetGUID()); - return UseItem(items[0], ObjectGuid::Empty, nullptr, tank); + bot->SetSelection(chosenTank->GetGUID()); + return UseItem(items[0], ObjectGuid::Empty, nullptr, chosenTank); } // Use the soulstone item on a healer in the group with nc strategy "ss healer" bool UseSoulstoneHealerAction::Execute(Event event) { + CleanupSoulstoneReservations(); + std::vector items = AI_VALUE2(std::vector, "inventory items", "soulstone"); if (items.empty()) return false; Player* healer = nullptr; Group* group = bot->GetGroup(); + uint32 now = getMSTime(); if (group) { for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) @@ -194,8 +276,20 @@ bool UseSoulstoneHealerAction::Execute(Event event) Player* member = gref->GetSource(); if (member && member->IsAlive() && botAI->IsHeal(member) && !HasSoulstoneAura(member)) { - healer = member; - break; + { + std::lock_guard lock(soulstoneReservationsMutex); + if (soulstoneReservations.count(member->GetGUID()) && + soulstoneReservations[member->GetGUID()] > now) + continue; // Already being soulstoned + + float distance = sServerFacade->GetDistance2d(bot, member); + if (distance < 30.0f && bot->IsWithinLOSInMap(member)) + { + healer = member; + soulstoneReservations[healer->GetGUID()] = now + 2500; // Reserve for 2.5 seconds + break; + } + } } } } diff --git a/src/strategy/warlock/WarlockAiObjectContext.cpp b/src/strategy/warlock/WarlockAiObjectContext.cpp index e3dc6ca9..1de0d00f 100644 --- a/src/strategy/warlock/WarlockAiObjectContext.cpp +++ b/src/strategy/warlock/WarlockAiObjectContext.cpp @@ -8,7 +8,6 @@ #include "DemonologyWarlockStrategy.h" #include "DestructionWarlockStrategy.h" #include "TankWarlockStrategy.h" -#include "DpsWarlockStrategy.h" #include "GenericTriggers.h" #include "GenericWarlockNonCombatStrategy.h" #include "NamedObjectContext.h" @@ -29,15 +28,10 @@ public: creators["boost"] = &WarlockStrategyFactoryInternal::boost; creators["cc"] = &WarlockStrategyFactoryInternal::cc; creators["pet"] = &WarlockStrategyFactoryInternal::pet; - creators["affli"] = &WarlockStrategyFactoryInternal::affliction; creators["affli aoe"] = &WarlockStrategyFactoryInternal::affliction_aoe; - creators["demo"] = &WarlockStrategyFactoryInternal::demonology; creators["demo aoe"] = &WarlockStrategyFactoryInternal::demonology_aoe; - creators["destro"] = &WarlockStrategyFactoryInternal::destruction; creators["destro aoe"] = &WarlockStrategyFactoryInternal::destruction_aoe; creators["meta melee"] = &WarlockStrategyFactoryInternal::meta_melee_aoe; - creators["dps"] = &WarlockStrategyFactoryInternal::dps; - creators["aoe"] = &WarlockStrategyFactoryInternal::aoe; creators["curse of elements"] = &WarlockStrategyFactoryInternal::curse_of_elements; } @@ -47,15 +41,10 @@ private: 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* affliction(PlayerbotAI* botAI) { return new AfflictionWarlockStrategy(botAI); } static Strategy* affliction_aoe(PlayerbotAI* botAI) { return new AfflictionWarlockAoeStrategy(botAI); } - static Strategy* demonology(PlayerbotAI* botAI) { return new DemonologyWarlockStrategy(botAI); } static Strategy* demonology_aoe(PlayerbotAI* botAI) { return new DemonologyWarlockAoeStrategy(botAI); } - static Strategy* destruction(PlayerbotAI* botAI) { return new DestructionWarlockStrategy(botAI); } static Strategy* destruction_aoe(PlayerbotAI* botAI) { return new DestructionWarlockAoeStrategy(botAI); } static Strategy* meta_melee_aoe(PlayerbotAI* botAI) { return new MetaMeleeAoeStrategy(botAI); } - static Strategy* dps(PlayerbotAI* botAI) { return new DpsWarlockStrategy(botAI); } - static Strategy* aoe(PlayerbotAI* botAI) { return new DpsAoeWarlockStrategy(botAI); } static Strategy* curse_of_elements(PlayerbotAI* botAI) { return new WarlockCurseOfTheElementsStrategy(botAI); } }; @@ -65,10 +54,16 @@ public: WarlockCombatStrategyFactoryInternal() : NamedObjectContext(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 @@ -76,11 +71,6 @@ class NonCombatBuffStrategyFactoryInternal : public NamedObjectContext public: NonCombatBuffStrategyFactoryInternal() : NamedObjectContext(false, true) { - creators["imp"] = &NonCombatBuffStrategyFactoryInternal::imp; - creators["voidwalker"] = &NonCombatBuffStrategyFactoryInternal::voidwalker; - creators["succubus"] = &NonCombatBuffStrategyFactoryInternal::succubus; - creators["felhunter"] = &NonCombatBuffStrategyFactoryInternal::felhunter; - creators["felguard"] = &NonCombatBuffStrategyFactoryInternal::felguard; creators["ss self"] = &NonCombatBuffStrategyFactoryInternal::soulstone_self; creators["ss master"] = &NonCombatBuffStrategyFactoryInternal::soulstone_master; creators["ss tank"] = &NonCombatBuffStrategyFactoryInternal::soulstone_tank; @@ -88,11 +78,6 @@ public: } 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); } static Strategy* soulstone_self(PlayerbotAI* ai) { return new SoulstoneSelfStrategy(ai); } static Strategy* soulstone_master(PlayerbotAI* ai) { return new SoulstoneMasterStrategy(ai); } static Strategy* soulstone_tank(PlayerbotAI* ai) { return new SoulstoneTankStrategy(ai); } From 1c6949029036c6cbb4094dbb40b599794cdb1889 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 4 Jul 2025 11:33:46 -0700 Subject: [PATCH 2/3] Added range checks for running away if too close Added range checks for running away if too close - The warlocks were happily standing in cleave range and dieing. Now, they will backpedal if too close to an enemy. I also added custom logic to check if in demonology form before backpedaling. I also removed shadow cleave, as the dps increase with negligible (0.1%) and it was causing errors in the actions, resulting in the warlock standing idle in combat. --- src/strategy/warlock/AfflictionWarlockStrategy.cpp | 2 ++ src/strategy/warlock/DemonologyWarlockStrategy.cpp | 7 +++---- .../warlock/DestructionWarlockStrategy.cpp | 2 ++ src/strategy/warlock/WarlockAiObjectContext.cpp | 4 ++++ src/strategy/warlock/WarlockTriggers.h | 14 ++++++++++++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/strategy/warlock/AfflictionWarlockStrategy.cpp b/src/strategy/warlock/AfflictionWarlockStrategy.cpp index eaeef3be..968bb445 100644 --- a/src/strategy/warlock/AfflictionWarlockStrategy.cpp +++ b/src/strategy/warlock/AfflictionWarlockStrategy.cpp @@ -84,6 +84,8 @@ void AfflictionWarlockStrategy::InitTriggers(std::vector& triggers // 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", 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))); } // ===== AoE Strategy, 3+ enemies ===== diff --git a/src/strategy/warlock/DemonologyWarlockStrategy.cpp b/src/strategy/warlock/DemonologyWarlockStrategy.cpp index 2cfdfac7..1062d015 100644 --- a/src/strategy/warlock/DemonologyWarlockStrategy.cpp +++ b/src/strategy/warlock/DemonologyWarlockStrategy.cpp @@ -30,7 +30,6 @@ public: creators["seed of corruption"] = &seed_of_corruption; creators["rain of fire"] = &rain_of_fire; creators["demon charge"] = &demon_charge; - creators["shadow cleave"] = &shadow_cleave; } private: @@ -52,7 +51,6 @@ private: static ActionNode* seed_of_corruption(PlayerbotAI*) { return new ActionNode("seed of corruption", nullptr, nullptr, nullptr); } static ActionNode* rain_of_fire(PlayerbotAI*) { return new ActionNode("rain of fire", nullptr, nullptr, nullptr); } static ActionNode* demon_charge(PlayerbotAI*) { return new ActionNode("demon charge", nullptr, nullptr, nullptr); } - static ActionNode* shadow_cleave(PlayerbotAI*) { return new ActionNode("shadow cleave", nullptr, nullptr, nullptr); } }; // ===== Single Target Strategy ===== @@ -97,6 +95,8 @@ void DemonologyWarlockStrategy::InitTriggers(std::vector& triggers // 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", 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))); } // ===== AoE Strategy, 3+ enemies ===== @@ -122,6 +122,5 @@ void MetaMeleeAoeStrategy::InitTriggers(std::vector& triggers) { triggers.push_back(new TriggerNode("immolation aura active", NextAction::array(0, new NextAction("reach melee", 25.5f), - new NextAction("demon charge", 25.0f), - new NextAction("shadow cleave", 24.5f), nullptr))); + new NextAction("demon charge", 25.0f), nullptr))); } diff --git a/src/strategy/warlock/DestructionWarlockStrategy.cpp b/src/strategy/warlock/DestructionWarlockStrategy.cpp index 42498bde..2ecc6467 100644 --- a/src/strategy/warlock/DestructionWarlockStrategy.cpp +++ b/src/strategy/warlock/DestructionWarlockStrategy.cpp @@ -91,6 +91,8 @@ void DestructionWarlockStrategy::InitTriggers(std::vector& trigger // 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", 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))); } // ===== AoE Strategy, 3+ enemies ===== diff --git a/src/strategy/warlock/WarlockAiObjectContext.cpp b/src/strategy/warlock/WarlockAiObjectContext.cpp index 1de0d00f..e8463fe8 100644 --- a/src/strategy/warlock/WarlockAiObjectContext.cpp +++ b/src/strategy/warlock/WarlockAiObjectContext.cpp @@ -122,6 +122,8 @@ public: creators["metamorphosis"] = &WarlockTriggerFactoryInternal::metamorphosis; creators["demonic empowerment"] = &WarlockTriggerFactoryInternal::demonic_empowerment; 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; } private: @@ -158,6 +160,8 @@ private: static Trigger* metamorphosis(PlayerbotAI* ai) { return new MetamorphosisTrigger(ai); } static Trigger* demonic_empowerment(PlayerbotAI* ai) { return new DemonicEmpowermentTrigger(ai); } 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); } }; class WarlockAiObjectContextInternal : public NamedObjectContext diff --git a/src/strategy/warlock/WarlockTriggers.h b/src/strategy/warlock/WarlockTriggers.h index da12c697..40f72e6a 100644 --- a/src/strategy/warlock/WarlockTriggers.h +++ b/src/strategy/warlock/WarlockTriggers.h @@ -271,4 +271,18 @@ class MoltenCoreTrigger : public HasAuraTrigger public: MoltenCoreTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "molten core") {} }; + +class MetamorphosisNotActiveTrigger : public HasNoAuraTrigger +{ +public: + MetamorphosisNotActiveTrigger(PlayerbotAI* ai) : HasNoAuraTrigger(ai, "metamorphosis") {} +}; + +class MetaMeleeEnemyTooCloseForSpellTrigger : public TwoTriggers +{ +public: + MetaMeleeEnemyTooCloseForSpellTrigger(PlayerbotAI* ai) + : TwoTriggers(ai, "enemy too close for spell", "metamorphosis not active") {} +}; + #endif From 1a20d549fe92182f76b7732561a90a38a67b8fe3 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 4 Jul 2025 22:44:17 -0700 Subject: [PATCH 3/3] Eureka! I re-implemented the pet strategies into the "general" strategy area of the WarlockAiObjectContext, and it worked!!! Finally! The issue before was they were under the "Buff" area of the aiobjectcontext, which can only have 1 active at any given time - this is why the soulstone strategy and the pet strategy were cancelling out each other. Now, pets are summoned via a non-combat strategy that is assigned with aifactory by spec! --- src/AiFactory.cpp | 12 ++++ .../GenericWarlockNonCombatStrategy.cpp | 68 +++++++++++++++---- .../warlock/GenericWarlockNonCombatStrategy.h | 50 ++++++++++++++ .../warlock/WarlockAiObjectContext.cpp | 10 +++ 4 files changed, 127 insertions(+), 13 deletions(-) diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index 9589a8f2..adf53668 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -594,6 +594,18 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const nonCombatEngine->addStrategy("dps assist", false); break; case CLASS_WARLOCK: + if (tab == WARLOCK_TAB_AFFLICATION) + { + nonCombatEngine->addStrategiesNoInit("felhunter", nullptr); + } + else if (tab == WARLOCK_TAB_DEMONOLOGY) + { + nonCombatEngine->addStrategiesNoInit("felguard", nullptr); + } + else if (tab == WARLOCK_TAB_DESTRUCTION) + { + nonCombatEngine->addStrategiesNoInit("imp", nullptr); + } nonCombatEngine->addStrategiesNoInit("dps assist", nullptr); break; case CLASS_DEATH_KNIGHT: diff --git a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp index d5baa276..f99c06c5 100644 --- a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp +++ b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp @@ -101,19 +101,61 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector& tr triggers.push_back(new TriggerNode("spellstone", NextAction::array(0, new NextAction("spellstone", 24.0f), nullptr))); } - // Pet-summoning triggers based on spec - if (tab == 0) // Affliction - { - triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), nullptr))); - } - else if (tab == 1) // Demonology - { - triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), nullptr))); - } - else if (tab == 2) // Destruction - { - triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), nullptr))); - } +} + +// Non-combat strategy for summoning a Imp +// Enabled by default for the Destruction spec +// To enable, type "nc +imp" +// To disable, type "nc -imp" +SummonImpStrategy::SummonImpStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + +void SummonImpStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), NULL))); +} + +// Non-combat strategy for summoning a Voidwalker +// Disabled by default +// To enable, type "nc +voidwalker" +// To disable, type "nc -voidwalker" +SummonVoidwalkerStrategy::SummonVoidwalkerStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + +void SummonVoidwalkerStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon voidwalker", 29.0f), NULL))); +} + +// Non-combat strategy for summoning a Succubus +// Disabled by default +// To enable, type "nc +succubus" +// To disable, type "nc -succubus" +SummonSuccubusStrategy::SummonSuccubusStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + +void SummonSuccubusStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon succubus", 29.0f), NULL))); +} + +// Non-combat strategy for summoning a Felhunter +// Enabled by default for the Affliction spec +// To enable, type "nc +felhunter" +// To disable, type "nc -felhunter" +SummonFelhunterStrategy::SummonFelhunterStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + +void SummonFelhunterStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), NULL))); +} + +// Non-combat strategy for summoning a Felguard +// Enabled by default for the Demonology spec +// To enable, type "nc +felguard" +// To disable, type "nc -felguard" +SummonFelguardStrategy::SummonFelguardStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + +void SummonFelguardStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), NULL))); } // Non-combat strategy for selecting themselves to receive soulstone diff --git a/src/strategy/warlock/GenericWarlockNonCombatStrategy.h b/src/strategy/warlock/GenericWarlockNonCombatStrategy.h index 19277dfd..ef226c50 100644 --- a/src/strategy/warlock/GenericWarlockNonCombatStrategy.h +++ b/src/strategy/warlock/GenericWarlockNonCombatStrategy.h @@ -19,6 +19,56 @@ public: void InitTriggers(std::vector& triggers) override; }; +class SummonImpStrategy : public NonCombatStrategy +{ +public: + SummonImpStrategy(PlayerbotAI* ai); + virtual std::string const getName() override { return "imp"; } + +public: + void InitTriggers(std::vector& triggers) override; +}; + +class SummonVoidwalkerStrategy : public NonCombatStrategy +{ +public: + SummonVoidwalkerStrategy(PlayerbotAI* ai); + virtual std::string const getName() override { return "voidwalker"; } + +public: + void InitTriggers(std::vector& triggers) override; +}; + +class SummonSuccubusStrategy : public NonCombatStrategy +{ +public: + SummonSuccubusStrategy(PlayerbotAI* ai); + virtual std::string const getName() override { return "succubus"; } + +public: + void InitTriggers(std::vector& triggers) override; +}; + +class SummonFelhunterStrategy : public NonCombatStrategy +{ +public: + SummonFelhunterStrategy(PlayerbotAI* ai); + virtual std::string const getName() override { return "felhunter"; } + +public: + void InitTriggers(std::vector& triggers) override; +}; + +class SummonFelguardStrategy : public NonCombatStrategy +{ +public: + SummonFelguardStrategy(PlayerbotAI* ai); + virtual std::string const getName() override { return "felguard"; } + +public: + void InitTriggers(std::vector& triggers) override; +}; + class SoulstoneSelfStrategy : public NonCombatStrategy { public: diff --git a/src/strategy/warlock/WarlockAiObjectContext.cpp b/src/strategy/warlock/WarlockAiObjectContext.cpp index e8463fe8..cd56398a 100644 --- a/src/strategy/warlock/WarlockAiObjectContext.cpp +++ b/src/strategy/warlock/WarlockAiObjectContext.cpp @@ -33,6 +33,11 @@ public: 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; } private: @@ -46,6 +51,11 @@ private: 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); } }; class WarlockCombatStrategyFactoryInternal : public NamedObjectContext