9.7 KiB
Task 1.7: State Machine Integration with BotAI & BotSession
Summary
This task integrates the BotInitStateMachine into BotAI and BotSession to fix Issue #1 where bots already in groups at login wouldn't follow their leader.
Root Cause Analysis
The bug occurred because OnGroupJoined() was called BEFORE the bot was fully in the world (IsInWorld() returned false), causing strategy activation to fail silently. The state machine enforces proper initialization ordering.
Files Modified
1. BotAI.h - Add State Machine and Safe References
Location: c:/TrinityBots/TrinityCore/src/modules/Playerbot/AI/BotAI.h
Key Changes:
// Add includes at top:
#include "Core/StateMachine/BotInitStateMachine.h"
#include "Core/References/SafeObjectReference.h"
// Add to private members:
std::unique_ptr<StateMachine::BotInitStateMachine> m_initStateMachine;
References::SafePlayerReference m_groupLeader;
// Add public methods:
bool IsFullyInitialized() const {
return m_initStateMachine && m_initStateMachine->IsReady();
}
float GetInitProgress() const {
return m_initStateMachine ? m_initStateMachine->GetProgress() : 0.0f;
}
void SetGroupLeader(Player* leader) {
m_groupLeader.Set(leader);
}
Player* GetGroupLeader() const {
return m_groupLeader.Get();
}
void ActivateBaseStrategies();
2. BotAI.cpp - Integrate State Machine into Update Loop
Location: c:/TrinityBots/TrinityCore/src/modules/Playerbot/AI/BotAI.cpp
Constructor Changes:
BotAI::BotAI(Player* bot)
: _bot(bot)
, m_initStateMachine(nullptr) // Will be created in first UpdateAI
, m_groupLeader() // Safe reference, initially empty
{
// ... existing initialization ...
// DON'T check for group here - let state machine handle it
// REMOVED: if (_bot->GetGroup()) { OnGroupJoined(_bot->GetGroup()); }
}
UpdateAI Changes:
void BotAI::UpdateAI(uint32 diff)
{
if (!_bot || !_bot->IsInWorld())
return;
// PHASE 1: Initialize state machine on first update
if (!m_initStateMachine && _bot->IsInWorld())
{
m_initStateMachine = std::make_unique<StateMachine::BotInitStateMachine>(_bot);
m_initStateMachine->Start();
TC_LOG_INFO("module.playerbot",
"BotInitStateMachine created and started for bot {}",
_bot->GetName());
}
// PHASE 2: Update state machine
if (m_initStateMachine && !m_initStateMachine->IsReady())
{
m_initStateMachine->Update(diff);
// Don't process AI logic until initialization complete
if (!m_initStateMachine->IsReady())
{
return; // Skip rest of update
}
// State machine just became ready!
TC_LOG_INFO("module.playerbot",
"Bot {} initialization complete - now ready for AI updates",
_bot->GetName());
// Check if bot was in group at login
if (m_initStateMachine->WasInGroupAtLogin())
{
if (Group* group = _bot->GetGroup())
{
OnGroupJoined(group);
}
}
else
{
ActivateStrategy("idle");
}
}
// PHASE 3: Normal AI updates (existing code continues here)
// ... rest of UpdateAI ...
}
New Method:
void BotAI::ActivateBaseStrategies()
{
// Called by BotInitStateMachine when ready
ActivateStrategy("idle");
TC_LOG_DEBUG("module.playerbot",
"Base strategies activated for bot {}",
_bot ? _bot->GetName() : "NULL");
}
Modified OnGroupJoined:
void BotAI::OnGroupJoined(Group* group)
{
if (!group || !_bot) return;
// Set group leader using safe reference
ObjectGuid leaderGuid = group->GetLeaderGUID();
if (Player* leader = ObjectAccessor::FindPlayer(leaderGuid))
{
SetGroupLeader(leader);
TC_LOG_INFO("module.playerbot",
"Bot {} joined group, leader set to {}",
_bot->GetName(), leader->GetName());
}
// Activate follow strategy
ActivateStrategy("follow");
// ... rest of existing OnGroupJoined logic ...
}
3. BotSession.cpp - Remove Problematic Group Check
Location: c:/TrinityBots/TrinityCore/src/modules/Playerbot/Session/BotSession.cpp
Lines: 944-960
REMOVE THIS ENTIRE BLOCK:
// DELETE THESE LINES (944-960):
if (Player* player = GetPlayer())
{
Group* group = player->GetGroup();
TC_LOG_ERROR("module.playerbot.session", "Bot {} login group check: player={}, group={}",
player->GetName(), (void*)player, (void*)group);
if (group)
{
TC_LOG_INFO("module.playerbot.session", "Bot {} is already in group at login - activating strategies", player->GetName());
if (BotAI* ai = GetAI())
{
TC_LOG_ERROR("module.playerbot.session", "About to call OnGroupJoined with group={}", (void*)group);
ai->OnGroupJoined(group); // TOO EARLY!
}
}
}
REPLACE WITH:
// NEW CODE - State machine handles this properly now:
// The BotInitStateMachine will check for group membership
// at the correct time (in CHECKING_GROUP state, AFTER IsInWorld())
// No explicit call needed here - state machine handles it automatically
TC_LOG_INFO("module.playerbot.session",
"Bot {} login complete - state machine will handle initialization",
GetPlayer()->GetName());
4. LeaderFollowBehavior.cpp - Use Safe References
Location: c:/TrinityBots/TrinityCore/src/modules/Playerbot/Movement/LeaderFollowBehavior.cpp
Modified UpdateBehavior:
void LeaderFollowBehavior::UpdateBehavior(uint32 diff, BotAI* ai)
{
if (!ai || !ai->GetBot())
return;
Player* bot = ai->GetBot();
// NEW: Use safe reference from BotAI
Player* leader = ai->GetGroupLeader();
if (!leader)
{
// Leader logged out or no longer exists - safe!
TC_LOG_DEBUG("module.playerbot.follow",
"Bot {} has no valid leader to follow",
bot->GetName());
ClearFollowTarget();
return;
}
// Continue with existing follow logic using 'leader'
SetFollowTarget(leader);
// ... rest of existing UpdateBehavior code ...
}
Integration Flow
Player Login
↓
BotSession::HandleBotPlayerLogin()
↓
AddToWorld()
↓
Bot is now IsInWorld() == true
↓
[No explicit group check here anymore]
↓
BotAI::UpdateAI() first call
↓
Create BotInitStateMachine
↓
Start() → CREATED → LOADING_CHARACTER
↓
UpdateAI() subsequent calls
↓
State machine progresses:
LOADING_CHARACTER → IN_WORLD → CHECKING_GROUP
↓
In CHECKING_GROUP state:
- Bot is guaranteed IsInWorld()
- Safe to call GetGroup()
- If in group, cache leader info
↓
ACTIVATING_STRATEGIES state:
- Call OnGroupJoined() if in group
- Follow strategy activates
- OnActivate() runs successfully
↓
READY state:
- Bot fully initialized
- Normal AI updates begin
Testing Checklist
-
Solo Bot Login
- Bot logs in without group
- State machine initializes properly
- Idle strategy activates
- No crashes or errors
-
Grouped Bot Login
- Bot logs in already in group
- State machine detects group
- Follow strategy activates
- Bot follows leader correctly
-
Leader Logout
- Leader logs out while bot is following
- Safe reference returns nullptr
- Bot stops following gracefully
- No crash on leader deletion
-
Performance
- State machine overhead < 1ms
- Safe reference cache hit rate > 90%
- No memory leaks
- No thread deadlocks
Expected Log Output
Successful Grouped Bot Login:
[INFO] BotInitStateMachine created and started for bot TestBot
[DEBUG] State transition: CREATED → LOADING_CHARACTER
[DEBUG] State transition: LOADING_CHARACTER → IN_WORLD
[DEBUG] State transition: IN_WORLD → CHECKING_GROUP
[INFO] Bot TestBot is in group with leader TestLeader
[DEBUG] State transition: CHECKING_GROUP → ACTIVATING_STRATEGIES
[INFO] Bot TestBot joined group, leader set to TestLeader
[INFO] Bot TestBot activated group strategies - follow and group_combat
[DEBUG] State transition: ACTIVATING_STRATEGIES → READY
[INFO] Bot TestBot initialization complete - now ready for AI updates
Files Affected
src/modules/Playerbot/AI/BotAI.h- Added state machine member and methodssrc/modules/Playerbot/AI/BotAI.cpp- Integrated state machine into UpdateAIsrc/modules/Playerbot/Session/BotSession.cpp- Removed early group checksrc/modules/Playerbot/Movement/LeaderFollowBehavior.cpp- Use safe referencessrc/modules/Playerbot/Core/Events/BotEventTypes.h- Already exists (skeleton)src/modules/Playerbot/CMakeLists.txt- Already includes all required files
Build Instructions
-
The CMakeLists.txt already includes:
Core/StateMachine/BotInitStateMachine.cpp/hCore/References/SafeObjectReference.cpp/hCore/Events/BotEventTypes.h
-
Include directories are already set:
${CMAKE_CURRENT_SOURCE_DIR}/Core${CMAKE_CURRENT_SOURCE_DIR}/Core/References${CMAKE_CURRENT_SOURCE_DIR}/Core/StateMachine
-
Rebuild:
cd build cmake --build . --target playerbot
Conclusion
This integration fixes Issue #1 by ensuring bot initialization happens in the correct order using a state machine. The bot is guaranteed to be fully in the world before any group operations occur. Additionally, safe references prevent crashes when group leaders log out (Issue #4).
The solution is non-intrusive, maintaining backward compatibility while adding robust initialization sequencing. The state machine pattern can be extended for other initialization requirements in the future.