15 KiB
Phase 2.3: Fix Combat Activation - Universal ClassAI
Duration: 1 week (2025-01-27 to 2025-02-03) Status: ⏳ PENDING Owner: Development Team
Objectives
Make ClassAI (class-specific combat AI) work in ALL combat situations, not just group combat:
- Remove group-only restriction from OnCombatUpdate()
- Ensure ClassAI activates during solo questing
- Ensure ClassAI activates when gathering mobs interrupt
- Maintain ClassAI activation in group/dungeon/raid scenarios
- Verify all 13 classes work correctly
Background
Current Problem
// CURRENT (WRONG) - Only runs in group combat
void BotAI::UpdateAI(uint32 diff)
{
UpdateStrategies(diff);
if (IsInCombat() && HasStrategy("group_combat")) // ❌ Group-only restriction
{
OnCombatUpdate(diff); // ClassAI called here
}
}
Result:
- ✅ Bot in dungeon group → ClassAI works, spells cast
- ❌ Bot questing solo → ClassAI never called, only auto-attacks
- ❌ Bot gathering, mob attacks → ClassAI never called, only auto-attacks
Root Cause
OnCombatUpdate() (which calls ClassAI) is gated behind group combat strategy check. This was intended for group coordination but accidentally disabled ALL spell casting for solo bots.
Solution
// NEW (CORRECT) - Runs in ALL combat
void BotAI::UpdateAI(uint32 diff)
{
UpdateStrategies(diff);
if (IsInCombat()) // ✅ No group check - always run
{
OnCombatUpdate(diff); // ClassAI runs for EVERYONE in combat
}
}
Result:
- ✅ Bot in dungeon group → ClassAI works
- ✅ Bot questing solo → ClassAI works, casts spells
- ✅ Bot gathering, interrupted → ClassAI works, fights back properly
Technical Requirements
Performance Constraints
- OnCombatUpdate() must remain <1ms per bot
- ClassAI::Update() must remain <0.5ms per bot
- Total combat overhead <1.5ms per bot
- Must scale to 500+ bots in simultaneous combat
ClassAI Architecture
Each class has specialized ClassAI:
- WarriorClassAI - Rage management, stance dancing
- PaladinClassAI - Seal management, holy power
- HunterClassAI - Pet control, focus management
- RogueClassAI - Combo points, energy management
- PriestClassAI - Mana management, dual-spec healing/shadow
- ShamanClassAI - Totem management, shock/lightning rotation
- MageClassAI - Arcane/Fire/Frost rotations, mana gems
- WarlockClassAI - Pet management, soul shards, DoTs
- DruidClassAI - Shapeshifting, HoTs, versatile forms
- DeathKnightClassAI - Rune management, presence system
- MonkClassAI - Chi/energy management, roll mechanics
- DemonHunterClassAI - Fury/pain resource, metamorphosis
- EvokerClassAI - Essence management, empowerment system
Integration Points
- BotAI::OnCombatUpdate() is the single entry point
- OnCombatUpdate() calls _classAI->Update(diff)
- ClassAI has full access to Player* for spell casting
- Must work with CombatMovementStrategy (Phase 2.2)
Deliverables
1. Modified BotAI::UpdateAI()
Location: src/modules/Playerbot/AI/BotAI.cpp
Before (lines ~140-160):
void BotAI::UpdateAI(uint32 diff)
{
if (!_bot || !_bot->IsInWorld())
return;
// Update active strategies
UpdateStrategies(diff);
// Update movement
UpdateMovement(diff);
// Combat specialization (GROUP COMBAT ONLY) ❌
if (IsInCombat() && HasActiveStrategy("group_combat"))
{
OnCombatUpdate(diff);
}
}
After:
void BotAI::UpdateAI(uint32 diff)
{
if (!_bot || !_bot->IsInWorld())
return;
// PHASE 1: Manager updates (heavyweight, throttled)
UpdateManagers(diff); // From Phase 2.4
// PHASE 2: Strategy updates (lightweight, every frame)
UpdateStrategies(diff);
// PHASE 3: Movement execution
UpdateMovement(diff);
// PHASE 4: Combat specialization (ALL COMBAT) ✅
if (IsInCombat()) // No group restriction!
{
OnCombatUpdate(diff);
}
}
2. Verify ClassAI Initialization
Location: src/modules/Playerbot/AI/BotAI.cpp
Ensure ClassAI is created for ALL bots:
void BotAI::Initialize()
{
if (!_bot)
return;
// Create class-specific AI
_classAI = BotAIFactory::CreateClassAI(_bot, this);
if (!_classAI)
{
TC_LOG_ERROR("module.playerbot", "Failed to create ClassAI for bot {}", _bot->GetName());
return;
}
// Initialize strategies
InitializeStrategies();
TC_LOG_INFO("module.playerbot", "BotAI initialized for {} (class: {})",
_bot->GetName(), _bot->getClass());
}
3. Test Solo Quest Combat
Location: tests/integration/SoloQuestCombatTest.cpp
Test Scenario:
TEST(SoloQuestCombat, WarriorCastsSpells)
{
// Setup: Create level 20 Warrior bot, no group
Bot* warrior = CreateTestBot(CLASS_WARRIOR, 20);
ASSERT_FALSE(warrior->GetGroup());
// Action: Bot attacks training dummy
Unit* dummy = SpawnTrainingDummy();
warrior->Attack(dummy, true);
// Wait for combat
WaitForCombat(warrior, 5000);
ASSERT_TRUE(warrior->IsInCombat());
// Verify: Bot casts Heroic Strike, not just auto-attacks
uint32 heroicStrikeCount = CountSpellCasts(warrior, 78); // Heroic Strike
ASSERT_GT(heroicStrikeCount, 0) << "Warrior should cast Heroic Strike in solo combat";
// Verify: OnCombatUpdate was called
ASSERT_TRUE(warrior->GetAI()->WasOnCombatUpdateCalled());
}
4. Test All 13 Classes
Location: tests/integration/AllClassesCombatTest.cpp
Test Matrix:
- ✅ Warrior: Heroic Strike, Rend, Battle Shout
- ✅ Paladin: Judgement, Crusader Strike, seals
- ✅ Hunter: Steady Shot, Arcane Shot, pet commands
- ✅ Rogue: Sinister Strike, Eviscerate, combo points
- ✅ Priest: Smite, Shadow Word: Pain, Mind Blast
- ✅ Shaman: Lightning Bolt, Flame Shock, totems
- ✅ Mage: Fireball, Frostbolt, Arcane Missiles
- ✅ Warlock: Shadow Bolt, Corruption, pet commands
- ✅ Druid: Wrath, Moonfire, shapeshifting
- ✅ Death Knight: Icy Touch, Plague Strike, rune management
- ✅ Monk: Jab, Tiger Palm, chi management
- ✅ Demon Hunter: Demon's Bite, Chaos Strike
- ✅ Evoker: Living Flame, Azure Strike
5. Test Gathering Interruption
Location: tests/integration/GatheringCombatTest.cpp
Test Scenario:
TEST(GatheringCombat, BotFightsBackWhenInterrupted)
{
// Setup: Bot is gathering herbs
Bot* druid = CreateTestBot(CLASS_DRUID, 30);
GameObject* herb = SpawnHerb(druid->GetPosition());
druid->StartGathering(herb);
// Action: Mob attacks while gathering
Creature* mob = SpawnHostileMob(druid->GetPosition());
mob->Attack(druid, true);
// Wait for bot to react
WaitForCombat(druid, 5000);
ASSERT_TRUE(druid->IsInCombat());
// Verify: Bot STOPS gathering and fights
ASSERT_FALSE(druid->IsGathering());
// Verify: Bot casts combat spells
uint32 wrathCount = CountSpellCasts(druid, 5176); // Wrath
ASSERT_GT(wrathCount, 0) << "Druid should cast Wrath when attacked";
// Verify: Bot kills mob
WaitForCombatEnd(druid, 30000);
ASSERT_TRUE(mob->isDead());
}
6. Test Group Combat (Regression)
Location: tests/integration/GroupCombatRegressionTest.cpp
Verify group combat still works:
TEST(GroupCombat, GroupBotsStillCastSpells)
{
// Setup: Create 5-bot dungeon group
Group* group = CreateTestGroup();
Bot* tank = AddBotToGroup(group, CLASS_WARRIOR, ROLE_TANK);
Bot* healer = AddBotToGroup(group, CLASS_PRIEST, ROLE_HEALER);
Bot* dps1 = AddBotToGroup(group, CLASS_MAGE, ROLE_DPS);
Bot* dps2 = AddBotToGroup(group, CLASS_ROGUE, ROLE_DPS);
Bot* dps3 = AddBotToGroup(group, CLASS_HUNTER, ROLE_DPS);
// Action: Pull dungeon boss
Creature* boss = SpawnDungeonBoss();
tank->Attack(boss, true);
WaitForGroupCombat(group, 5000);
// Verify: ALL bots cast spells
ASSERT_GT(CountSpellCasts(tank, 78), 0); // Heroic Strike
ASSERT_GT(CountSpellCasts(healer, 2061), 0); // Flash Heal
ASSERT_GT(CountSpellCasts(dps1, 133), 0); // Fireball
ASSERT_GT(CountSpellCasts(dps2, 1752), 0); // Sinister Strike
ASSERT_GT(CountSpellCasts(dps3, 3044), 0); // Arcane Shot
// Verify: Boss dies
WaitForCombatEnd(group, 60000);
ASSERT_TRUE(boss->isDead());
}
7. Documentation
Location: docs/COMBAT_ACTIVATION_GUIDE.md
Content:
- Explanation of universal ClassAI activation
- Combat flow diagram (UpdateAI → OnCombatUpdate → ClassAI)
- Difference between group combat strategy and universal combat
- Troubleshooting guide for "bot doesn't cast spells"
- Performance considerations
Implementation Steps
Step 1: Remove Group Restriction (1 day)
File: src/modules/Playerbot/AI/BotAI.cpp
Change 1: Modify UpdateAI()
// OLD:
if (IsInCombat() && HasActiveStrategy("group_combat"))
OnCombatUpdate(diff);
// NEW:
if (IsInCombat())
OnCombatUpdate(diff);
Change 2: Add debug logging (temporary)
if (IsInCombat())
{
static uint32 debugCounter = 0;
if (++debugCounter % 100 == 0)
{
TC_LOG_INFO("module.playerbot", "🎯 OnCombatUpdate() called for bot {} (counter={})",
_bot->GetName(), debugCounter);
}
OnCombatUpdate(diff);
}
Compile and verify: No compilation errors
Step 2: Verify ClassAI Initialization (1 day)
File: src/modules/Playerbot/AI/BotAI.cpp
Add initialization check:
void BotAI::Initialize()
{
// ... existing code ...
_classAI = BotAIFactory::CreateClassAI(_bot, this);
if (!_classAI)
{
TC_LOG_ERROR("module.playerbot", "❌ CRITICAL: Failed to create ClassAI for bot {} (class: {})",
_bot->GetName(), _bot->getClass());
return;
}
TC_LOG_INFO("module.playerbot", "✅ ClassAI created for bot {} (class: {})",
_bot->GetName(), _bot->getClass());
}
Test: Spawn 13 bots (one of each class), verify all get ClassAI
Step 3: Test Solo Quest Combat (2 days)
Test with 3 classes (Warrior, Mage, Priest):
- Create test bot (no group)
- Attack training dummy
- Monitor combat log
- Verify spell casts (not just auto-attacks)
- Verify OnCombatUpdate() called
Expected Results:
- Warrior: Heroic Strike every 3-5 seconds
- Mage: Fireball spam
- Priest: Smite/Mind Blast rotation
If fails: Add detailed debug logging in OnCombatUpdate()
Step 4: Test All 13 Classes (2 days)
Test Matrix (automate with script):
for class in WARRIOR PALADIN HUNTER ROGUE PRIEST SHAMAN MAGE WARLOCK DRUID DK MONK DH EVOKER; do
echo "Testing $class..."
spawn_bot $class 30
attack_dummy
wait_for_combat
verify_spell_casts
cleanup
done
Success Criteria: All 13 classes cast at least 1 class-appropriate spell
Step 5: Test Gathering Interruption (1 day)
Test Scenario:
- Bot starts gathering herb/ore
- Mob spawns and attacks
- Verify bot stops gathering
- Verify bot enters combat and casts spells
- Verify bot resumes gathering after combat
Classes to Test: Druid, Rogue (stealth), Warrior
Step 6: Regression Testing (1 day)
Verify group combat still works:
- Create 5-bot group
- Enter dungeon
- Pull boss
- Verify ALL bots cast spells
- Verify tank holds aggro
- Verify healer heals
- Verify DPS deal damage
- Verify boss dies
If regression detected: Investigate group combat strategy interaction
Step 7: Documentation & Cleanup (1 day)
- Write combat activation guide
- Remove temporary debug logging
- Update ARCHITECTURE.md with new flow
- Create troubleshooting guide
- Final code review
Success Criteria
Functional Requirements
- ✅ OnCombatUpdate() called for ALL bots in combat (not just groups)
- ✅ Solo questing bots cast class-appropriate spells
- ✅ Gathering bots fight back when attacked
- ✅ Group combat still works (no regression)
- ✅ All 13 classes verified working
- ✅ Dungeon/raid combat works
Combat Effectiveness
- ✅ Solo bot kills level-appropriate mob in <30 seconds
- ✅ Bot uses 80%+ of available GCDs (Global Cooldowns)
- ✅ Bot manages resources correctly (rage/mana/energy/runes)
- ✅ Bot uses cooldowns appropriately
- ✅ Bot doesn't stand idle during combat
Performance
- ✅ OnCombatUpdate() <1ms per bot per frame
- ✅ ClassAI::Update() <0.5ms per bot per frame
- ✅ No performance regression with 100+ bots in combat
- ✅ No memory leaks after 1000 combat cycles
Code Quality
- ✅ Clean code without workarounds
- ✅ No group-specific restrictions in core combat logic
- ✅ Proper separation: Strategies coordinate, ClassAI executes
- ✅ Full documentation of combat flow
- ✅ Comprehensive test coverage
Dependencies
Requires
- Phase 2.1 complete (BehaviorManager base class - for context)
- Phase 2.2 complete (CombatMovementStrategy - for positioning)
- Existing ClassAI implementations (all 13 classes)
- TrinityCore combat APIs
Blocks
- Phase 2.5 (IdleStrategy needs working combat to delegate)
- Phase 2.6 (Integration testing requires combat working)
- Full bot functionality (combat is core feature)
Risk Mitigation
Risk: Performance regression with universal combat
Mitigation:
- Profile OnCombatUpdate() before/after change
- Ensure ClassAI implementations are optimized
- Test with 500+ bots in combat
Risk: Breaking group combat coordination
Mitigation:
- Keep GroupCombatStrategy separate (for coordination)
- GroupCombatStrategy observes, ClassAI executes
- Comprehensive regression testing
Risk: Combat spam in logs
Mitigation:
- Use throttled debug logging (every 100th call)
- Remove debug logging after testing phase
- Configure log levels properly
Risk: Bots stuck in combat state
Mitigation:
- Verify IsInCombat() state management
- Test combat → idle transitions
- Add automatic combat timeout (5 minutes)
Testing Checklist
Solo Combat Testing
- Warrior solo quest combat
- Mage solo quest combat
- Priest solo quest combat
- Hunter solo quest combat (with pet)
- Rogue solo quest combat (stealth opener)
- All other 8 classes solo combat
Interruption Testing
- Gathering herbs, attacked by mob
- Fishing, attacked by mob
- Skinning, attacked by mob
- Trading with NPC, attacked by mob
Group Combat Testing (Regression)
- 5-bot dungeon group combat
- 10-bot raid combat
- 25-bot raid combat
- Tank holds aggro with threat skills
- Healer keeps group alive
- DPS use full rotations
Edge Cases
- Bot level 1 combat (minimal spells)
- Bot level 80 combat (full rotation)
- Dual-wielding classes
- Pet classes (Hunter, Warlock, DK)
- Shapeshifting (Druid)
- Stealth classes (Rogue, Druid)
Next Phase
After completion, proceed to Phase 2.4: Refactor Managers (Remove Automation Singletons)
Last Updated: 2025-01-13 Next Review: 2025-02-03