TrinityCore
Loading...
Searching...
No Matches
PoolMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "PoolMgr.h"
19#include "Containers.h"
20#include "Creature.h"
21#include "DatabaseEnv.h"
22#include "GameObject.h"
23#include "Log.h"
24#include "Map.h"
25#include "MapUtils.h"
26#include "ObjectMgr.h"
27#include <fmt/ranges.h>
28
29PoolObject::PoolObject(uint64 _guid, float _chance) : guid(_guid), chance(std::fabs(_chance))
30{
31}
32
34// template class SpawnedPoolData
35
36SpawnedPoolData::SpawnedPoolData(Map* owner) : mOwner(owner) { }
38
39// Method that tell amount spawned objects/subpools
41{
42 SpawnedPoolPools::const_iterator itr = mSpawnedPools.find(pool_id);
43 return itr != mSpawnedPools.end() ? itr->second : 0;
44}
45
46// Method that tell if a creature is spawned currently
47template<>
48TC_GAME_API bool SpawnedPoolData::IsSpawnedObject<Creature>(uint64 db_guid) const
49{
50 return mSpawnedCreatures.find(db_guid) != mSpawnedCreatures.end();
51}
52
53// Method that tell if a gameobject is spawned currently
54template<>
55TC_GAME_API bool SpawnedPoolData::IsSpawnedObject<GameObject>(uint64 db_guid) const
56{
57 return mSpawnedGameobjects.find(db_guid) != mSpawnedGameobjects.end();
58}
59
60// Method that tell if a pool is spawned currently
61template<>
62TC_GAME_API bool SpawnedPoolData::IsSpawnedObject<Pool>(uint64 sub_pool_id) const
63{
64 return mSpawnedPools.find(sub_pool_id) != mSpawnedPools.end();
65}
66
67bool SpawnedPoolData::IsSpawnedObject(SpawnObjectType type, uint64 db_guid_or_pool_id) const
68{
69 switch (type)
70 {
72 return IsSpawnedObject<Creature>(db_guid_or_pool_id);
74 return IsSpawnedObject<GameObject>(db_guid_or_pool_id);
75 default:
76 ABORT_MSG("Invalid spawn type %u passed to SpawnedPoolData::IsSpawnedObject (with spawnId " UI64FMTD ")", uint32(type), db_guid_or_pool_id);
77 }
78}
79
80template<>
81void SpawnedPoolData::AddSpawn<Creature>(uint64 db_guid, uint32 pool_id)
82{
83 mSpawnedCreatures.insert(db_guid);
84 ++mSpawnedPools[pool_id];
85}
86
87template<>
88void SpawnedPoolData::AddSpawn<GameObject>(uint64 db_guid, uint32 pool_id)
89{
90 mSpawnedGameobjects.insert(db_guid);
91 ++mSpawnedPools[pool_id];
92}
93
94template<>
95void SpawnedPoolData::AddSpawn<Pool>(uint64 sub_pool_id, uint32 pool_id)
96{
97 mSpawnedPools[sub_pool_id] = 0;
98 ++mSpawnedPools[pool_id];
99}
100
101template<>
102void SpawnedPoolData::RemoveSpawn<Creature>(uint64 db_guid, uint32 pool_id)
104 mSpawnedCreatures.erase(db_guid);
105 uint32& val = mSpawnedPools[pool_id];
106 if (val > 0)
107 --val;
108}
109
110template<>
111void SpawnedPoolData::RemoveSpawn<GameObject>(uint64 db_guid, uint32 pool_id)
112{
113 mSpawnedGameobjects.erase(db_guid);
114 uint32& val = mSpawnedPools[pool_id];
115 if (val > 0)
116 --val;
117}
118
119template<>
120void SpawnedPoolData::RemoveSpawn<Pool>(uint64 sub_pool_id, uint32 pool_id)
121{
122 mSpawnedPools.erase(sub_pool_id);
123 uint32& val = mSpawnedPools[pool_id];
124 if (val > 0)
125 --val;
126}
127
129// Methods of template class PoolGroup
130
131template <class T>
133{
134}
135
136template <class T>
137PoolGroup<T>::~PoolGroup() = default;
138
139template <class T>
141{
142 return isEmpty();
143}
144
145template <>
147{
148 for (PoolObject const& explicitlyChanced : ExplicitlyChanced)
149 if (!sPoolMgr->IsEmpty(explicitlyChanced.guid))
150 return false;
151
152 for (PoolObject const& equalChanced : EqualChanced)
153 if (!sPoolMgr->IsEmpty(equalChanced.guid))
154 return false;
155
156 return true;
157}
158
159// Method to add a gameobject/creature guid to the proper list depending on pool type and chance value
160template <class T>
161void PoolGroup<T>::AddEntry(PoolObject& poolitem, uint32 maxentries)
162{
163 if (poolitem.chance != 0 && maxentries == 1)
164 ExplicitlyChanced.push_back(poolitem);
165 else
166 EqualChanced.push_back(poolitem);
167}
168
169// Method to check the chances are proper in this object pool
170template <class T>
172{
173 if (EqualChanced.empty())
174 {
175 float chance = 0;
176 for (uint32 i = 0; i < ExplicitlyChanced.size(); ++i)
177 chance += ExplicitlyChanced[i].chance;
178 if (chance != 100 && chance != 0)
179 return false;
180 }
181 return true;
182}
183
184// Main method to despawn a creature or gameobject in a pool
185template<class T>
186void PoolGroup<T>::DespawnObject(SpawnedPoolData& spawns, uint64 guid, bool alwaysDeleteRespawnTime)
187{
188 // if spawned
189 if (spawns.IsSpawnedObject<T>(guid))
190 {
191 Despawn1Object(spawns, guid, alwaysDeleteRespawnTime);
192 spawns.RemoveSpawn<T>(guid, poolId);
193 }
194 else if (alwaysDeleteRespawnTime)
195 RemoveRespawnTimeFromDB(spawns, guid);
196}
197
198template <class T>
199void PoolGroup<T>::DespawnAllObjects(SpawnedPoolData& spawns, bool alwaysDeleteRespawnTime)
200{
201 for (PoolObject& pooledObject : EqualChanced)
202 DespawnObject(spawns, pooledObject.guid, alwaysDeleteRespawnTime);
203
204 for (PoolObject& pooledObject : ExplicitlyChanced)
205 DespawnObject(spawns, pooledObject.guid, alwaysDeleteRespawnTime);
206}
207
208// Method that is actualy doing the removal job on one creature
209template<>
210void PoolGroup<Creature>::Despawn1Object(SpawnedPoolData& spawns, uint64 guid, bool alwaysDeleteRespawnTime, bool saveRespawnTime)
211{
212 auto creatureBounds = spawns.GetMap()->GetCreatureBySpawnIdStore().equal_range(guid);
213 for (auto itr = creatureBounds.first; itr != creatureBounds.second;)
214 {
215 Creature* creature = itr->second;
216 ++itr;
217 // For dynamic spawns, save respawn time here
218 if (saveRespawnTime && !creature->GetRespawnCompatibilityMode())
219 creature->SaveRespawnTime();
220 creature->AddObjectToRemoveList();
221 }
222
223 if (alwaysDeleteRespawnTime)
224 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, guid, nullptr, true);
225}
226
227// Same on one gameobject
228template<>
229void PoolGroup<GameObject>::Despawn1Object(SpawnedPoolData& spawns, uint64 guid, bool alwaysDeleteRespawnTime, bool saveRespawnTime)
230{
231 auto gameobjectBounds = spawns.GetMap()->GetGameObjectBySpawnIdStore().equal_range(guid);
232 for (auto itr = gameobjectBounds.first; itr != gameobjectBounds.second;)
233 {
234 GameObject* go = itr->second;
235 ++itr;
236
237 // For dynamic spawns, save respawn time here
238 if (saveRespawnTime && !go->GetRespawnCompatibilityMode())
239 go->SaveRespawnTime();
241 }
242
243 if (alwaysDeleteRespawnTime)
244 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, guid, nullptr, true);
245}
246
247// Same on one pool
248template<>
249void PoolGroup<Pool>::Despawn1Object(SpawnedPoolData& spawns, uint64 child_pool_id, bool alwaysDeleteRespawnTime, bool /*saveRespawnTime*/)
250{
251 sPoolMgr->DespawnPool(spawns, child_pool_id, alwaysDeleteRespawnTime);
252}
253
254// Method for a pool only to remove any found record causing a circular dependency loop
255template<>
257{
258 for (PoolObjectList::iterator itr = ExplicitlyChanced.begin(); itr != ExplicitlyChanced.end(); ++itr)
259 {
260 if (itr->guid == child_pool_id)
261 {
262 ExplicitlyChanced.erase(itr);
263 break;
264 }
265 }
266 for (PoolObjectList::iterator itr = EqualChanced.begin(); itr != EqualChanced.end(); ++itr)
267 {
268 if (itr->guid == child_pool_id)
269 {
270 EqualChanced.erase(itr);
271 break;
272 }
273 }
274}
275
276template <class T>
278{
279 // First clear the object that triggered the respawn, if any.
280 // DespawnObject is responsible for decrementing the active object counter.
281 if (triggerFrom)
282 DespawnObject(spawns, triggerFrom);
283
284 int32 count = limit - spawns.GetSpawnedObjects(poolId);
285 if (count <= 0)
286 return;
287
288 PoolObjectList candidates;
289 candidates.reserve(EqualChanced.size() + ExplicitlyChanced.size());
290
291 // Add all not already active candidates.
292 for (PoolObject& obj : EqualChanced)
293 if (!spawns.IsSpawnedObject<T>(obj.guid))
294 candidates.push_back(obj);
295
296 for (PoolObject& obj : ExplicitlyChanced)
297 if (!spawns.IsSpawnedObject<T>(obj.guid))
298 candidates.push_back(obj);
299
300 if (candidates.empty())
301 return;
302
303 PoolObjectList rolledObjects;
304 rolledObjects.reserve(count);
305
306 // Attempt to select one object based on explicit chance.
307 if (!ExplicitlyChanced.empty())
308 {
309 float roll = rand_chance();
310 for (PoolObject& candidate : candidates)
311 {
312 if (candidate.chance > 0)
313 {
314 roll -= candidate.chance;
315 if (roll < 0)
316 {
317 rolledObjects.push_back(candidate);
318 std::swap(candidate, candidates.back());
319 candidates.pop_back();
320 break; // We only roll for one chanced object.
321 }
322 }
323 }
324 }
325
326 // Fill the remaining slots with random selections from the rest of the candidates.
327 uint32 remainingCount = count - rolledObjects.size();
328 if (remainingCount > 0 && !candidates.empty())
329 {
330 if (candidates.size() > remainingCount)
331 Trinity::Containers::RandomResize(candidates, remainingCount);
332
333 rolledObjects.insert(rolledObjects.end(), candidates.begin(), candidates.end());
334 }
335
336 // Spawn all the objects we've selected.
337 for (PoolObject& objToSpawn : rolledObjects)
338 {
339 spawns.AddSpawn<T>(objToSpawn.guid, poolId);
340 Spawn1Object(spawns, &objToSpawn);
341 }
342}
343
344// Method that is actualy doing the spawn job on 1 creature
345template <>
347{
348 if (CreatureData const* data = sObjectMgr->GetCreatureData(obj->guid))
349 {
350 // Spawn if necessary (loaded grids only)
351 // We use spawn coords to spawn
352 if (spawns.GetMap()->IsGridLoaded(data->spawnPoint))
354 }
355}
356
357// Same for 1 gameobject
358template <>
360{
361 if (GameObjectData const* data = sObjectMgr->GetGameObjectData(obj->guid))
362 {
363 // Spawn if necessary (loaded grids only)
364 // We use current coords to unspawn, not spawn coords since creature can have changed grid
365 if (spawns.GetMap()->IsGridLoaded(data->spawnPoint))
366 if (GameObject* go = GameObject::CreateGameObjectFromDB(obj->guid, spawns.GetMap(), false))
367 if (go->isSpawnedByDefault())
368 if (!spawns.GetMap()->AddToMap(go))
369 delete go;
370 }
371}
372
373// Same for 1 pool
374template <>
376{
377 sPoolMgr->SpawnPool(spawns, obj->guid);
378}
379
380template <>
382{
383 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, guid, nullptr, true);
384}
385
386template <>
388{
389 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, guid, nullptr, true);
390}
391
392template <>
394
396// Methods of class PoolMgr
397
398PoolMgr::PoolMgr() = default;
399PoolMgr::~PoolMgr() = default;
400
402{
403 mGameobjectSearchMap.clear();
404 mCreatureSearchMap.clear();
405}
406
408{
409 static PoolMgr instance;
410 return &instance;
411}
412
414{
415 // Pool templates
416 {
417 uint32 oldMSTime = getMSTime();
418
419 QueryResult result = WorldDatabase.Query("SELECT entry, max_limit FROM pool_template");
420 if (!result)
421 {
422 mPoolTemplate.clear();
423 TC_LOG_INFO("server.loading", ">> Loaded 0 object pools. DB table `pool_template` is empty.");
424 return;
425 }
426
427 uint32 count = 0;
428 do
429 {
430 Field* fields = result->Fetch();
431
432 uint32 pool_id = fields[0].GetUInt32();
433
434 PoolTemplateData& pPoolTemplate = mPoolTemplate[pool_id];
435 pPoolTemplate.MaxLimit = fields[1].GetUInt32();
436 pPoolTemplate.MapId = -1;
437
438 ++count;
439 }
440 while (result->NextRow());
441
442 TC_LOG_INFO("server.loading", ">> Loaded {} objects pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
443 }
444
445 // Creatures
446
447 TC_LOG_INFO("server.loading", "Loading Creatures Pooling Data...");
448 {
449 uint32 oldMSTime = getMSTime();
450
451 // 1 2 3
452 QueryResult result = WorldDatabase.Query("SELECT spawnId, poolSpawnId, chance FROM pool_members WHERE type = 0");
453
454 if (!result)
455 {
456 TC_LOG_INFO("server.loading", ">> Loaded 0 creatures in pools. DB table `pool_creature` is empty.");
457 }
458 else
459 {
460 uint32 count = 0;
461 do
462 {
463 Field* fields = result->Fetch();
464
465 uint64 guid = fields[0].GetUInt64();
466 uint32 pool_id = fields[1].GetUInt32();
467 float chance = fields[2].GetFloat();
468
469 CreatureData const* data = sObjectMgr->GetCreatureData(guid);
470 if (!data)
471 {
472 TC_LOG_ERROR("sql.sql", "`pool_creature` has a non existing creature spawn (GUID: {}) defined for pool id ({}), skipped.", guid, pool_id);
473 continue;
474 }
475 auto it = mPoolTemplate.find(pool_id);
476 if (it == mPoolTemplate.end())
477 {
478 TC_LOG_ERROR("sql.sql", "`pool_creature` pool id ({}) is not in `pool_template`, skipped.", pool_id);
479 continue;
480 }
481 if (chance < 0 || chance > 100)
482 {
483 TC_LOG_ERROR("sql.sql", "`pool_creature` has an invalid chance ({}) for creature guid ({}) in pool id ({}), skipped.", chance, guid, pool_id);
484 continue;
485 }
486 PoolTemplateData* pPoolTemplate = &mPoolTemplate[pool_id];
487 if (pPoolTemplate->MapId == -1)
488 pPoolTemplate->MapId = int32(data->mapId);
489
490 if (pPoolTemplate->MapId != int32(data->mapId))
491 {
492 TC_LOG_ERROR("sql.sql", "`pool_creature` has creature spawns on multiple different maps for creature guid ({}) in pool id ({}), skipped.", guid, pool_id);
493 continue;
494 }
495
496 PoolObject plObject = PoolObject(guid, chance);
497 PoolGroup<Creature>& cregroup = mPoolCreatureGroups[pool_id];
498 cregroup.SetPoolId(pool_id);
499 cregroup.AddEntry(plObject, pPoolTemplate->MaxLimit);
500 SearchPair p(guid, pool_id);
501 mCreatureSearchMap.insert(p);
502
503 ++count;
504 }
505 while (result->NextRow());
506
507 TC_LOG_INFO("server.loading", ">> Loaded {} creatures in pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
508 }
509 }
510
511 // Gameobjects
512
513 TC_LOG_INFO("server.loading", "Loading Gameobject Pooling Data...");
514 {
515 uint32 oldMSTime = getMSTime();
516
517 // 1 2 3
518 QueryResult result = WorldDatabase.Query("SELECT spawnId, poolSpawnId, chance FROM pool_members WHERE type = 1");
519
520 if (!result)
521 {
522 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobjects in pools. DB table `pool_gameobject` is empty.");
523 }
524 else
525 {
526 uint32 count = 0;
527 do
528 {
529 Field* fields = result->Fetch();
530
531 uint64 guid = fields[0].GetUInt64();
532 uint32 pool_id = fields[1].GetUInt32();
533 float chance = fields[2].GetFloat();
534
535 GameObjectData const* data = sObjectMgr->GetGameObjectData(guid);
536 if (!data)
537 {
538 TC_LOG_ERROR("sql.sql", "`pool_gameobject` has a non existing gameobject spawn (GUID: {}) defined for pool id ({}), skipped.", guid, pool_id);
539 continue;
540 }
541
542 auto it = mPoolTemplate.find(pool_id);
543 if (it == mPoolTemplate.end())
544 {
545 TC_LOG_ERROR("sql.sql", "`pool_gameobject` pool id ({}) is not in `pool_template`, skipped.", pool_id);
546 continue;
547 }
548
549 if (chance < 0 || chance > 100)
550 {
551 TC_LOG_ERROR("sql.sql", "`pool_gameobject` has an invalid chance ({}) for gameobject guid ({}) in pool id ({}), skipped.", chance, guid, pool_id);
552 continue;
553 }
554
555 PoolTemplateData* pPoolTemplate = &mPoolTemplate[pool_id];
556 if (pPoolTemplate->MapId == -1)
557 pPoolTemplate->MapId = int32(data->mapId);
558
559 if (pPoolTemplate->MapId != int32(data->mapId))
560 {
561 TC_LOG_ERROR("sql.sql", "`pool_gameobject` has gameobject spawns on multiple different maps for gameobject guid ({}) in pool id ({}), skipped.", guid, pool_id);
562 continue;
563 }
564
565 PoolObject plObject = PoolObject(guid, chance);
567 gogroup.SetPoolId(pool_id);
568 gogroup.AddEntry(plObject, pPoolTemplate->MaxLimit);
569 SearchPair p(guid, pool_id);
570 mGameobjectSearchMap.insert(p);
571
572 ++count;
573 }
574 while (result->NextRow());
575
576 TC_LOG_INFO("server.loading", ">> Loaded {} gameobject in pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
577 }
578 }
579
580 // Pool of pools
581
582 TC_LOG_INFO("server.loading", "Loading Mother Pooling Data...");
583 {
584 uint32 oldMSTime = getMSTime();
585
586 // 1 2 3
587 QueryResult result = WorldDatabase.Query("SELECT spawnId, poolSpawnId, chance FROM pool_members WHERE type = 2");
588
589 if (!result)
590 {
591 TC_LOG_INFO("server.loading", ">> Loaded 0 pools in pools");
592 }
593 else
594 {
595 uint32 count = 0;
596 do
597 {
598 Field* fields = result->Fetch();
599
600 uint32 child_pool_id = fields[0].GetUInt64();
601 uint32 mother_pool_id = fields[1].GetUInt32();
602 float chance = fields[2].GetFloat();
603
604 {
605 auto it = mPoolTemplate.find(mother_pool_id);
606 if (it == mPoolTemplate.end())
607 {
608 TC_LOG_ERROR("sql.sql", "`pool_pool` mother_pool id ({}) is not in `pool_template`, skipped.", mother_pool_id);
609 continue;
610 }
611 }
612 {
613 auto it = mPoolTemplate.find(child_pool_id);
614 if (it == mPoolTemplate.end())
615 {
616 TC_LOG_ERROR("sql.sql", "`pool_pool` included pool_id ({}) is not in `pool_template`, skipped.", child_pool_id);
617 continue;
618 }
619 }
620 if (mother_pool_id == child_pool_id)
621 {
622 TC_LOG_ERROR("sql.sql", "`pool_pool` pool_id ({}) includes itself, dead-lock detected, skipped.", child_pool_id);
623 continue;
624 }
625 if (chance < 0 || chance > 100)
626 {
627 TC_LOG_ERROR("sql.sql", "`pool_pool` has an invalid chance ({}) for pool id ({}) in mother pool id ({}), skipped.", chance, child_pool_id, mother_pool_id);
628 continue;
629 }
630 PoolTemplateData* pPoolTemplateMother = &mPoolTemplate[mother_pool_id];
631 PoolObject plObject = PoolObject(child_pool_id, chance);
632 PoolGroup<Pool>& plgroup = mPoolPoolGroups[mother_pool_id];
633 plgroup.SetPoolId(mother_pool_id);
634 plgroup.AddEntry(plObject, pPoolTemplateMother->MaxLimit);
635 SearchPair p(child_pool_id, mother_pool_id);
636 mPoolSearchMap.insert(p);
637
638 ++count;
639 }
640 while (result->NextRow());
641
642 // Now check for circular reference
643 // All pool_ids are in pool_template
644 for (auto const& it : mPoolTemplate)
645 {
646 std::set<uint32> checkedPools;
647 for (SearchMap::iterator poolItr = mPoolSearchMap.find(it.first); poolItr != mPoolSearchMap.end(); poolItr = mPoolSearchMap.find(poolItr->second))
648 {
649 if (mPoolTemplate[poolItr->first].MapId != -1)
650 {
651 if (mPoolTemplate[poolItr->second].MapId == -1)
652 mPoolTemplate[poolItr->second].MapId = mPoolTemplate[poolItr->first].MapId;
653
654 if (mPoolTemplate[poolItr->second].MapId != mPoolTemplate[poolItr->first].MapId)
655 {
656 TC_LOG_ERROR("sql.sql", "`pool_pool` has child pools on multiple maps in pool id ({}), skipped.", poolItr->second);
657 mPoolPoolGroups[poolItr->second].RemoveOneRelation(poolItr->first);
658 mPoolSearchMap.erase(poolItr);
659 --count;
660 break;
661 }
662 }
663
664 checkedPools.insert(poolItr->first);
665 if (checkedPools.find(poolItr->second) != checkedPools.end())
666 {
667 TC_LOG_ERROR("sql.sql", "The pool(s) {} create(s) a circular reference, which can cause the server to freeze.\nRemoving the last link between mother pool {} and child pool {}",
668 fmt::join(checkedPools, " "), poolItr->first, poolItr->second);
669 mPoolPoolGroups[poolItr->second].RemoveOneRelation(poolItr->first);
670 mPoolSearchMap.erase(poolItr);
671 --count;
672 break;
673 }
674 }
675 }
676
677 TC_LOG_INFO("server.loading", ">> Loaded {} pools in mother pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
678 }
679 }
680
681 for (auto const& [poolId, templateData] : mPoolTemplate)
682 {
683 if (IsEmpty(poolId))
684 {
685 TC_LOG_ERROR("sql.sql", "Pool Id {} is empty (has no creatures and no gameobects and either no child pools or child pools are all empty. The pool will not be spawned", poolId);
686 continue;
687 }
688 ASSERT(templateData.MapId != -1);
689 }
690
691 // The initialize method will spawn all pools not in an event and not in another pool, this is why there is 2 left joins with 2 null checks
692 TC_LOG_INFO("server.loading", "Starting objects pooling system...");
693 {
694 uint32 oldMSTime = getMSTime();
695
696 QueryResult result = WorldDatabase.Query("SELECT DISTINCT pool_template.entry, pool_members.spawnId, pool_members.poolSpawnId FROM pool_template"
697 " LEFT JOIN game_event_pool ON pool_template.entry = game_event_pool.pool_entry"
698 " LEFT JOIN pool_members ON pool_members.type = 2 AND pool_template.entry = pool_members.spawnId WHERE game_event_pool.pool_entry IS NULL");
699
700 if (!result)
701 {
702 TC_LOG_INFO("server.loading", ">> Pool handling system initialized, 0 pools spawned.");
703 }
704 else
705 {
706 uint32 count = 0;
707 do
708 {
709 Field* fields = result->Fetch();
710 uint32 pool_entry = fields[0].GetUInt32();
711 uint32 pool_pool_id = fields[1].GetUInt64();
712
713 if (IsEmpty(pool_entry))
714 continue;
715
716 if (!CheckPool(pool_entry))
717 {
718 if (pool_pool_id)
719 // The pool is a child pool in pool_pool table. Ideally we should remove it from the pool handler to ensure it never gets spawned,
720 // however that could recursively invalidate entire chain of mother pools. It can be done in the future but for now we'll do nothing.
721 TC_LOG_ERROR("sql.sql", "Pool Id {} has no equal chance pooled entites defined and explicit chance sum is not 100. This broken pool is a child pool of Id {} and cannot be safely removed.", pool_entry, fields[2].GetUInt32());
722 else
723 TC_LOG_ERROR("sql.sql", "Pool Id {} has no equal chance pooled entites defined and explicit chance sum is not 100. The pool will not be spawned.", pool_entry);
724 continue;
725 }
726
727 // Don't spawn child pools, they are spawned recursively by their parent pools
728 if (!pool_pool_id)
729 {
730 mAutoSpawnPoolsPerMap[mPoolTemplate[pool_entry].MapId].push_back(pool_entry);
731 count++;
732 }
733 }
734 while (result->NextRow());
735
736 TC_LOG_DEBUG("pool", "Pool handling system initialized, {} pools will be spawned by default in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
737
738 }
739 }
740}
741
742// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
743// If it's same, the creature is respawned only (added back to map)
744template<>
745void PoolMgr::SpawnPool<Creature>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid)
746{
747 auto it = mPoolCreatureGroups.find(pool_id);
748 if (it != mPoolCreatureGroups.end() && !it->second.isEmpty())
749 it->second.SpawnObject(spawnedPoolData, mPoolTemplate[pool_id].MaxLimit, db_guid);
750}
751
752// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
753// If it's same, the gameobject is respawned only (added back to map)
754template<>
755void PoolMgr::SpawnPool<GameObject>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid)
756{
757 auto it = mPoolGameobjectGroups.find(pool_id);
758 if (it != mPoolGameobjectGroups.end() && !it->second.isEmpty())
759 it->second.SpawnObject(spawnedPoolData, mPoolTemplate[pool_id].MaxLimit, db_guid);
760}
761
762// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
763// If it's same, the pool is respawned only
764template<>
765void PoolMgr::SpawnPool<Pool>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 sub_pool_id)
766{
767 auto it = mPoolPoolGroups.find(pool_id);
768 if (it != mPoolPoolGroups.end() && !it->second.isEmpty())
769 it->second.SpawnObject(spawnedPoolData, mPoolTemplate[pool_id].MaxLimit, sub_pool_id);
770}
771
772void PoolMgr::SpawnPool(SpawnedPoolData& spawnedPoolData, uint32 pool_id)
773{
774 SpawnPool<Pool>(spawnedPoolData, pool_id, 0);
775 SpawnPool<GameObject>(spawnedPoolData, pool_id, 0);
776 SpawnPool<Creature>(spawnedPoolData, pool_id, 0);
777}
778
779// Call to despawn a pool, all gameobjects/creatures in this pool are removed
780void PoolMgr::DespawnPool(SpawnedPoolData& spawnedPoolData, uint32 pool_id, bool alwaysDeleteRespawnTime)
781{
782 {
783 auto it = mPoolCreatureGroups.find(pool_id);
784 if (it != mPoolCreatureGroups.end() && !it->second.isEmpty())
785 it->second.DespawnAllObjects(spawnedPoolData, alwaysDeleteRespawnTime);
786 }
787 {
788 auto it = mPoolGameobjectGroups.find(pool_id);
789 if (it != mPoolGameobjectGroups.end() && !it->second.isEmpty())
790 it->second.DespawnAllObjects(spawnedPoolData, alwaysDeleteRespawnTime);
791 }
792 {
793 auto it = mPoolPoolGroups.find(pool_id);
794 if (it != mPoolPoolGroups.end() && !it->second.isEmpty())
795 it->second.DespawnAllObjects(spawnedPoolData, alwaysDeleteRespawnTime);
796 }
797}
798
799// Selects proper template overload to call based on passed type
801{
802 switch (type)
803 {
805 return IsPartOfAPool<Creature>(spawnId);
807 return IsPartOfAPool<GameObject>(spawnId);
809 return 0;
810 default:
811 ABORT_MSG("Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId " UI64FMTD ")", uint32(type), spawnId);
812 return 0;
813 }
814}
815
816bool PoolMgr::IsEmpty(uint32 pool_id) const
817{
818 {
819 auto it = mPoolGameobjectGroups.find(pool_id);
820 if (it != mPoolGameobjectGroups.end() && !it->second.isEmptyDeepCheck())
821 return false;
822 }
823 {
824 auto it = mPoolCreatureGroups.find(pool_id);
825 if (it != mPoolCreatureGroups.end() && !it->second.isEmptyDeepCheck())
826 return false;
827 }
828 {
829 auto it = mPoolPoolGroups.find(pool_id);
830 if (it != mPoolPoolGroups.end() && !it->second.isEmptyDeepCheck())
831 return false;
832 }
833 return true;
834}
835
836// Method that check chance integrity of the creatures and gameobjects in this pool
837bool PoolMgr::CheckPool(uint32 pool_id) const
838{
839 {
840 auto it = mPoolGameobjectGroups.find(pool_id);
841 if (it != mPoolGameobjectGroups.end() && !it->second.CheckPool())
842 return false;
843 }
844 {
845 auto it = mPoolCreatureGroups.find(pool_id);
846 if (it != mPoolCreatureGroups.end() && !it->second.CheckPool())
847 return false;
848 }
849 {
850 auto it = mPoolPoolGroups.find(pool_id);
851 if (it != mPoolPoolGroups.end() && !it->second.CheckPool())
852 return false;
853 }
854 return true;
855}
856
857// Call to update the pool when a gameobject/creature part of pool [pool_id] is ready to respawn
858// Here we cache only the creature/gameobject whose guid is passed as parameter
859// Then the spawn pool call will use this cache to decide
860template<typename T>
861void PoolMgr::UpdatePool(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id)
862{
863 if (uint32 motherpoolid = IsPartOfAPool<Pool>(pool_id))
864 SpawnPool<Pool>(spawnedPoolData, motherpoolid, pool_id);
865 else
866 SpawnPool<T>(spawnedPoolData, pool_id, db_guid_or_pool_id);
867}
868
869template void PoolMgr::UpdatePool<Pool>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id);
870template void PoolMgr::UpdatePool<GameObject>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id);
871template void PoolMgr::UpdatePool<Creature>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id);
872
873void PoolMgr::UpdatePool(SpawnedPoolData& spawnedPoolData, uint32 pool_id, SpawnObjectType type, uint64 spawnId)
874{
875 switch (type)
876 {
878 UpdatePool<Creature>(spawnedPoolData, pool_id, spawnId);
879 break;
881 UpdatePool<GameObject>(spawnedPoolData, pool_id, spawnId);
882 break;
883 default:
884 ABORT_MSG("Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId " UI64FMTD ")", uint32(type), spawnId);
885 }
886}
887
888std::unique_ptr<SpawnedPoolData> PoolMgr::InitPoolsForMap(Map* map)
889{
890 std::unique_ptr<SpawnedPoolData> spawnedPoolData = std::make_unique<SpawnedPoolData>(map);
891 if (std::vector<uint32> const* poolIds = Trinity::Containers::MapGetValuePtr(mAutoSpawnPoolsPerMap, spawnedPoolData->GetMap()->GetId()))
892 for (uint32 poolId : *poolIds)
893 SpawnPool(*spawnedPoolData, poolId);
894
895 return spawnedPoolData;
896}
897
std::shared_ptr< ResultSet > QueryResult
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
#define UI64FMTD
Definition Define.h:138
#define TC_GAME_API
Definition Define.h:129
int32_t int32
Definition Define.h:150
uint64_t uint64
Definition Define.h:153
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
#define ABORT_MSG
Definition Errors.h:80
#define ASSERT
Definition Errors.h:72
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:173
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:182
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:176
#define sObjectMgr
Definition ObjectMgr.h:1873
#define sPoolMgr
Definition PoolMgr.h:179
float rand_chance()
Definition Random.cpp:82
SpawnObjectType
Definition SpawnData.h:35
@ SPAWN_TYPE_GAMEOBJECT
Definition SpawnData.h:37
@ SPAWN_TYPE_AREATRIGGER
Definition SpawnData.h:38
@ SPAWN_TYPE_CREATURE
Definition SpawnData.h:36
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition Timer.h:57
uint32 getMSTime()
Definition Timer.h:33
static Creature * CreateCreatureFromDB(ObjectGuid::LowType spawnId, Map *map, bool addToMap=true, bool allowDuplicate=false)
bool GetRespawnCompatibilityMode() const
Definition Creature.h:431
void SaveRespawnTime(uint32 forceDelay=0)
Class used to access individual fields of database query result.
Definition Field.h:94
float GetFloat() const noexcept
Definition Field.cpp:85
uint64 GetUInt64() const noexcept
Definition Field.cpp:71
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
bool isSpawnedByDefault() const
Definition GameObject.h:271
static GameObject * CreateGameObjectFromDB(ObjectGuid::LowType spawnId, Map *map, bool addToMap=true)
void SaveRespawnTime(uint32 forceDelay=0)
bool GetRespawnCompatibilityMode()
Definition GameObject.h:386
Definition Map.h:227
bool AddToMap(T *)
Definition Map.cpp:527
void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, CharacterDatabaseTransaction dbTrans=nullptr, bool alwaysDeleteFromDB=false)
Definition Map.h:730
GameObjectBySpawnIdContainer & GetGameObjectBySpawnIdStore()
Definition Map.h:470
bool IsGridLoaded(uint32 gridId) const
Definition Map.h:277
CreatureBySpawnIdContainer & GetCreatureBySpawnIdStore()
Definition Map.h:466
uint64 LowType
Definition ObjectGuid.h:321
void SetPoolId(uint32 pool_id)
Definition PoolMgr.h:99
void DespawnObject(SpawnedPoolData &spawns, uint64 guid, bool alwaysDeleteRespawnTime=false)
Definition PoolMgr.cpp:186
void Despawn1Object(SpawnedPoolData &spawns, uint64 guid, bool alwaysDeleteRespawnTime=false, bool saveRespawnTime=true)
void RemoveOneRelation(uint32 child_pool_id)
void DespawnAllObjects(SpawnedPoolData &spawns, bool alwaysDeleteRespawnTime=false)
Definition PoolMgr.cpp:199
void Spawn1Object(SpawnedPoolData &spawns, PoolObject *obj)
void AddEntry(PoolObject &poolitem, uint32 maxentries)
Definition PoolMgr.cpp:161
bool isEmptyDeepCheck() const
Definition PoolMgr.cpp:140
bool CheckPool() const
Definition PoolMgr.cpp:171
void RemoveRespawnTimeFromDB(SpawnedPoolData &spawns, uint64 guid)
std::vector< PoolObject > PoolObjectList
Definition PoolMgr.h:89
void SpawnObject(SpawnedPoolData &spawns, uint32 limit, uint64 triggerFrom)
Definition PoolMgr.cpp:277
std::pair< uint64, uint32 > SearchPair
Definition PoolMgr.h:166
bool IsEmpty(uint32 pool_id) const
Definition PoolMgr.cpp:816
bool CheckPool(uint32 pool_id) const
Definition PoolMgr.cpp:837
std::unique_ptr< SpawnedPoolData > InitPoolsForMap(Map *map)
Definition PoolMgr.cpp:888
void Initialize()
Definition PoolMgr.cpp:401
void UpdatePool(SpawnedPoolData &spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id)
Definition PoolMgr.cpp:861
static PoolMgr * instance()
Definition PoolMgr.cpp:407
void SpawnPool(SpawnedPoolData &spawnedPoolData, uint32 pool_id)
Definition PoolMgr.cpp:772
PoolTemplateData const * GetPoolTemplate(uint16 pool_id) const
Definition PoolMgr.cpp:898
PoolGroupCreatureMap mPoolCreatureGroups
Definition PoolMgr.h:170
void LoadFromDB()
Definition PoolMgr.cpp:413
PoolGroupPoolMap mPoolPoolGroups
Definition PoolMgr.h:172
SearchMap mGameobjectSearchMap
Definition PoolMgr.h:174
PoolTemplateDataMap mPoolTemplate
Definition PoolMgr.h:169
std::unordered_map< uint32, std::vector< uint32 > > mAutoSpawnPoolsPerMap
Definition PoolMgr.h:176
uint32 IsPartOfAPool(uint64 db_guid_or_pool_id) const
SearchMap mPoolSearchMap
Definition PoolMgr.h:175
PoolGroupGameObjectMap mPoolGameobjectGroups
Definition PoolMgr.h:171
void DespawnPool(SpawnedPoolData &spawnedPoolData, uint32 pool_id, bool alwaysDeleteRespawnTime=false)
Definition PoolMgr.cpp:780
SearchMap mCreatureSearchMap
Definition PoolMgr.h:173
SpawnedPoolObjects mSpawnedCreatures
Definition PoolMgr.h:81
SpawnedPoolObjects mSpawnedGameobjects
Definition PoolMgr.h:82
Map * GetMap() const
Definition PoolMgr.h:65
SpawnedPoolData(Map *owner)
Definition PoolMgr.cpp:36
void AddSpawn(uint64 db_guid_or_pool_id, uint32 pool_id)
uint32 GetSpawnedObjects(uint32 pool_id) const
Definition PoolMgr.cpp:40
bool IsSpawnedObject(uint64 db_guid_or_pool_id) const
void RemoveSpawn(uint64 db_guid_or_pool_id, uint32 pool_id)
SpawnedPoolPools mSpawnedPools
Definition PoolMgr.h:83
void AddObjectToRemoveList()
Definition Object.cpp:1179
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
void RandomResize(C &container, std::size_t requestedSize)
Definition Containers.h:67
STL namespace.
uint64 guid
Definition PoolMgr.h:42
float chance
Definition PoolMgr.h:43
PoolObject(uint64 _guid, float _chance)
Definition PoolMgr.cpp:29
uint32 MaxLimit
Definition PoolMgr.h:36