From 3586ea8ba91dc8457c9ae73fcd748c3280d9ee6d Mon Sep 17 00:00:00 2001 From: Michael Serajnik Date: Mon, 6 Apr 2026 18:33:44 +0200 Subject: [PATCH 1/4] Enable bot pet spell autocast. --- src/game/PlayerBots/CombatBotBaseAI.cpp | 37 +++++++++++++++++++++++++ src/game/PlayerBots/CombatBotBaseAI.h | 1 + 2 files changed, 38 insertions(+) diff --git a/src/game/PlayerBots/CombatBotBaseAI.cpp b/src/game/PlayerBots/CombatBotBaseAI.cpp index 45b647bbdcb..d3ad9e9a22d 100644 --- a/src/game/PlayerBots/CombatBotBaseAI.cpp +++ b/src/game/PlayerBots/CombatBotBaseAI.cpp @@ -2328,8 +2328,45 @@ Player* CombatBotBaseAI::SelectDispelTarget(SpellEntry const* pSpellEntry) const return nullptr; } +void CombatBotBaseAI::InitializeBotPetAutocast() +{ + if (!me) + return; + + Pet* pet = me->GetPet(); + if (!pet || pet->GetPetAutoSpellSize() > 0) + return; + + CharmInfo* charmInfo = pet->GetCharmInfo(); + if (!charmInfo) + return; + + for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) + { + UnitActionBarEntry const* actionBarEntry = charmInfo->GetActionBarEntry(i); + if (!actionBarEntry || !actionBarEntry->IsActionBarForSpell()) + continue; + + uint32 spellId = actionBarEntry->GetAction(); + if (!spellId || !pet->HasSpell(spellId)) + continue; + + SpellEntry const* spellInfo = sSpellMgr.GetSpellEntry(spellId); + if (!spellInfo || !spellInfo->IsAutocastable()) + continue; + + pet->ToggleAutocast(spellId, true); + charmInfo->SetSpellAutocast(spellId, true); + } +} + void CombatBotBaseAI::SummonPetIfNeeded() { + // Only initialize autocast for spells the current pet already knows. + // Bot pets do not currently simulate trainer/grimoire-based pet spell + // learning. + InitializeBotPetAutocast(); + if (me->GetClass() == CLASS_HUNTER) { if (me->GetCharmGuid()) diff --git a/src/game/PlayerBots/CombatBotBaseAI.h b/src/game/PlayerBots/CombatBotBaseAI.h index 4c9c7537ecc..4ac9c3c924b 100644 --- a/src/game/PlayerBots/CombatBotBaseAI.h +++ b/src/game/PlayerBots/CombatBotBaseAI.h @@ -92,6 +92,7 @@ class CombatBotBaseAI : public PlayerBotAI void ResetSpellData(); void AddAllSpellReagents(); void SummonPetIfNeeded(); + void InitializeBotPetAutocast(); void LearnArmorProficiencies(); void LearnPremadeSpecForClass(); void EquipPremadeGearTemplate(); From 8d866e4de7ad5148337639c23d9907880d9002e8 Mon Sep 17 00:00:00 2001 From: Michael Serajnik Date: Mon, 6 Apr 2026 18:34:38 +0200 Subject: [PATCH 2/4] Don't use hardcoded list for Hunter bot pets. --- src/game/PlayerBots/CombatBotBaseAI.cpp | 89 +++++++++++++++++++------ 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/src/game/PlayerBots/CombatBotBaseAI.cpp b/src/game/PlayerBots/CombatBotBaseAI.cpp index d3ad9e9a22d..525831d6ff5 100644 --- a/src/game/PlayerBots/CombatBotBaseAI.cpp +++ b/src/game/PlayerBots/CombatBotBaseAI.cpp @@ -33,26 +33,72 @@ enum CombatBotSpells SPELL_TAME_BEAST = 13481, SPELL_REVIVE_PET = 982, SPELL_CALL_PET = 883, - - PET_WOLF = 565, - PET_CAT = 681, - PET_BEAR = 822, - PET_CRAB = 831, - PET_GORILLA = 1108, - PET_BIRD = 1109, - PET_BOAR = 1190, - PET_BAT = 1554, - PET_CROC = 1693, - PET_SPIDER = 1781, - PET_OWL = 1997, - PET_STRIDER = 2322, - PET_SCORPID = 3127, - PET_SERPENT = 3247, - PET_RAPTOR = 3254, - PET_TURTLE = 3461, - PET_HYENA = 4127, }; +namespace +{ +bool HasUsableHunterPetSpellData(uint32 petEntry) +{ + PetCreateSpellEntry const* createSpells = sObjectMgr.GetPetCreateSpellEntry(petEntry); + if (!createSpells) + return false; + + for (uint32 spellId : createSpells->spellId) + { + if (!spellId) + break; // Match Pet::InitPetCreateSpells(), which stops at the first empty slot. + + SpellEntry const* learnSpellInfo = sSpellMgr.GetSpellEntry(spellId); + if (!learnSpellInfo) + continue; + + uint32 petSpellId = spellId; + if (learnSpellInfo->Effect[0] == SPELL_EFFECT_LEARN_SPELL || + learnSpellInfo->Effect[0] == SPELL_EFFECT_LEARN_PET_SPELL) + petSpellId = learnSpellInfo->EffectTriggerSpell[0]; + + SpellEntry const* petSpellInfo = sSpellMgr.GetSpellEntry(petSpellId); + if (petSpellInfo && petSpellInfo->IsAutocastable()) + return true; + } + + return false; +} + +uint32 SelectHunterBotPetEntry() +{ + static std::vector const petEntries = []() + { + std::vector entries; + + for (const auto& itr : sObjectMgr.GetCreatureInfoMap()) + { + CreatureInfo const* cInfo = itr.second.get(); + if (!cInfo || !cInfo->IsTameable()) + continue; + + // Exclude some special case creatures. + if (cInfo->npc_flags || cInfo->script_id || cInfo->spawn_spell_id) + continue; + + // Exclude creatures that don't have at last one usable Hunter pet spell. + if (HasUsableHunterPetSpellData(cInfo->entry)) + entries.push_back(cInfo->entry); + } + + if (entries.empty()) + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CombatBotBaseAI::SelectHunterBotPetEntry: No valid hunter bot pet entries found for patch %u.", sWorld.GetWowPatch()); + + return entries; + }(); + + if (petEntries.empty()) + return 0; + + return SelectRandomContainerElement(petEntries); +} +} + void CombatBotBaseAI::AutoAssignRole() { switch (me->GetClass()) @@ -2388,9 +2434,10 @@ void CombatBotBaseAI::SummonPetIfNeeded() return; } - uint32 petId = PickRandomValue( PET_WOLF, PET_CAT, PET_BEAR, PET_CRAB, PET_GORILLA, PET_BIRD, - PET_BOAR, PET_BAT, PET_CROC, PET_SPIDER, PET_OWL, PET_STRIDER, - PET_SCORPID, PET_SERPENT, PET_RAPTOR, PET_TURTLE, PET_HYENA ); + uint32 petId = SelectHunterBotPetEntry(); + if (!petId) + return; + if (Creature* pCreature = me->SummonCreature(petId, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 0.0f, TEMPSUMMON_TIMED_COMBAT_OR_DEAD_DESPAWN, 3000, false, 3000)) From d7e63d73b68e7d99eb75bd006388b7aa41883af4 Mon Sep 17 00:00:00 2001 From: Michael Serajnik Date: Mon, 6 Apr 2026 18:35:19 +0200 Subject: [PATCH 3/4] Add Warlock bot pet fallback for lower levels. --- src/game/PlayerBots/CombatBotBaseAI.cpp | 45 ++++++++++++++++++++----- src/game/PlayerBots/CombatBotBaseAI.h | 3 ++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/game/PlayerBots/CombatBotBaseAI.cpp b/src/game/PlayerBots/CombatBotBaseAI.cpp index 525831d6ff5..aa219ec9203 100644 --- a/src/game/PlayerBots/CombatBotBaseAI.cpp +++ b/src/game/PlayerBots/CombatBotBaseAI.cpp @@ -2406,6 +2406,41 @@ void CombatBotBaseAI::InitializeBotPetAutocast() } } +std::vector CombatBotBaseAI::CollectWarlockBotSummonSpells() const +{ + std::vector summons; + if (!me) + return summons; + + if (me->HasSpell(SPELL_SUMMON_IMP)) + summons.push_back(SPELL_SUMMON_IMP); + if (me->HasSpell(SPELL_SUMMON_VOIDWALKER)) + summons.push_back(SPELL_SUMMON_VOIDWALKER); + if (me->HasSpell(SPELL_SUMMON_FELHUNTER)) + summons.push_back(SPELL_SUMMON_FELHUNTER); + if (me->HasSpell(SPELL_SUMMON_SUCCUBUS)) + summons.push_back(SPELL_SUMMON_SUCCUBUS); + + if (!summons.empty()) + return summons; + + // Fall back to the earliest summon quest requirements when no premade spec + // provides the summon spells (intended for bots that are below level 19 + // where no premade spec exists, but still includes higher levels for + // completeness' sake). + uint32 level = me->GetLevel(); + if (level >= 1) + summons.push_back(SPELL_SUMMON_IMP); + if (level >= 10) + summons.push_back(SPELL_SUMMON_VOIDWALKER); + if (level >= 20) + summons.push_back(SPELL_SUMMON_SUCCUBUS); + if (level >= 30) + summons.push_back(SPELL_SUMMON_FELHUNTER); + + return summons; +} + void CombatBotBaseAI::SummonPetIfNeeded() { // Only initialize autocast for spells the current pet already knows. @@ -2451,15 +2486,7 @@ void CombatBotBaseAI::SummonPetIfNeeded() if (me->GetPetGuid() || me->GetCharmGuid()) return; - std::vector vSummons; - if (me->HasSpell(SPELL_SUMMON_IMP)) - vSummons.push_back(SPELL_SUMMON_IMP); - if (me->HasSpell(SPELL_SUMMON_VOIDWALKER)) - vSummons.push_back(SPELL_SUMMON_VOIDWALKER); - if (me->HasSpell(SPELL_SUMMON_FELHUNTER)) - vSummons.push_back(SPELL_SUMMON_FELHUNTER); - if (me->HasSpell(SPELL_SUMMON_SUCCUBUS)) - vSummons.push_back(SPELL_SUMMON_SUCCUBUS); + std::vector vSummons = CollectWarlockBotSummonSpells(); if (!vSummons.empty()) me->CastSpell(me, SelectRandomContainerElement(vSummons), true); } diff --git a/src/game/PlayerBots/CombatBotBaseAI.h b/src/game/PlayerBots/CombatBotBaseAI.h index 4ac9c3c924b..d7131d5e655 100644 --- a/src/game/PlayerBots/CombatBotBaseAI.h +++ b/src/game/PlayerBots/CombatBotBaseAI.h @@ -5,6 +5,8 @@ #include "SpellEntry.h" #include "Player.h" +#include + struct HealSpellCompare { bool operator() (SpellEntry const* const lhs, SpellEntry const* const rhs) const @@ -93,6 +95,7 @@ class CombatBotBaseAI : public PlayerBotAI void AddAllSpellReagents(); void SummonPetIfNeeded(); void InitializeBotPetAutocast(); + std::vector CollectWarlockBotSummonSpells() const; void LearnArmorProficiencies(); void LearnPremadeSpecForClass(); void EquipPremadeGearTemplate(); From a3fc22b697c2433bf1eb1fe48fe9281ebc868842 Mon Sep 17 00:00:00 2001 From: Michael Serajnik Date: Mon, 6 Apr 2026 18:41:23 +0200 Subject: [PATCH 4/4] Wrap item stat helper in anonymous namespace. --- src/game/PlayerBots/CombatBotBaseAI.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/game/PlayerBots/CombatBotBaseAI.cpp b/src/game/PlayerBots/CombatBotBaseAI.cpp index aa219ec9203..8e42b05ee87 100644 --- a/src/game/PlayerBots/CombatBotBaseAI.cpp +++ b/src/game/PlayerBots/CombatBotBaseAI.cpp @@ -2667,6 +2667,8 @@ void CombatBotBaseAI::EquipPremadeGearTemplate() } } +namespace +{ inline uint32 GetPrimaryItemStatForClassAndRole(uint8 playerClass, uint8 role) { switch (playerClass) @@ -2698,6 +2700,7 @@ inline uint32 GetPrimaryItemStatForClassAndRole(uint8 playerClass, uint8 role) } return ITEM_MOD_STAMINA; } +} void CombatBotBaseAI::EquipRandomGearInEmptySlots() {