core improvements
This commit is contained in:
@@ -694,9 +694,20 @@ void BotAI::UpdateAI(uint32 diff)
|
||||
if (bg && (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN))
|
||||
{
|
||||
// DIAGNOSTIC: Log BG AI activation (throttled to once per 10 seconds per bot)
|
||||
static std::unordered_map<uint32, uint32> lastBGLog;
|
||||
// FIXED: Use thread-local map with automatic cleanup to prevent memory leak
|
||||
static thread_local std::unordered_map<uint32, uint32> lastBGLog;
|
||||
static thread_local uint32 lastCleanup = 0;
|
||||
uint32 botId = _bot->GetGUID().GetCounter();
|
||||
uint32 nowMs = GameTime::GetGameTimeMS();
|
||||
|
||||
// Periodic cleanup every 60 seconds to prevent memory leak
|
||||
if (nowMs - lastCleanup > 60000)
|
||||
{
|
||||
if (lastBGLog.size() > 1000)
|
||||
lastBGLog.clear();
|
||||
lastCleanup = nowMs;
|
||||
}
|
||||
|
||||
if (!lastBGLog.count(botId) || (nowMs - lastBGLog[botId] > 10000))
|
||||
{
|
||||
TC_LOG_DEBUG("module.playerbot.bg", "?? BG AI ACTIVE: Bot {} in {} (Instance: {}, Status: IN_PROGRESS)",
|
||||
@@ -1013,6 +1024,7 @@ void BotAI::UpdateStrategies(uint32 diff)
|
||||
// ========================================================================
|
||||
|
||||
std::vector<Strategy*> strategiesToCheck;
|
||||
strategiesToCheck.reserve(16); // Typical bot has <16 active strategies
|
||||
{
|
||||
std::lock_guard lock(_mutex);
|
||||
|
||||
@@ -1031,6 +1043,7 @@ void BotAI::UpdateStrategies(uint32 diff)
|
||||
// ========================================================================
|
||||
|
||||
std::vector<Strategy*> activeStrategies;
|
||||
activeStrategies.reserve(8); // Typically <8 strategies pass IsActive()
|
||||
|
||||
for (Strategy* strategy : strategiesToCheck)
|
||||
{
|
||||
@@ -1580,6 +1593,7 @@ void BotAI::OnGroupJoined(Group* group)
|
||||
// then call OnActivate() callbacks AFTER releasing the lock
|
||||
|
||||
std::vector<Strategy*> strategiesToActivate;
|
||||
strategiesToActivate.reserve(8); // Typical activation batch
|
||||
|
||||
// PHASE 1: Check strategy existence and activate - ALL UNDER ONE LOCK
|
||||
{
|
||||
@@ -1864,6 +1878,7 @@ std::vector<Strategy*> BotAI::GetActiveStrategies() const
|
||||
// If another thread requested unique_lock during that time,
|
||||
// and the calling code then tried to call GetStrategy(), DEADLOCK!
|
||||
std::vector<Strategy*> result;
|
||||
result.reserve(16); // Reserve for typical active strategy count
|
||||
{
|
||||
std::lock_guard lock(_mutex);
|
||||
|
||||
|
||||
@@ -5583,7 +5583,20 @@ void QuestCompletion::UpdateBotQuestCompletion(Player* player, uint32 diff)
|
||||
}
|
||||
|
||||
// Throttle updates - only process every 500ms
|
||||
static std::unordered_map<uint32, uint32> lastUpdateTime;
|
||||
// FIXED: Use thread_local with periodic cleanup to prevent memory leak
|
||||
static thread_local std::unordered_map<uint32, uint32> lastUpdateTime;
|
||||
static thread_local uint32 lastCleanup = 0;
|
||||
|
||||
// Periodic cleanup every 60 seconds
|
||||
if (diff > lastCleanup && lastCleanup != 0 && diff - lastCleanup > 60000)
|
||||
{
|
||||
if (lastUpdateTime.size() > 1000)
|
||||
lastUpdateTime.clear();
|
||||
lastCleanup = diff;
|
||||
}
|
||||
if (lastCleanup == 0)
|
||||
lastCleanup = diff;
|
||||
|
||||
auto& lastUpdate = lastUpdateTime[botGuid];
|
||||
lastUpdate += diff;
|
||||
|
||||
|
||||
@@ -394,6 +394,7 @@ std::vector<uint32> QuestPickup::DiscoverNearbyQuests(Player* bot, float scanRad
|
||||
return {};
|
||||
|
||||
std::vector<uint32> discoveredQuests;
|
||||
discoveredQuests.reserve(16); // Typical quest giver has <16 quests
|
||||
std::vector<QuestGiverInfo> givers = ScanForQuestGivers(bot, scanRadius);
|
||||
for (auto const& giver : givers)
|
||||
{
|
||||
@@ -417,6 +418,7 @@ std::vector<QuestGiverInfo> QuestPickup::ScanForQuestGivers(Player* bot, float s
|
||||
return {};
|
||||
|
||||
std::vector<QuestGiverInfo> foundGivers;
|
||||
foundGivers.reserve(8); // Typical area has <8 quest givers
|
||||
Position botPos = bot->GetPosition();
|
||||
|
||||
// DEADLOCK FIX: Use lock-free spatial grid with snapshots
|
||||
@@ -691,6 +693,7 @@ std::vector<std::string> QuestPickup::GetEligibilityIssues(uint32 questId, Playe
|
||||
std::vector<uint32> QuestPickup::FilterQuests(const std::vector<uint32>& questIds, Player* bot, const QuestPickupFilter& filter)
|
||||
{
|
||||
std::vector<uint32> filteredQuests;
|
||||
filteredQuests.reserve(questIds.size()); // Reserve based on input size
|
||||
|
||||
if (!bot)
|
||||
return filteredQuests;
|
||||
@@ -750,6 +753,7 @@ std::vector<uint32> QuestPickup::PrioritizeQuests(const std::vector<uint32>& que
|
||||
return questIds;
|
||||
|
||||
std::vector<std::pair<uint32, float>> questPriorities;
|
||||
questPriorities.reserve(questIds.size()); // Reserve based on input
|
||||
|
||||
for (uint32 questId : questIds)
|
||||
{
|
||||
@@ -762,8 +766,7 @@ std::vector<uint32> QuestPickup::PrioritizeQuests(const std::vector<uint32>& que
|
||||
[](const auto& a, const auto& b) { return a.second > b.second; });
|
||||
|
||||
std::vector<uint32> prioritizedQuests;
|
||||
for (auto const& [questId, priority] : questPriorities)
|
||||
prioritizedQuests.push_back(questId);
|
||||
prioritizedQuests.reserve(questPriorities.size()); // Reserve for output
|
||||
return prioritizedQuests;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,11 @@ void BotClusterDetector::Update(uint32 diff)
|
||||
for (size_t idx : eligible)
|
||||
{
|
||||
Player* bot = bots[idx];
|
||||
|
||||
// Validate bot is still valid before accessing
|
||||
if (!bot || !bot->IsInWorld() || !bot->IsAlive())
|
||||
continue;
|
||||
|
||||
ObjectGuid guid = bot->GetGUID();
|
||||
|
||||
// Skip if recently dispersed
|
||||
@@ -176,18 +181,33 @@ void BotClusterDetector::Update(uint32 diff)
|
||||
|
||||
bool BotClusterDetector::IsEligibleForDispersal(Player* bot) const
|
||||
{
|
||||
if (!bot || !bot->IsInWorld())
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
// Re-validate - bot could have become invalid since we got the list
|
||||
if (!bot->IsInWorld() || !bot->IsAlive())
|
||||
return false;
|
||||
|
||||
// Never disperse human players
|
||||
WorldSession* session = bot->GetSession();
|
||||
if (!session || !session->IsBot())
|
||||
if (!session)
|
||||
return false;
|
||||
|
||||
// Double-check IsInWorld after getting session - prevents crash on stale pointer
|
||||
if (!bot->IsInWorld())
|
||||
return false;
|
||||
|
||||
if (!session->IsBot())
|
||||
return false;
|
||||
|
||||
// Never disperse bots in combat
|
||||
if (bot->IsInCombat())
|
||||
return false;
|
||||
|
||||
// Re-validate again before map checks - bot could have become invalid
|
||||
if (!bot->IsInWorld())
|
||||
return false;
|
||||
|
||||
// Never disperse bots in BG or dungeon/raid
|
||||
if (bot->InBattleground())
|
||||
return false;
|
||||
|
||||
@@ -1097,6 +1097,7 @@ void DoubleBufferedSpatialGrid::SwapBuffers()
|
||||
_totalQueries.fetch_add(1, ::std::memory_order_relaxed);
|
||||
|
||||
::std::vector<DoubleBufferedSpatialGrid::CreatureSnapshot> results;
|
||||
results.reserve(64); // Pre-reserve for typical nearby creature count
|
||||
auto const& readBuffer = GetReadBuffer();
|
||||
|
||||
// Get all cells within radius
|
||||
@@ -1139,6 +1140,7 @@ void DoubleBufferedSpatialGrid::SwapBuffers()
|
||||
_totalQueries.fetch_add(1, ::std::memory_order_relaxed);
|
||||
|
||||
::std::vector<PlayerSnapshot> results;
|
||||
results.reserve(32); // Pre-reserve for typical nearby player count
|
||||
auto const& readBuffer = GetReadBuffer();
|
||||
auto cells = GetCellsInRadius(pos, radius);
|
||||
float radiusSq = radius * radius;
|
||||
@@ -1177,6 +1179,7 @@ void DoubleBufferedSpatialGrid::SwapBuffers()
|
||||
_totalQueries.fetch_add(1, ::std::memory_order_relaxed);
|
||||
|
||||
::std::vector<GameObjectSnapshot> results;
|
||||
results.reserve(32); // Pre-reserve for typical nearby game object count
|
||||
auto const& readBuffer = GetReadBuffer();
|
||||
auto cells = GetCellsInRadius(pos, radius);
|
||||
float radiusSq = radius * radius;
|
||||
@@ -1215,6 +1218,7 @@ void DoubleBufferedSpatialGrid::SwapBuffers()
|
||||
_totalQueries.fetch_add(1, ::std::memory_order_relaxed);
|
||||
|
||||
::std::vector<AreaTriggerSnapshot> results;
|
||||
results.reserve(16); // Pre-reserve for typical nearby area trigger count
|
||||
auto const& readBuffer = GetReadBuffer();
|
||||
auto cells = GetCellsInRadius(pos, radius);
|
||||
float radiusSq = radius * radius;
|
||||
@@ -1253,6 +1257,7 @@ void DoubleBufferedSpatialGrid::SwapBuffers()
|
||||
_totalQueries.fetch_add(1, ::std::memory_order_relaxed);
|
||||
|
||||
::std::vector<DynamicObjectSnapshot> results;
|
||||
results.reserve(8); // Pre-reserve for typical nearby dynamic object count
|
||||
auto const& readBuffer = GetReadBuffer();
|
||||
auto cells = GetCellsInRadius(pos, radius);
|
||||
float radiusSq = radius * radius;
|
||||
|
||||
@@ -299,7 +299,7 @@ void HousingMap::SpawnPlotGameObjects()
|
||||
// fragment, SpellForVisuals, SpellXSpellVisualID) BEFORE the CREATE_OBJECT
|
||||
// packet is sent. The client needs FHousingPlotAreaTrigger_C and
|
||||
// DecalPropertiesId=621 in the initial create to render the plot border decal.
|
||||
AreaTrigger* plotAt = AreaTrigger::CreateStaticAreaTrigger({ .Id = 37358, .IsCustom = false }, this, atPos, -1, false);
|
||||
AreaTrigger* plotAt = AreaTrigger::CreateStaticAreaTrigger({ .Id = 37358, .IsCustom = false }, this, atPos, -1);
|
||||
if (plotAt)
|
||||
{
|
||||
PhasingHandler::InitDbPhaseShift(plotAt->GetPhaseShift(), PHASE_USE_FLAGS_ALWAYS_VISIBLE, 0, 0);
|
||||
|
||||
@@ -38,6 +38,37 @@ uint64 GroundValidator::MakeCacheKey(uint32 mapId, float x, float y)
|
||||
return (static_cast<uint64>(mapId) << 32) | (static_cast<uint64>(gridX) << 16) | gridY;
|
||||
}
|
||||
|
||||
void GroundValidator::ClearCache()
|
||||
{
|
||||
uint32 currentTime = GameTime::GetGameTimeMS();
|
||||
|
||||
// Remove expired entries
|
||||
for (auto it = _heightCache.begin(); it != _heightCache.end(); )
|
||||
{
|
||||
if ((currentTime - it->second.timestamp) > CACHE_LIFETIME_MS)
|
||||
it = _heightCache.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
// If cache is still too large, clear oldest entries
|
||||
if (_heightCache.size() > MAX_CACHE_SIZE)
|
||||
{
|
||||
// Clear half the cache
|
||||
size_t toRemove = _heightCache.size() - (MAX_CACHE_SIZE / 2);
|
||||
for (size_t i = 0; i < toRemove && !_heightCache.empty(); ++i)
|
||||
{
|
||||
auto oldestIt = _heightCache.begin();
|
||||
for (auto it = _heightCache.begin(); it != _heightCache.end(); ++it)
|
||||
{
|
||||
if (it->second.timestamp < oldestIt->second.timestamp)
|
||||
oldestIt = it;
|
||||
}
|
||||
_heightCache.erase(oldestIt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float GroundValidator::GetGroundHeight(Unit const* unit)
|
||||
{
|
||||
if (!unit || !unit->IsInWorld())
|
||||
@@ -67,6 +98,14 @@ float GroundValidator::GetGroundHeight(Unit const* unit)
|
||||
cache.timestamp = currentTime;
|
||||
_heightCache[cacheKey] = cache;
|
||||
|
||||
// Periodic cleanup when cache gets large
|
||||
static uint32 lastCleanup = 0;
|
||||
if (_heightCache.size() > MAX_CACHE_SIZE && (currentTime - lastCleanup) > 60000)
|
||||
{
|
||||
ClearCache();
|
||||
lastCleanup = currentTime;
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
@@ -186,8 +225,3 @@ bool GroundValidator::IsUnsafeTerrain(Unit const* unit)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GroundValidator::ClearCache()
|
||||
{
|
||||
_heightCache.clear();
|
||||
}
|
||||
|
||||
@@ -46,12 +46,13 @@ public:
|
||||
static bool IsOnBridge(Unit const* unit);
|
||||
static bool IsUnsafeTerrain(Unit const* unit);
|
||||
|
||||
void ClearCache();
|
||||
static void ClearCache();
|
||||
|
||||
private:
|
||||
static constexpr float VOID_HEIGHT = -500.0f;
|
||||
static constexpr float BOT_MAX_FALL_DISTANCE = 50.0f;
|
||||
static constexpr uint32 CACHE_LIFETIME_MS = 5000;
|
||||
static constexpr size_t MAX_CACHE_SIZE = 10000;
|
||||
|
||||
static std::unordered_map<uint64, GroundHeightCache> _heightCache;
|
||||
|
||||
|
||||
@@ -63,6 +63,47 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// npc_scarlet_corpse_49340
|
||||
class npc_scarlet_corpse_49340 : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_scarlet_corpse_49340() : CreatureScript("npc_scarlet_corpse_49340") {}
|
||||
|
||||
struct npc_scarlet_corpse_49340AI : public ScriptedAI
|
||||
{
|
||||
npc_scarlet_corpse_49340AI(Creature* creature) : ScriptedAI(creature)
|
||||
{
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
}
|
||||
|
||||
void SpellHit(WorldObject* caster, SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
if (Player* player = caster->ToPlayer())
|
||||
if (player->GetQuestStatus(26800) == QUEST_STATUS_INCOMPLETE)
|
||||
if (Creature* darnell = GetDarnell(player))
|
||||
{
|
||||
darnell->AI()->SetGUID(me->GetGUID(), me->GetEntry());
|
||||
darnell->AI()->DoAction(1);
|
||||
}
|
||||
}
|
||||
|
||||
Creature* GetDarnell(Player* player)
|
||||
{
|
||||
for (Unit::ControlList::const_iterator itr = player->m_Controlled.begin(); itr != player->m_Controlled.end(); ++itr)
|
||||
if ((*itr)->GetEntry() == 49337)
|
||||
return (*itr)->ToCreature();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_scarlet_corpse_49340AI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_tirisfal_glades()
|
||||
{
|
||||
// Playerchoice
|
||||
@@ -70,4 +111,7 @@ void AddSC_tirisfal_glades()
|
||||
|
||||
// Quest
|
||||
new quest_a_legend_you_can_hold();
|
||||
|
||||
//Npc
|
||||
new npc_scarlet_corpse_49340();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user