From 873906ba11d59eb2cea33ab40d42025199b9023e Mon Sep 17 00:00:00 2001 From: Jildor Date: Tue, 19 Aug 2025 10:30:39 +0200 Subject: [PATCH] Core/Pools: Refactor PoolGroup::SpawnObject * Prevent double spawn with pools with maxlimit 1 in certain situations * Prevent infinite recursive call with specific case of nested pools (cherry picked from commit 3e6bbb827de4013578af65a343af7aa979a186b5) --- src/server/game/Pools/PoolMgr.cpp | 101 +++++++++++++++--------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/src/server/game/Pools/PoolMgr.cpp b/src/server/game/Pools/PoolMgr.cpp index daf010b176..98e4dc8b2e 100644 --- a/src/server/game/Pools/PoolMgr.cpp +++ b/src/server/game/Pools/PoolMgr.cpp @@ -289,66 +289,69 @@ void PoolGroup::RemoveOneRelation(uint32 child_pool_id) template void PoolGroup::SpawnObject(SpawnedPoolData& spawns, uint32 limit, uint64 triggerFrom) { - int count = limit - spawns.GetSpawnedObjects(poolId); - - // If triggered from some object respawn this object is still marked as spawned - // and also counted into m_SpawnedPoolAmount so we need increase count to be - // spawned by 1 + // First clear the object that triggered the respawn, if any. + // DespawnObject is responsible for decrementing the active object counter. if (triggerFrom) - ++count; + DespawnObject(spawns, triggerFrom); - if (count > 0) + int32 count = limit - spawns.GetSpawnedObjects(poolId); + if (count <= 0) + return; + + PoolObjectList candidates; + candidates.reserve(EqualChanced.size() + ExplicitlyChanced.size()); + + // Add all not already active candidates. + for (PoolObject& obj : EqualChanced) + if (!spawns.IsSpawnedObject(obj.guid)) + candidates.push_back(obj); + + for (PoolObject& obj : ExplicitlyChanced) + if (!spawns.IsSpawnedObject(obj.guid)) + candidates.push_back(obj); + + if (candidates.empty()) + return; + + PoolObjectList rolledObjects; + rolledObjects.reserve(count); + + // Attempt to select one object based on explicit chance. + if (!ExplicitlyChanced.empty()) { - PoolObjectList rolledObjects; - rolledObjects.reserve(count); - - // roll objects to be spawned - if (!ExplicitlyChanced.empty()) + float roll = rand_chance(); + for (PoolObject& candidate : candidates) { - float roll = rand_chance(); - - for (PoolObject& obj : ExplicitlyChanced) + if (candidate.chance > 0) { - roll -= obj.chance; - // Triggering object is marked as spawned at this time and can be also rolled (respawn case) - // so this need explicit check for this case - if (roll < 0 && (obj.guid == triggerFrom || !spawns.IsSpawnedObject(obj.guid))) + roll -= candidate.chance; + if (roll < 0) { - rolledObjects.push_back(obj); - break; + rolledObjects.push_back(candidate); + std::swap(candidate, candidates.back()); + candidates.pop_back(); + break; // We only roll for one chanced object. } } } - - if (!EqualChanced.empty() && rolledObjects.empty()) - { - std::copy_if(EqualChanced.begin(), EqualChanced.end(), std::back_inserter(rolledObjects), [triggerFrom, &spawns](PoolObject const& object) - { - return object.guid == triggerFrom || !spawns.IsSpawnedObject(object.guid); - }); - - Trinity::Containers::RandomResize(rolledObjects, count); - } - - // try to spawn rolled objects - for (PoolObject& obj : rolledObjects) - { - if (obj.guid == triggerFrom) - { - ReSpawn1Object(spawns, &obj); - triggerFrom = 0; - } - else - { - spawns.AddSpawn(obj.guid, poolId); - Spawn1Object(spawns, &obj); - } - } } - // One spawn one despawn no count increase - if (triggerFrom) - DespawnObject(spawns, triggerFrom); + // Fill the remaining slots with random selections from the rest of the candidates. + uint32 remainingCount = count - rolledObjects.size(); + if (remainingCount > 0 && !candidates.empty()) + { + if (candidates.size() > remainingCount) + Trinity::Containers::RandomResize(candidates, remainingCount); + + rolledObjects.insert(rolledObjects.end(), candidates.begin(), candidates.end()); + } + + // Spawn all the objects we've selected. + for (PoolObject& objToSpawn : rolledObjects) + { + spawns.AddSpawn(objToSpawn.guid, poolId); + Spawn1Object(spawns, &objToSpawn); + } } // Method that is actualy doing the spawn job on 1 creature