fix bot crash

This commit is contained in:
luis
2026-05-01 03:07:34 -03:00
parent 4c953ad571
commit 19deb2f50e
7 changed files with 145 additions and 84 deletions
@@ -292,8 +292,8 @@ CharacterDatabasePreparedStatement* BotDatabasePool::GetPreparedStatement(uint32
468, // CHAR_SEL_GM_SUGGESTIONS (duplicate entry for clarity)
536, // CHAR_SEL_CHARACTER_AURA_FROZEN
537, // CHAR_SEL_CHARACTER_ONLINE
539, // CHAR_SEL_CHAR_DEL_INFO_BY_NAME
540, // CHAR_SEL_CHAR_DEL_INFO
368, // CHAR_SEL_CHAR_DEL_INFO_BY_NAME
369, // CHAR_SEL_CHAR_DEL_INFO
541, // CHAR_SEL_CHARS_BY_ACCOUNT_ID
542, // CHAR_SEL_CHAR_PINFO
543, // CHAR_SEL_PINFO_BANS
@@ -520,8 +520,8 @@ void PlayerbotCharacterDBInterface::InitializeStatementClassification()
// Character info queries
536, // CHAR_SEL_CHARACTER_AURA_FROZEN
537, // CHAR_SEL_CHARACTER_ONLINE
539, // CHAR_SEL_CHAR_DEL_INFO_BY_NAME
540, // CHAR_SEL_CHAR_DEL_INFO
368, // CHAR_SEL_CHAR_DEL_INFO_BY_NAME
369, // CHAR_SEL_CHAR_DEL_INFO
541, // CHAR_SEL_CHARS_BY_ACCOUNT_ID
542, // CHAR_SEL_CHAR_PINFO
543, // CHAR_SEL_PINFO_BANS
+65 -59
View File
@@ -27,6 +27,7 @@
#include "SpellAuraEffects.h"
#include "SpellMgr.h"
#include "TemporarySummon.h"
#include "../../modules/Playerbot/Core/PlayerBotHooks.h"
#include <boost/heap/fibonacci_heap.hpp>
const CompareThreatLessThan ThreatManager::CompareThreat;
@@ -39,12 +40,17 @@ void ThreatReference::AddThreat(float amount)
{
if (amount == 0.0f)
return;
float oldThreat = _baseAmount;
_baseAmount = std::max<float>(_baseAmount + amount, 0.0f);
if (amount > 0.0f)
HeapNotifyIncreased();
else
HeapNotifyDecreased();
_mgr._needClientUpdate = true;
// PLAYERBOT HOOK: Notify bots of threat change
if (Playerbot::PlayerBotHooks::OnThreatChanged)
Playerbot::PlayerBotHooks::OnThreatChanged(_mgr.GetOwner(), _victim, oldThreat, _baseAmount);
}
void ThreatReference::ScaleThreat(float factor)
@@ -154,7 +160,7 @@ void ThreatReference::UnregisterAndFree()
class ThreatReferenceImpl : public ThreatReference
{
public:
explicit ThreatReferenceImpl(ThreatManager* mgr, Unit* victim) : ThreatReference(mgr, victim) { }
explicit ThreatReferenceImpl(ThreatManager* mgr, Unit* victim) : ThreatReference(mgr, victim) {}
ThreatManager::Heap::handle_type _handle;
};
@@ -190,7 +196,7 @@ void ThreatReference::HeapNotifyDecreased()
}
ThreatManager::ThreatManager(Unit* owner) : _owner(owner), _ownerCanHaveThreatList(false), _needClientUpdate(false), _needThreatClearUpdate(false), _updateTimer(THREAT_UPDATE_INTERVAL),
_sortedThreatList(std::make_unique<Heap>()), _currentVictimRef(nullptr), _fixateRef(nullptr)
_sortedThreatList(std::make_unique<Heap>()), _currentVictimRef(nullptr), _fixateRef(nullptr)
{
for (int8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
_singleSchoolModifiers[i] = 1.0f;
@@ -295,12 +301,12 @@ Trinity::IteratorPair<ThreatManager::ThreatListIterator, std::nullptr_t> ThreatM
auto itr = _myThreatListEntries.begin();
auto end = _myThreatListEntries.end();
std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
{
if (itr == end)
return nullptr;
{
if (itr == end)
return nullptr;
return (itr++)->second;
};
return (itr++)->second;
};
return { ThreatListIterator{ std::move(generator) }, nullptr };
}
@@ -309,12 +315,12 @@ Trinity::IteratorPair<ThreatManager::ThreatListIterator, std::nullptr_t> ThreatM
auto itr = _sortedThreatList->ordered_begin();
auto end = _sortedThreatList->ordered_end();
std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
{
if (itr == end)
return nullptr;
{
if (itr == end)
return nullptr;
return *(itr++);
};
return *(itr++);
};
return { ThreatListIterator{ std::move(generator) }, nullptr };
}
@@ -482,7 +488,7 @@ void ThreatManager::ScaleThreat(Unit* target, float factor)
{
auto it = _myThreatListEntries.find(target->GetGUID());
if (it != _myThreatListEntries.end())
it->second->ScaleThreat(std::max<float>(factor,0.0f));
it->second->ScaleThreat(std::max<float>(factor, 0.0f));
}
void ThreatManager::MatchUnitThreatToHighestThreat(Unit* target)
@@ -675,7 +681,7 @@ void ThreatManager::RegisterForAIUpdate(ObjectGuid const& guid)
return a->_online < b->_online;
if (a->_taunted != b->_taunted) // taunt state precedence (TAUNT > NONE > DETAUNT)
return a->_taunted < b->_taunted;
return (a->GetThreat()*aWeight < b->GetThreat());
return (a->GetThreat() * aWeight < b->GetThreat());
}
/*static*/ float ThreatManager::CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell)
@@ -696,40 +702,40 @@ void ThreatManager::RegisterForAIUpdate(ObjectGuid const& guid)
SpellSchoolMask const mask = spell ? spell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL;
switch (mask)
{
case SPELL_SCHOOL_MASK_NORMAL:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NORMAL];
break;
case SPELL_SCHOOL_MASK_HOLY:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_HOLY];
break;
case SPELL_SCHOOL_MASK_FIRE:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FIRE];
break;
case SPELL_SCHOOL_MASK_NATURE:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NATURE];
break;
case SPELL_SCHOOL_MASK_FROST:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FROST];
break;
case SPELL_SCHOOL_MASK_SHADOW:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_SHADOW];
break;
case SPELL_SCHOOL_MASK_ARCANE:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_ARCANE];
break;
default:
case SPELL_SCHOOL_MASK_NORMAL:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NORMAL];
break;
case SPELL_SCHOOL_MASK_HOLY:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_HOLY];
break;
case SPELL_SCHOOL_MASK_FIRE:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FIRE];
break;
case SPELL_SCHOOL_MASK_NATURE:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NATURE];
break;
case SPELL_SCHOOL_MASK_FROST:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FROST];
break;
case SPELL_SCHOOL_MASK_SHADOW:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_SHADOW];
break;
case SPELL_SCHOOL_MASK_ARCANE:
threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_ARCANE];
break;
default:
{
auto it = victimMgr._multiSchoolModifiers.find(mask);
if (it != victimMgr._multiSchoolModifiers.end())
{
auto it = victimMgr._multiSchoolModifiers.find(mask);
if (it != victimMgr._multiSchoolModifiers.end())
{
threat *= it->second;
break;
}
float mod = victim->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, mask);
victimMgr._multiSchoolModifiers[mask] = mod;
threat *= mod;
threat *= it->second;
break;
}
float mod = victim->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, mask);
victimMgr._multiSchoolModifiers[mask] = mod;
threat *= mod;
break;
}
}
return threat;
}
@@ -857,21 +863,21 @@ void ThreatManager::SendThreatListToClients(bool newHighest) const
return;
auto fillSharedPacketDataAndSend = [&](auto& packet)
{
packet.UnitGUID = _owner->GetGUID();
packet.ThreatList.reserve(_sortedThreatList->size());
for (ThreatReference const* ref : *_sortedThreatList)
{
if (!ref->IsAvailable())
continue;
packet.UnitGUID = _owner->GetGUID();
packet.ThreatList.reserve(_sortedThreatList->size());
for (ThreatReference const* ref : *_sortedThreatList)
{
if (!ref->IsAvailable())
continue;
WorldPackets::Combat::ThreatInfo threatInfo;
threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
threatInfo.Threat = int64(ref->GetThreat() * 100);
packet.ThreatList.push_back(threatInfo);
}
_owner->SendMessageToSet(packet.Write(), false);
};
WorldPackets::Combat::ThreatInfo threatInfo;
threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
threatInfo.Threat = int64(ref->GetThreat() * 100);
packet.ThreatList.push_back(threatInfo);
}
_owner->SendMessageToSet(packet.Write(), false);
};
if (newHighest)
{
@@ -931,7 +937,7 @@ void ThreatManager::UpdateRedirectInfo()
for (auto const& pair : _redirectRegistry) // (spellid, victim -> pct)
for (auto const& victimPair : pair.second) // (victim,pct)
{
float thisPct = std::min(100.0f - totalPct, victimPair.second);
float thisPct = std::min(100 - totalPct, victimPair.second);
if (thisPct > 0)
{
_redirectInfo.push_back({ victimPair.first, thisPct });
+22 -17
View File
@@ -163,6 +163,8 @@
#include <G3D/g3dmath.h>
#include <sstream>
#include "../../modules/Playerbot/Core/PlayerBotHooks.h"
// corpse reclaim times
#define DEATH_EXPIRE_STEP (5*MINUTE)
#define MAX_DEATH_COUNT 3
@@ -1190,6 +1192,10 @@ void Player::setDeathState(DeathState s)
// reset all death criterias
FailCriteria(CriteriaFailEvent::Death, 0);
// PLAYERBOT HOOK: Notify bots of death
if (Playerbot::PlayerBotHooks::OnPlayerDeath)
Playerbot::PlayerBotHooks::OnPlayerDeath(this);
}
Unit::setDeathState(s);
@@ -4646,6 +4652,10 @@ void Player::ResurrectPlayer(float restore_percent, bool applySickness)
}
}
}
// PLAYERBOT HOOK: Notify bots of resurrection
if (Playerbot::PlayerBotHooks::OnPlayerResurrected)
Playerbot::PlayerBotHooks::OnPlayerResurrected(this);
}
void Player::KillPlayer()
@@ -25760,7 +25770,12 @@ void Player::SendInitialPacketsAfterAddToMap()
GetSession()->SendLoadCUFProfiles();
CastSpell(this, 836, true); // LOGINEFFECT
// Skip LOGINEFFECT for bots - visual effect requires client rendering
// Bots don't send CMSG_CAST_SPELL ACKs, causing m_spellModTakingSpell assertion failures (Spell.cpp:603)
#ifdef BUILD_PLAYERBOT
if (!GetSession()->IsBot())
#endif
CastSpell(this, 836, true); // LOGINEFFECT
// set some aura effects that send packet to player client after add player to map
// SendMessageToSet not send it to player not it map, only for aura that not changed anything at re-apply
@@ -26649,25 +26664,15 @@ Player* Player::GetTrader() const
return m_trade ? m_trade->GetTrader() : nullptr;
}
void Player::InitiateTrade(Player* target)
void Player::InitiateTrade(Player* trader)
{
if (!target || target == this)
// PlayerBot integration: minimal core hook for trade initiation
// Pattern from TradeHandler.cpp:714-715
if (!trader || m_trade || trader->m_trade)
return;
if (m_trade)
TradeCancel(true);
if (target->m_trade)
target->TradeCancel(true);
m_trade = new TradeData(this, target);
target->m_trade = new TradeData(target, this);
// Send trade status to both players
// WorldPackets::Trade::TradeStatus info;
// info.Status = TRADE_STATUS_BEGIN_TRADE;
// GetSession()->SendTradeStatus(info);
// target->GetSession()->SendTradeStatus(info);
m_trade = new TradeData(this, trader);
trader->m_trade = new TradeData(trader, this);
}
bool Player::IsSpellFitByClassAndRace(uint32 spell_id) const
+1 -1
View File
@@ -1660,7 +1660,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
Player* GetTrader() const;
TradeData* GetTradeData() const { return m_trade; }
void TradeCancel(bool sendback);
void InitiateTrade(Player* target);
void InitiateTrade(Player* trader); // PlayerBot integration: minimal core hook
CinematicMgr* GetCinematicMgr() const { return _cinematicMgr.get(); }
+25 -1
View File
@@ -1971,7 +1971,31 @@ void Map::SendObjectUpdates()
while (!_updateObjects.empty())
{
BaseEntity* obj = *_updateObjects.begin();
ASSERT(obj->IsInWorld());
// PLAYERBOT FIX: Multiple safety checks for race condition prevention
//
// Race condition scenarios:
// 1. BaseEntity::RemoveFromWorld() sets m_inWorld=false before ClearUpdateMask removes from set
// 2. Bot marked for removal (SetDestroyedObject) but still in _updateObjects due to re-add
// 3. Object freed but memory not yet overwritten - partial corruption
//
// Check 1: Skip objects not in world
if (!obj->IsInWorld())
{
TC_LOG_DEBUG("maps", "Map::SendObjectUpdates: Skipping object not in world");
_updateObjects.erase(_updateObjects.begin());
continue;
}
// Check 2: Skip objects marked for destruction (prevents use-after-free)
// This catches objects that passed IsInWorld() but are being destroyed
if (obj->IsDestroyedObject())
{
TC_LOG_DEBUG("maps", "Map::SendObjectUpdates: Skipping destroyed object");
_updateObjects.erase(_updateObjects.begin());
continue;
}
_updateObjects.erase(_updateObjects.begin());
obj->BuildUpdate(update_players);
}
+28 -2
View File
@@ -171,6 +171,12 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 battlenetAccoun
ResetTimeOutTime(false);
LoginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = {};", GetAccountId()); // One-time query
}
#ifdef BUILD_PLAYERBOT
else if (is_bot)
{
m_Address = "bot";
}
#endif
_instanceConnectKey.Raw = UI64LIT(0);
}
@@ -187,7 +193,10 @@ WorldSession::~WorldSession()
{
if (m_Socket[i])
{
m_Socket[i]->CloseSocket();
#ifdef BUILD_PLAYERBOT
if (!IsBot()) // Bot sessions have nullptr sockets
#endif
m_Socket[i]->CloseSocket();
m_Socket[i].reset();
}
}
@@ -268,7 +277,11 @@ void WorldSession::SendPacket(WorldPacket const* packet, bool forced /*= false*/
if (!m_Socket[conIdx])
{
TC_LOG_ERROR("network.opcode", "Prevented sending of {} to non existent socket {} to {}", GetOpcodeNameForLogging(static_cast<OpcodeServer>(packet->GetOpcode())), uint32(conIdx), GetPlayerInfo());
#ifdef BUILD_PLAYERBOT
// Bot sessions may not have sockets, silently skip packet sending for them
if (!IsBot())
#endif
TC_LOG_ERROR("network.opcode", "Prevented sending of {} to non existent socket {} to {}", GetOpcodeNameForLogging(static_cast<OpcodeServer>(packet->GetOpcode())), uint32(conIdx), GetPlayerInfo());
return;
}
@@ -369,7 +382,12 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
///- Before we process anything:
/// If necessary, kick the player because the client didn't send anything for too long
/// (or they've been idling in character select)
/// Note: Bots don't have sockets, so skip idle check for them
#ifdef BUILD_PLAYERBOT
if (!IsBot() && IsConnectionIdle() && !HasPermission(rbac::RBAC_PERM_IGNORE_IDLE_CONNECTION) && m_Socket[CONNECTION_TYPE_REALM])
#else
if (IsConnectionIdle() && !HasPermission(rbac::RBAC_PERM_IGNORE_IDLE_CONNECTION))
#endif
m_Socket[CONNECTION_TYPE_REALM]->CloseSocket();
///- Retrieve packets from the receive queue and call the appropriate handlers
@@ -383,7 +401,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
constexpr uint32 MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE = 100;
#ifdef BUILD_PLAYERBOT
while ((m_Socket[CONNECTION_TYPE_REALM] || IsBot()) && _recvQueue.next(packet, updater))
#else
while (m_Socket[CONNECTION_TYPE_REALM] && _recvQueue.next(packet, updater))
#endif
{
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
@@ -577,7 +599,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
}
}
#ifdef BUILD_PLAYERBOT
if (!m_Socket[CONNECTION_TYPE_REALM] && !IsBot())
#else
if (!m_Socket[CONNECTION_TYPE_REALM])
#endif
return false; //Will remove this session from the world session map
}