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
185// If no guid is passed, the pool is just removed (event end case)
186// If guid is filled, cache will be used and no removal will occur, it just fill the cache
187template<class T>
188void PoolGroup<T>::DespawnObject(SpawnedPoolData& spawns, uint64 guid, bool alwaysDeleteRespawnTime)
189{
190 for (size_t i=0; i < EqualChanced.size(); ++i)
191 {
192 // if spawned
193 if (spawns.IsSpawnedObject<T>(EqualChanced[i].guid))
194 {
195 if (!guid || EqualChanced[i].guid == guid)
196 {
197 Despawn1Object(spawns, EqualChanced[i].guid, alwaysDeleteRespawnTime);
198 spawns.RemoveSpawn<T>(EqualChanced[i].guid, poolId);
199 }
200 }
201 else if (alwaysDeleteRespawnTime)
202 RemoveRespawnTimeFromDB(spawns, EqualChanced[i].guid);
203 }
204
205 for (size_t i = 0; i < ExplicitlyChanced.size(); ++i)
206 {
207 // spawned
208 if (spawns.IsSpawnedObject<T>(ExplicitlyChanced[i].guid))
209 {
210 if (!guid || ExplicitlyChanced[i].guid == guid)
211 {
212 Despawn1Object(spawns, ExplicitlyChanced[i].guid, alwaysDeleteRespawnTime);
213 spawns.RemoveSpawn<T>(ExplicitlyChanced[i].guid, poolId);
214 }
215 }
216 else if (alwaysDeleteRespawnTime)
217 RemoveRespawnTimeFromDB(spawns, ExplicitlyChanced[i].guid);
218 }
219}
220
221// Method that is actualy doing the removal job on one creature
222template<>
223void PoolGroup<Creature>::Despawn1Object(SpawnedPoolData& spawns, uint64 guid, bool alwaysDeleteRespawnTime, bool saveRespawnTime)
224{
225 auto creatureBounds = spawns.GetMap()->GetCreatureBySpawnIdStore().equal_range(guid);
226 for (auto itr = creatureBounds.first; itr != creatureBounds.second;)
227 {
228 Creature* creature = itr->second;
229 ++itr;
230 // For dynamic spawns, save respawn time here
231 if (saveRespawnTime && !creature->GetRespawnCompatibilityMode())
232 creature->SaveRespawnTime();
233 creature->AddObjectToRemoveList();
234 }
235
236 if (alwaysDeleteRespawnTime)
237 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, guid, nullptr, true);
238}
239
240// Same on one gameobject
241template<>
242void PoolGroup<GameObject>::Despawn1Object(SpawnedPoolData& spawns, uint64 guid, bool alwaysDeleteRespawnTime, bool saveRespawnTime)
243{
244 auto gameobjectBounds = spawns.GetMap()->GetGameObjectBySpawnIdStore().equal_range(guid);
245 for (auto itr = gameobjectBounds.first; itr != gameobjectBounds.second;)
246 {
247 GameObject* go = itr->second;
248 ++itr;
249
250 // For dynamic spawns, save respawn time here
251 if (saveRespawnTime && !go->GetRespawnCompatibilityMode())
252 go->SaveRespawnTime();
254 }
255
256 if (alwaysDeleteRespawnTime)
257 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, guid, nullptr, true);
258}
259
260// Same on one pool
261template<>
262void PoolGroup<Pool>::Despawn1Object(SpawnedPoolData& spawns, uint64 child_pool_id, bool alwaysDeleteRespawnTime, bool /*saveRespawnTime*/)
263{
264 sPoolMgr->DespawnPool(spawns, child_pool_id, alwaysDeleteRespawnTime);
265}
266
267// Method for a pool only to remove any found record causing a circular dependency loop
268template<>
270{
271 for (PoolObjectList::iterator itr = ExplicitlyChanced.begin(); itr != ExplicitlyChanced.end(); ++itr)
272 {
273 if (itr->guid == child_pool_id)
274 {
275 ExplicitlyChanced.erase(itr);
276 break;
277 }
278 }
279 for (PoolObjectList::iterator itr = EqualChanced.begin(); itr != EqualChanced.end(); ++itr)
280 {
281 if (itr->guid == child_pool_id)
282 {
283 EqualChanced.erase(itr);
284 break;
285 }
286 }
287}
288
289template <class T>
291{
292 int count = limit - spawns.GetSpawnedObjects(poolId);
293
294 // If triggered from some object respawn this object is still marked as spawned
295 // and also counted into m_SpawnedPoolAmount so we need increase count to be
296 // spawned by 1
297 if (triggerFrom)
298 ++count;
299
300 if (count > 0)
301 {
302 PoolObjectList rolledObjects;
303 rolledObjects.reserve(count);
304
305 // roll objects to be spawned
306 if (!ExplicitlyChanced.empty())
307 {
308 float roll = rand_chance();
309
310 for (PoolObject& obj : ExplicitlyChanced)
311 {
312 roll -= obj.chance;
313 // Triggering object is marked as spawned at this time and can be also rolled (respawn case)
314 // so this need explicit check for this case
315 if (roll < 0 && (obj.guid == triggerFrom || !spawns.IsSpawnedObject<T>(obj.guid)))
316 {
317 rolledObjects.push_back(obj);
318 break;
319 }
320 }
321 }
322
323 if (!EqualChanced.empty() && rolledObjects.empty())
324 {
325 std::copy_if(EqualChanced.begin(), EqualChanced.end(), std::back_inserter(rolledObjects), [triggerFrom, &spawns](PoolObject const& object)
326 {
327 return object.guid == triggerFrom || !spawns.IsSpawnedObject<T>(object.guid);
328 });
329
330 Trinity::Containers::RandomResize(rolledObjects, count);
331 }
332
333 // try to spawn rolled objects
334 for (PoolObject& obj : rolledObjects)
335 {
336 if (obj.guid == triggerFrom)
337 {
338 ReSpawn1Object(spawns, &obj);
339 triggerFrom = 0;
340 }
341 else
342 {
343 spawns.AddSpawn<T>(obj.guid, poolId);
344 Spawn1Object(spawns, &obj);
345 }
346 }
347 }
348
349 // One spawn one despawn no count increase
350 if (triggerFrom)
351 DespawnObject(spawns, triggerFrom);
352}
353
354// Method that is actualy doing the spawn job on 1 creature
355template <>
357{
358 if (CreatureData const* data = sObjectMgr->GetCreatureData(obj->guid))
359 {
360 // Spawn if necessary (loaded grids only)
361 // We use spawn coords to spawn
362 if (spawns.GetMap()->IsGridLoaded(data->spawnPoint))
364 }
365}
366
367// Same for 1 gameobject
368template <>
370{
371 if (GameObjectData const* data = sObjectMgr->GetGameObjectData(obj->guid))
372 {
373 // Spawn if necessary (loaded grids only)
374 // We use current coords to unspawn, not spawn coords since creature can have changed grid
375 if (spawns.GetMap()->IsGridLoaded(data->spawnPoint))
376 if (GameObject* go = GameObject::CreateGameObjectFromDB(obj->guid, spawns.GetMap(), false))
377 if (go->isSpawnedByDefault())
378 if (!spawns.GetMap()->AddToMap(go))
379 delete go;
380 }
381}
382
383// Same for 1 pool
384template <>
386{
387 sPoolMgr->SpawnPool(spawns, obj->guid);
388}
389
390// Method that does the respawn job on the specified creature
391template <>
393{
394 Despawn1Object(spawns, obj->guid, false, false);
395 Spawn1Object(spawns, obj);
396}
397
398// Method that does the respawn job on the specified gameobject
399template <>
401{
402 Despawn1Object(spawns, obj->guid, false, false);
403 Spawn1Object(spawns, obj);
404}
405
406// Nothing to do for a child Pool
407template <>
408void PoolGroup<Pool>::ReSpawn1Object(SpawnedPoolData& /*spawns*/, PoolObject* /*obj*/) { }
409
410template <>
412{
413 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, guid, nullptr, true);
414}
415
416template <>
418{
419 spawns.GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, guid, nullptr, true);
420}
421
422template <>
424
426// Methods of class PoolMgr
427
428PoolMgr::PoolMgr() = default;
429PoolMgr::~PoolMgr() = default;
430
432{
433 mGameobjectSearchMap.clear();
434 mCreatureSearchMap.clear();
435}
436
438{
439 static PoolMgr instance;
440 return &instance;
441}
442
444{
445 // Pool templates
446 {
447 uint32 oldMSTime = getMSTime();
448
449 QueryResult result = WorldDatabase.Query("SELECT entry, max_limit FROM pool_template");
450 if (!result)
451 {
452 mPoolTemplate.clear();
453 TC_LOG_INFO("server.loading", ">> Loaded 0 object pools. DB table `pool_template` is empty.");
454 return;
455 }
456
457 uint32 count = 0;
458 do
459 {
460 Field* fields = result->Fetch();
461
462 uint32 pool_id = fields[0].GetUInt32();
463
464 PoolTemplateData& pPoolTemplate = mPoolTemplate[pool_id];
465 pPoolTemplate.MaxLimit = fields[1].GetUInt32();
466 pPoolTemplate.MapId = -1;
467
468 ++count;
469 }
470 while (result->NextRow());
471
472 TC_LOG_INFO("server.loading", ">> Loaded {} objects pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
473 }
474
475 // Creatures
476
477 TC_LOG_INFO("server.loading", "Loading Creatures Pooling Data...");
478 {
479 uint32 oldMSTime = getMSTime();
480
481 // 1 2 3
482 QueryResult result = WorldDatabase.Query("SELECT spawnId, poolSpawnId, chance FROM pool_members WHERE type = 0");
483
484 if (!result)
485 {
486 TC_LOG_INFO("server.loading", ">> Loaded 0 creatures in pools. DB table `pool_creature` is empty.");
487 }
488 else
489 {
490 uint32 count = 0;
491 do
492 {
493 Field* fields = result->Fetch();
494
495 uint64 guid = fields[0].GetUInt64();
496 uint32 pool_id = fields[1].GetUInt32();
497 float chance = fields[2].GetFloat();
498
499 CreatureData const* data = sObjectMgr->GetCreatureData(guid);
500 if (!data)
501 {
502 TC_LOG_ERROR("sql.sql", "`pool_creature` has a non existing creature spawn (GUID: {}) defined for pool id ({}), skipped.", guid, pool_id);
503 continue;
504 }
505 auto it = mPoolTemplate.find(pool_id);
506 if (it == mPoolTemplate.end())
507 {
508 TC_LOG_ERROR("sql.sql", "`pool_creature` pool id ({}) is not in `pool_template`, skipped.", pool_id);
509 continue;
510 }
511 if (chance < 0 || chance > 100)
512 {
513 TC_LOG_ERROR("sql.sql", "`pool_creature` has an invalid chance ({}) for creature guid ({}) in pool id ({}), skipped.", chance, guid, pool_id);
514 continue;
515 }
516 PoolTemplateData* pPoolTemplate = &mPoolTemplate[pool_id];
517 if (pPoolTemplate->MapId == -1)
518 pPoolTemplate->MapId = int32(data->mapId);
519
520 if (pPoolTemplate->MapId != int32(data->mapId))
521 {
522 TC_LOG_ERROR("sql.sql", "`pool_creature` has creature spawns on multiple different maps for creature guid ({}) in pool id ({}), skipped.", guid, pool_id);
523 continue;
524 }
525
526 PoolObject plObject = PoolObject(guid, chance);
527 PoolGroup<Creature>& cregroup = mPoolCreatureGroups[pool_id];
528 cregroup.SetPoolId(pool_id);
529 cregroup.AddEntry(plObject, pPoolTemplate->MaxLimit);
530 SearchPair p(guid, pool_id);
531 mCreatureSearchMap.insert(p);
532
533 ++count;
534 }
535 while (result->NextRow());
536
537 TC_LOG_INFO("server.loading", ">> Loaded {} creatures in pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
538 }
539 }
540
541 // Gameobjects
542
543 TC_LOG_INFO("server.loading", "Loading Gameobject Pooling Data...");
544 {
545 uint32 oldMSTime = getMSTime();
546
547 // 1 2 3
548 QueryResult result = WorldDatabase.Query("SELECT spawnId, poolSpawnId, chance FROM pool_members WHERE type = 1");
549
550 if (!result)
551 {
552 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobjects in pools. DB table `pool_gameobject` is empty.");
553 }
554 else
555 {
556 uint32 count = 0;
557 do
558 {
559 Field* fields = result->Fetch();
560
561 uint64 guid = fields[0].GetUInt64();
562 uint32 pool_id = fields[1].GetUInt32();
563 float chance = fields[2].GetFloat();
564
565 GameObjectData const* data = sObjectMgr->GetGameObjectData(guid);
566 if (!data)
567 {
568 TC_LOG_ERROR("sql.sql", "`pool_gameobject` has a non existing gameobject spawn (GUID: {}) defined for pool id ({}), skipped.", guid, pool_id);
569 continue;
570 }
571
572 auto it = mPoolTemplate.find(pool_id);
573 if (it == mPoolTemplate.end())
574 {
575 TC_LOG_ERROR("sql.sql", "`pool_gameobject` pool id ({}) is not in `pool_template`, skipped.", pool_id);
576 continue;
577 }
578
579 if (chance < 0 || chance > 100)
580 {
581 TC_LOG_ERROR("sql.sql", "`pool_gameobject` has an invalid chance ({}) for gameobject guid ({}) in pool id ({}), skipped.", chance, guid, pool_id);
582 continue;
583 }
584
585 PoolTemplateData* pPoolTemplate = &mPoolTemplate[pool_id];
586 if (pPoolTemplate->MapId == -1)
587 pPoolTemplate->MapId = int32(data->mapId);
588
589 if (pPoolTemplate->MapId != int32(data->mapId))
590 {
591 TC_LOG_ERROR("sql.sql", "`pool_gameobject` has gameobject spawns on multiple different maps for gameobject guid ({}) in pool id ({}), skipped.", guid, pool_id);
592 continue;
593 }
594
595 PoolObject plObject = PoolObject(guid, chance);
597 gogroup.SetPoolId(pool_id);
598 gogroup.AddEntry(plObject, pPoolTemplate->MaxLimit);
599 SearchPair p(guid, pool_id);
600 mGameobjectSearchMap.insert(p);
601
602 ++count;
603 }
604 while (result->NextRow());
605
606 TC_LOG_INFO("server.loading", ">> Loaded {} gameobject in pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
607 }
608 }
609
610 // Pool of pools
611
612 TC_LOG_INFO("server.loading", "Loading Mother Pooling Data...");
613 {
614 uint32 oldMSTime = getMSTime();
615
616 // 1 2 3
617 QueryResult result = WorldDatabase.Query("SELECT spawnId, poolSpawnId, chance FROM pool_members WHERE type = 2");
618
619 if (!result)
620 {
621 TC_LOG_INFO("server.loading", ">> Loaded 0 pools in pools");
622 }
623 else
624 {
625 uint32 count = 0;
626 do
627 {
628 Field* fields = result->Fetch();
629
630 uint32 child_pool_id = fields[0].GetUInt64();
631 uint32 mother_pool_id = fields[1].GetUInt32();
632 float chance = fields[2].GetFloat();
633
634 {
635 auto it = mPoolTemplate.find(mother_pool_id);
636 if (it == mPoolTemplate.end())
637 {
638 TC_LOG_ERROR("sql.sql", "`pool_pool` mother_pool id ({}) is not in `pool_template`, skipped.", mother_pool_id);
639 continue;
640 }
641 }
642 {
643 auto it = mPoolTemplate.find(child_pool_id);
644 if (it == mPoolTemplate.end())
645 {
646 TC_LOG_ERROR("sql.sql", "`pool_pool` included pool_id ({}) is not in `pool_template`, skipped.", child_pool_id);
647 continue;
648 }
649 }
650 if (mother_pool_id == child_pool_id)
651 {
652 TC_LOG_ERROR("sql.sql", "`pool_pool` pool_id ({}) includes itself, dead-lock detected, skipped.", child_pool_id);
653 continue;
654 }
655 if (chance < 0 || chance > 100)
656 {
657 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);
658 continue;
659 }
660 PoolTemplateData* pPoolTemplateMother = &mPoolTemplate[mother_pool_id];
661 PoolObject plObject = PoolObject(child_pool_id, chance);
662 PoolGroup<Pool>& plgroup = mPoolPoolGroups[mother_pool_id];
663 plgroup.SetPoolId(mother_pool_id);
664 plgroup.AddEntry(plObject, pPoolTemplateMother->MaxLimit);
665 SearchPair p(child_pool_id, mother_pool_id);
666 mPoolSearchMap.insert(p);
667
668 ++count;
669 }
670 while (result->NextRow());
671
672 // Now check for circular reference
673 // All pool_ids are in pool_template
674 for (auto const& it : mPoolTemplate)
675 {
676 std::set<uint32> checkedPools;
677 for (SearchMap::iterator poolItr = mPoolSearchMap.find(it.first); poolItr != mPoolSearchMap.end(); poolItr = mPoolSearchMap.find(poolItr->second))
678 {
679 if (mPoolTemplate[poolItr->first].MapId != -1)
680 {
681 if (mPoolTemplate[poolItr->second].MapId == -1)
682 mPoolTemplate[poolItr->second].MapId = mPoolTemplate[poolItr->first].MapId;
683
684 if (mPoolTemplate[poolItr->second].MapId != mPoolTemplate[poolItr->first].MapId)
685 {
686 TC_LOG_ERROR("sql.sql", "`pool_pool` has child pools on multiple maps in pool id ({}), skipped.", poolItr->second);
687 mPoolPoolGroups[poolItr->second].RemoveOneRelation(poolItr->first);
688 mPoolSearchMap.erase(poolItr);
689 --count;
690 break;
691 }
692 }
693
694 checkedPools.insert(poolItr->first);
695 if (checkedPools.find(poolItr->second) != checkedPools.end())
696 {
697 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 {}",
698 fmt::join(checkedPools, " "), poolItr->first, poolItr->second);
699 mPoolPoolGroups[poolItr->second].RemoveOneRelation(poolItr->first);
700 mPoolSearchMap.erase(poolItr);
701 --count;
702 break;
703 }
704 }
705 }
706
707 TC_LOG_INFO("server.loading", ">> Loaded {} pools in mother pools in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
708 }
709 }
710
711 for (auto const& [poolId, templateData] : mPoolTemplate)
712 {
713 if (IsEmpty(poolId))
714 {
715 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);
716 continue;
717 }
718 ASSERT(templateData.MapId != -1);
719 }
720
721 // 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
722 TC_LOG_INFO("server.loading", "Starting objects pooling system...");
723 {
724 uint32 oldMSTime = getMSTime();
725
726 QueryResult result = WorldDatabase.Query("SELECT DISTINCT pool_template.entry, pool_members.spawnId, pool_members.poolSpawnId FROM pool_template"
727 " LEFT JOIN game_event_pool ON pool_template.entry = game_event_pool.pool_entry"
728 " LEFT JOIN pool_members ON pool_members.type = 2 AND pool_template.entry = pool_members.spawnId WHERE game_event_pool.pool_entry IS NULL");
729
730 if (!result)
731 {
732 TC_LOG_INFO("server.loading", ">> Pool handling system initialized, 0 pools spawned.");
733 }
734 else
735 {
736 uint32 count = 0;
737 do
738 {
739 Field* fields = result->Fetch();
740 uint32 pool_entry = fields[0].GetUInt32();
741 uint32 pool_pool_id = fields[1].GetUInt64();
742
743 if (IsEmpty(pool_entry))
744 continue;
745
746 if (!CheckPool(pool_entry))
747 {
748 if (pool_pool_id)
749 // 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,
750 // however that could recursively invalidate entire chain of mother pools. It can be done in the future but for now we'll do nothing.
751 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());
752 else
753 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);
754 continue;
755 }
756
757 // Don't spawn child pools, they are spawned recursively by their parent pools
758 if (!pool_pool_id)
759 {
760 mAutoSpawnPoolsPerMap[mPoolTemplate[pool_entry].MapId].push_back(pool_entry);
761 count++;
762 }
763 }
764 while (result->NextRow());
765
766 TC_LOG_DEBUG("pool", "Pool handling system initialized, {} pools will be spawned by default in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
767
768 }
769 }
770}
771
772// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
773// If it's same, the creature is respawned only (added back to map)
774template<>
775void PoolMgr::SpawnPool<Creature>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid)
776{
777 auto it = mPoolCreatureGroups.find(pool_id);
778 if (it != mPoolCreatureGroups.end() && !it->second.isEmpty())
779 it->second.SpawnObject(spawnedPoolData, mPoolTemplate[pool_id].MaxLimit, db_guid);
780}
781
782// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
783// If it's same, the gameobject is respawned only (added back to map)
784template<>
785void PoolMgr::SpawnPool<GameObject>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid)
786{
787 auto it = mPoolGameobjectGroups.find(pool_id);
788 if (it != mPoolGameobjectGroups.end() && !it->second.isEmpty())
789 it->second.SpawnObject(spawnedPoolData, mPoolTemplate[pool_id].MaxLimit, db_guid);
790}
791
792// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
793// If it's same, the pool is respawned only
794template<>
795void PoolMgr::SpawnPool<Pool>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 sub_pool_id)
796{
797 auto it = mPoolPoolGroups.find(pool_id);
798 if (it != mPoolPoolGroups.end() && !it->second.isEmpty())
799 it->second.SpawnObject(spawnedPoolData, mPoolTemplate[pool_id].MaxLimit, sub_pool_id);
800}
801
802void PoolMgr::SpawnPool(SpawnedPoolData& spawnedPoolData, uint32 pool_id)
803{
804 SpawnPool<Pool>(spawnedPoolData, pool_id, 0);
805 SpawnPool<GameObject>(spawnedPoolData, pool_id, 0);
806 SpawnPool<Creature>(spawnedPoolData, pool_id, 0);
807}
808
809// Call to despawn a pool, all gameobjects/creatures in this pool are removed
810void PoolMgr::DespawnPool(SpawnedPoolData& spawnedPoolData, uint32 pool_id, bool alwaysDeleteRespawnTime)
811{
812 {
813 auto it = mPoolCreatureGroups.find(pool_id);
814 if (it != mPoolCreatureGroups.end() && !it->second.isEmpty())
815 it->second.DespawnObject(spawnedPoolData, 0, alwaysDeleteRespawnTime);
816 }
817 {
818 auto it = mPoolGameobjectGroups.find(pool_id);
819 if (it != mPoolGameobjectGroups.end() && !it->second.isEmpty())
820 it->second.DespawnObject(spawnedPoolData, 0, alwaysDeleteRespawnTime);
821 }
822 {
823 auto it = mPoolPoolGroups.find(pool_id);
824 if (it != mPoolPoolGroups.end() && !it->second.isEmpty())
825 it->second.DespawnObject(spawnedPoolData, 0, alwaysDeleteRespawnTime);
826 }
827}
828
829// Selects proper template overload to call based on passed type
831{
832 switch (type)
833 {
835 return IsPartOfAPool<Creature>(spawnId);
837 return IsPartOfAPool<GameObject>(spawnId);
839 return 0;
840 default:
841 ABORT_MSG("Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId " UI64FMTD ")", uint32(type), spawnId);
842 return 0;
843 }
844}
845
846bool PoolMgr::IsEmpty(uint32 pool_id) const
847{
848 {
849 auto it = mPoolGameobjectGroups.find(pool_id);
850 if (it != mPoolGameobjectGroups.end() && !it->second.isEmptyDeepCheck())
851 return false;
852 }
853 {
854 auto it = mPoolCreatureGroups.find(pool_id);
855 if (it != mPoolCreatureGroups.end() && !it->second.isEmptyDeepCheck())
856 return false;
857 }
858 {
859 auto it = mPoolPoolGroups.find(pool_id);
860 if (it != mPoolPoolGroups.end() && !it->second.isEmptyDeepCheck())
861 return false;
862 }
863 return true;
864}
865
866// Method that check chance integrity of the creatures and gameobjects in this pool
867bool PoolMgr::CheckPool(uint32 pool_id) const
868{
869 {
870 auto it = mPoolGameobjectGroups.find(pool_id);
871 if (it != mPoolGameobjectGroups.end() && !it->second.CheckPool())
872 return false;
873 }
874 {
875 auto it = mPoolCreatureGroups.find(pool_id);
876 if (it != mPoolCreatureGroups.end() && !it->second.CheckPool())
877 return false;
878 }
879 {
880 auto it = mPoolPoolGroups.find(pool_id);
881 if (it != mPoolPoolGroups.end() && !it->second.CheckPool())
882 return false;
883 }
884 return true;
885}
886
887// Call to update the pool when a gameobject/creature part of pool [pool_id] is ready to respawn
888// Here we cache only the creature/gameobject whose guid is passed as parameter
889// Then the spawn pool call will use this cache to decide
890template<typename T>
891void PoolMgr::UpdatePool(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id)
892{
893 if (uint32 motherpoolid = IsPartOfAPool<Pool>(pool_id))
894 SpawnPool<Pool>(spawnedPoolData, motherpoolid, pool_id);
895 else
896 SpawnPool<T>(spawnedPoolData, pool_id, db_guid_or_pool_id);
897}
898
899template void PoolMgr::UpdatePool<Pool>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id);
900template void PoolMgr::UpdatePool<GameObject>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id);
901template void PoolMgr::UpdatePool<Creature>(SpawnedPoolData& spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id);
902
903void PoolMgr::UpdatePool(SpawnedPoolData& spawnedPoolData, uint32 pool_id, SpawnObjectType type, uint64 spawnId)
904{
905 switch (type)
906 {
908 UpdatePool<Creature>(spawnedPoolData, pool_id, spawnId);
909 break;
911 UpdatePool<GameObject>(spawnedPoolData, pool_id, spawnId);
912 break;
913 default:
914 ABORT_MSG("Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId " UI64FMTD ")", uint32(type), spawnId);
915 }
916}
917
918std::unique_ptr<SpawnedPoolData> PoolMgr::InitPoolsForMap(Map* map)
919{
920 std::unique_ptr<SpawnedPoolData> spawnedPoolData = std::make_unique<SpawnedPoolData>(map);
921 if (std::vector<uint32> const* poolIds = Trinity::Containers::MapGetValuePtr(mAutoSpawnPoolsPerMap, spawnedPoolData->GetMap()->GetId()))
922 for (uint32 poolId : *poolIds)
923 SpawnPool(*spawnedPoolData, poolId);
924
925 return spawnedPoolData;
926}
927
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:88
#define ASSERT
Definition Errors.h:80
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
#define sObjectMgr
Definition ObjectMgr.h:1885
#define sPoolMgr
Definition PoolMgr.h:179
float rand_chance()
Definition Random.cpp:81
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:423
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:265
static GameObject * CreateGameObjectFromDB(ObjectGuid::LowType spawnId, Map *map, bool addToMap=true)
void SaveRespawnTime(uint32 forceDelay=0)
bool GetRespawnCompatibilityMode()
Definition GameObject.h:380
Definition Map.h:225
bool AddToMap(T *)
Definition Map.cpp:517
void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, CharacterDatabaseTransaction dbTrans=nullptr, bool alwaysDeleteFromDB=false)
Definition Map.h:727
GameObjectBySpawnIdContainer & GetGameObjectBySpawnIdStore()
Definition Map.h:465
bool IsGridLoaded(uint32 gridId) const
Definition Map.h:275
CreatureBySpawnIdContainer & GetCreatureBySpawnIdStore()
Definition Map.h:461
uint64 LowType
Definition ObjectGuid.h:321
void SetPoolId(uint32 pool_id)
Definition PoolMgr.h:99
void ReSpawn1Object(SpawnedPoolData &spawns, PoolObject *obj)
void DespawnObject(SpawnedPoolData &spawns, uint64 guid=0, bool alwaysDeleteRespawnTime=false)
Definition PoolMgr.cpp:188
void Despawn1Object(SpawnedPoolData &spawns, uint64 guid, bool alwaysDeleteRespawnTime=false, bool saveRespawnTime=true)
void RemoveOneRelation(uint32 child_pool_id)
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:290
std::pair< uint64, uint32 > SearchPair
Definition PoolMgr.h:166
bool IsEmpty(uint32 pool_id) const
Definition PoolMgr.cpp:846
bool CheckPool(uint32 pool_id) const
Definition PoolMgr.cpp:867
std::unique_ptr< SpawnedPoolData > InitPoolsForMap(Map *map)
Definition PoolMgr.cpp:918
void Initialize()
Definition PoolMgr.cpp:431
void UpdatePool(SpawnedPoolData &spawnedPoolData, uint32 pool_id, uint64 db_guid_or_pool_id)
Definition PoolMgr.cpp:891
static PoolMgr * instance()
Definition PoolMgr.cpp:437
void SpawnPool(SpawnedPoolData &spawnedPoolData, uint32 pool_id)
Definition PoolMgr.cpp:802
PoolTemplateData const * GetPoolTemplate(uint16 pool_id) const
Definition PoolMgr.cpp:928
PoolGroupCreatureMap mPoolCreatureGroups
Definition PoolMgr.h:170
void LoadFromDB()
Definition PoolMgr.cpp:443
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:810
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:1174
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