2 * Copyright (C) 2005-2009 MaNGOS <http://www.mangosproject.org/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "PoolManager.h"
20 #include "ObjectMgr.h"
21 #include "ObjectDefines.h"
22 #include "ProgressBar.h"
24 #include "MapManager.h"
25 #include "Policies/SingletonImp.h"
27 INSTANTIATE_SINGLETON_1(PoolManager
);
29 ////////////////////////////////////////////////////////////
30 // Methods of template class PoolGroup
32 // Method to add a gameobject/creature guid to the proper list depending on pool type and chance value
34 void PoolGroup
<T
>::AddEntry(PoolObject
& poolitem
, uint32 maxentries
)
36 if (poolitem
.chance
!= 0 && maxentries
== 1)
37 ExplicitlyChanced
.push_back(poolitem
);
39 EqualChanced
.push_back(poolitem
);
42 // Method to check the chances are proper in this object pool
44 bool PoolGroup
<T
>::CheckPool(void)
46 if (EqualChanced
.size() == 0)
49 for (uint32 i
=0; i
<ExplicitlyChanced
.size(); ++i
)
50 chance
+= ExplicitlyChanced
[i
].chance
;
51 if (chance
!= 100 && chance
!= 0)
57 // Method that tell if the gameobject, creature or pool is spawned currently
59 bool PoolGroup
<T
>::IsSpawnedObject(uint32 guid
)
61 for (uint32 i
= 0; i
< ExplicitlyChanced
.size(); ++i
)
62 if (ExplicitlyChanced
[i
].guid
== guid
)
63 return ExplicitlyChanced
[i
].spawned
;
64 for (uint32 i
= 0; i
< EqualChanced
.size(); ++i
)
65 if (EqualChanced
[i
].guid
== guid
)
66 return EqualChanced
[i
].spawned
;
71 void PoolGroup
<T
>::RollOne(int32
& index
, PoolObjectList
** store
, uint32 triggerFrom
)
73 if (!ExplicitlyChanced
.empty())
75 float roll
= (float)rand_chance();
77 for (uint32 i
= 0; i
< ExplicitlyChanced
.size(); ++i
)
79 roll
-= ExplicitlyChanced
[i
].chance
;
80 // Triggering object is marked as spawned at this time and can be also rolled (respawn case)
81 // so this need explicit check for this case
82 if (roll
< 0 && (!ExplicitlyChanced
[i
].spawned
|| ExplicitlyChanced
[i
].guid
== triggerFrom
))
85 *store
= &ExplicitlyChanced
;
91 if (!EqualChanced
.empty())
93 index
= irand(0, EqualChanced
.size()-1);
94 // Triggering object is marked as spawned at this time and can be also rolled (respawn case)
95 // so this need explicit check for this case
96 if (!EqualChanced
[index
].spawned
|| EqualChanced
[index
].guid
== triggerFrom
)
98 *store
= &EqualChanced
;
106 // Main method to despawn a creature or gameobject in a pool
107 // If no guid is passed, the pool is just removed (event end case)
108 // If guid is filled, cache will be used and no removal will occur, it just fill the cache
110 void PoolGroup
<T
>::DespawnObject(uint32 guid
)
112 for (size_t i
= 0; i
< EqualChanced
.size(); ++i
)
114 if (EqualChanced
[i
].spawned
)
116 if (!guid
|| EqualChanced
[i
].guid
== guid
)
118 Despawn1Object(EqualChanced
[i
].guid
);
119 EqualChanced
[i
].spawned
= false;
121 if (m_SpawnedPoolAmount
> 0)
122 --m_SpawnedPoolAmount
;
127 for (size_t i
= 0; i
< ExplicitlyChanced
.size(); ++i
)
129 if (ExplicitlyChanced
[i
].spawned
)
131 if (!guid
|| ExplicitlyChanced
[i
].guid
== guid
)
133 Despawn1Object(ExplicitlyChanced
[i
].guid
);
134 ExplicitlyChanced
[i
].spawned
= false;
136 if (m_SpawnedPoolAmount
> 0)
137 --m_SpawnedPoolAmount
;
143 // Method that is actualy doing the removal job on one creature
145 void PoolGroup
<Creature
>::Despawn1Object(uint32 guid
)
147 if (CreatureData
const* data
= sObjectMgr
.GetCreatureData(guid
))
149 sObjectMgr
.RemoveCreatureFromGrid(guid
, data
);
151 if (Creature
* pCreature
= ObjectAccessor::GetCreatureInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
)))
152 pCreature
->AddObjectToRemoveList();
156 // Same on one gameobject
158 void PoolGroup
<GameObject
>::Despawn1Object(uint32 guid
)
160 if (GameObjectData
const* data
= sObjectMgr
.GetGOData(guid
))
162 sObjectMgr
.RemoveGameobjectFromGrid(guid
, data
);
164 if (GameObject
* pGameobject
= ObjectAccessor::GetGameObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
)))
165 pGameobject
->AddObjectToRemoveList();
171 void PoolGroup
<Pool
>::Despawn1Object(uint32 child_pool_id
)
173 sPoolMgr
.DespawnPool(child_pool_id
);
176 // Method for a pool only to remove any found record causing a circular dependency loop
178 void PoolGroup
<Pool
>::RemoveOneRelation(uint16 child_pool_id
)
180 for (PoolObjectList::iterator itr
= ExplicitlyChanced
.begin(); itr
!= ExplicitlyChanced
.end(); ++itr
)
182 if(itr
->guid
== child_pool_id
)
184 ExplicitlyChanced
.erase(itr
);
188 for (PoolObjectList::iterator itr
= EqualChanced
.begin(); itr
!= EqualChanced
.end(); ++itr
)
190 if(itr
->guid
== child_pool_id
)
192 EqualChanced
.erase(itr
);
199 void PoolGroup
<T
>::SpawnObject(uint32 limit
, uint32 triggerFrom
)
201 uint32 lastDespawned
= 0;
202 int count
= limit
- m_SpawnedPoolAmount
;
204 // If triggered from some object respawn this object is still marked as spawned
205 // and also counted into m_SpawnedPoolAmount so we need increase count to be
210 // This will try to spawn the rest of pool, not guaranteed
211 for (int i
= 0; i
< count
; ++i
)
214 PoolObjectList
* store
;
216 RollOne(index
, &store
, triggerFrom
);
219 if ((*store
)[index
].guid
== lastDespawned
)
222 if ((*store
)[index
].guid
== triggerFrom
)
224 (*store
)[index
].spawned
= ReSpawn1Object(triggerFrom
);
229 (*store
)[index
].spawned
= Spawn1Object((*store
)[index
].guid
);
233 // One spawn one despawn no count increase
234 DespawnObject(triggerFrom
);
235 lastDespawned
= triggerFrom
;
239 ++m_SpawnedPoolAmount
;
243 // Method that is actualy doing the spawn job on 1 creature
245 bool PoolGroup
<Creature
>::Spawn1Object(uint32 guid
)
247 if (CreatureData
const* data
= sObjectMgr
.GetCreatureData(guid
))
249 sObjectMgr
.AddCreatureToGrid(guid
, data
);
251 // Spawn if necessary (loaded grids only)
252 Map
* map
= const_cast<Map
*>(sMapMgr
.CreateBaseMap(data
->mapid
));
253 // We use spawn coords to spawn
254 if (!map
->Instanceable() && map
->IsLoaded(data
->posX
, data
->posY
))
256 Creature
* pCreature
= new Creature
;
257 //sLog.outDebug("Spawning creature %u",guid);
258 if (!pCreature
->LoadFromDB(guid
, map
))
271 // Same for 1 gameobject
273 bool PoolGroup
<GameObject
>::Spawn1Object(uint32 guid
)
275 if (GameObjectData
const* data
= sObjectMgr
.GetGOData(guid
))
277 sObjectMgr
.AddGameobjectToGrid(guid
, data
);
278 // Spawn if necessary (loaded grids only)
279 // this base map checked as non-instanced and then only existed
280 Map
* map
= const_cast<Map
*>(sMapMgr
.CreateBaseMap(data
->mapid
));
281 // We use current coords to unspawn, not spawn coords since creature can have changed grid
282 if (!map
->Instanceable() && map
->IsLoaded(data
->posX
, data
->posY
))
284 GameObject
* pGameobject
= new GameObject
;
285 //sLog.outDebug("Spawning gameobject %u", guid);
286 if (!pGameobject
->LoadFromDB(guid
, map
))
293 if (pGameobject
->isSpawnedByDefault())
294 map
->Add(pGameobject
);
304 bool PoolGroup
<Pool
>::Spawn1Object(uint32 child_pool_id
)
306 sPoolMgr
.SpawnPool(child_pool_id
, 0, 0);
307 sPoolMgr
.SpawnPool(child_pool_id
, 0, TYPEID_GAMEOBJECT
);
308 sPoolMgr
.SpawnPool(child_pool_id
, 0, TYPEID_UNIT
);
312 // Method that does the respawn job on the specified creature
314 bool PoolGroup
<Creature
>::ReSpawn1Object(uint32 guid
)
316 if (CreatureData
const* data
= sObjectMgr
.GetCreatureData(guid
))
318 if (Creature
* pCreature
= ObjectAccessor::GetCreatureInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
)))
319 pCreature
->GetMap()->Add(pCreature
);
325 // Same for 1 gameobject
327 bool PoolGroup
<GameObject
>::ReSpawn1Object(uint32 guid
)
329 if (GameObjectData
const* data
= sObjectMgr
.GetGOData(guid
))
331 if (GameObject
* pGameobject
= ObjectAccessor::GetGameObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
)))
332 pGameobject
->GetMap()->Add(pGameobject
);
338 // Nothing to do for a child Pool
340 bool PoolGroup
<Pool
>::ReSpawn1Object(uint32
/*guid*/)
346 ////////////////////////////////////////////////////////////
347 // Methods of class PoolManager
349 PoolManager::PoolManager()
351 m_IsPoolSystemStarted
= false;
354 void PoolManager::LoadFromDB()
356 QueryResult
*result
= WorldDatabase
.Query("SELECT MAX(entry) FROM pool_template");
359 sLog
.outString(">> Table pool_template is empty.");
365 Field
*fields
= result
->Fetch();
366 max_pool_id
= fields
[0].GetUInt16();
370 mPoolTemplate
.resize(max_pool_id
+ 1);
372 result
= WorldDatabase
.Query("SELECT entry,max_limit FROM pool_template");
375 mPoolTemplate
.clear();
376 sLog
.outString(">> Table pool_template is empty:");
383 barGoLink
bar(result
->GetRowCount());
387 Field
*fields
= result
->Fetch();
391 uint16 pool_id
= fields
[0].GetUInt16();
393 PoolTemplateData
& pPoolTemplate
= mPoolTemplate
[pool_id
];
394 pPoolTemplate
.MaxLimit
= fields
[1].GetUInt32();
396 } while (result
->NextRow());
399 sLog
.outString( ">> Loaded %u objects pools", count
);
404 mPoolCreatureGroups
.resize(max_pool_id
+ 1);
405 mCreatureSearchMap
.clear();
407 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_creature");
416 sLog
.outString(">> Loaded %u creatures in pools", count
);
421 barGoLink
bar2(result
->GetRowCount());
424 Field
*fields
= result
->Fetch();
428 uint32 guid
= fields
[0].GetUInt32();
429 uint16 pool_id
= fields
[1].GetUInt16();
430 float chance
= fields
[2].GetFloat();
432 CreatureData
const* data
= sObjectMgr
.GetCreatureData(guid
);
435 sLog
.outErrorDb("`pool_creature` has a non existing creature spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
438 if (pool_id
> max_pool_id
)
440 sLog
.outErrorDb("`pool_creature` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
443 if (chance
< 0 || chance
> 100)
445 sLog
.outErrorDb("`pool_creature` has an invalid chance (%f) for creature guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
448 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
451 PoolObject plObject
= PoolObject(guid
, chance
);
452 PoolGroup
<Creature
>& cregroup
= mPoolCreatureGroups
[pool_id
];
453 cregroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
454 SearchPair
p(guid
, pool_id
);
455 mCreatureSearchMap
.insert(p
);
457 } while (result
->NextRow());
459 sLog
.outString( ">> Loaded %u creatures in pools", count
);
465 mPoolGameobjectGroups
.resize(max_pool_id
+ 1);
466 mGameobjectSearchMap
.clear();
468 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_gameobject");
477 sLog
.outString(">> Loaded %u gameobject in pools", count
);
482 barGoLink
bar2(result
->GetRowCount());
485 Field
*fields
= result
->Fetch();
489 uint32 guid
= fields
[0].GetUInt32();
490 uint16 pool_id
= fields
[1].GetUInt16();
491 float chance
= fields
[2].GetFloat();
493 GameObjectData
const* data
= sObjectMgr
.GetGOData(guid
);
496 sLog
.outErrorDb("`pool_gameobject` has a non existing gameobject spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
499 GameObjectInfo
const* goinfo
= ObjectMgr::GetGameObjectInfo(data
->id
);
500 if (goinfo
->type
!= GAMEOBJECT_TYPE_CHEST
&&
501 goinfo
->type
!= GAMEOBJECT_TYPE_GOOBER
&&
502 goinfo
->type
!= GAMEOBJECT_TYPE_FISHINGHOLE
)
504 sLog
.outErrorDb("`pool_gameobject` has a not lootable gameobject spawn (GUID: %u, type: %u) defined for pool id (%u), skipped.", guid
, goinfo
->type
, pool_id
);
507 if (pool_id
> max_pool_id
)
509 sLog
.outErrorDb("`pool_gameobject` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
512 if (chance
< 0 || chance
> 100)
514 sLog
.outErrorDb("`pool_gameobject` has an invalid chance (%f) for gameobject guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
517 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
521 PoolObject plObject
= PoolObject(guid
, chance
);
522 PoolGroup
<GameObject
>& gogroup
= mPoolGameobjectGroups
[pool_id
];
523 gogroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
524 SearchPair
p(guid
, pool_id
);
525 mGameobjectSearchMap
.insert(p
);
527 } while( result
->NextRow() );
529 sLog
.outString( ">> Loaded %u gameobject in pools", count
);
534 mPoolPoolGroups
.resize(max_pool_id
+ 1);
536 result
= WorldDatabase
.Query("SELECT pool_id, mother_pool, chance FROM pool_pool");
545 sLog
.outString(">> Loaded %u pools in pools", count
);
550 barGoLink
bar2( result
->GetRowCount() );
553 Field
*fields
= result
->Fetch();
557 uint16 child_pool_id
= fields
[0].GetUInt16();
558 uint16 mother_pool_id
= fields
[1].GetUInt16();
559 float chance
= fields
[2].GetFloat();
561 if (mother_pool_id
> max_pool_id
)
563 sLog
.outErrorDb("`pool_pool` mother_pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",mother_pool_id
);
566 if (child_pool_id
> max_pool_id
)
568 sLog
.outErrorDb("`pool_pool` included pool_id (%i) is out of range compared to max pool id in `pool_template`, skipped.",child_pool_id
);
571 if (mother_pool_id
== child_pool_id
)
573 sLog
.outErrorDb("`pool_pool` pool_id (%i) includes itself, dead-lock detected, skipped.",child_pool_id
);
576 if (chance
< 0 || chance
> 100)
578 sLog
.outErrorDb("`pool_pool` has an invalid chance (%f) for pool id (%u) in mother pool id (%i), skipped.", chance
, child_pool_id
, mother_pool_id
);
581 PoolTemplateData
*pPoolTemplateMother
= &mPoolTemplate
[mother_pool_id
];
585 PoolObject plObject
= PoolObject(child_pool_id
, chance
);
586 PoolGroup
<Pool
>& plgroup
= mPoolPoolGroups
[mother_pool_id
];
587 plgroup
.AddEntry(plObject
, pPoolTemplateMother
->MaxLimit
);
588 SearchPair
p(child_pool_id
, mother_pool_id
);
589 mPoolSearchMap
.insert(p
);
591 } while( result
->NextRow() );
593 // Now check for circular reference
594 for(uint16 i
=0; i
<max_pool_id
; ++i
)
596 std::set
<uint16
> checkedPools
;
597 for(SearchMap::iterator poolItr
= mPoolSearchMap
.find(i
); poolItr
!= mPoolSearchMap
.end(); poolItr
= mPoolSearchMap
.find(poolItr
->second
))
599 checkedPools
.insert(poolItr
->first
);
600 if(checkedPools
.find(poolItr
->second
) != checkedPools
.end())
602 std::ostringstream ss
;
604 for (std::set
<uint16
>::const_iterator itr
=checkedPools
.begin(); itr
!=checkedPools
.end(); ++itr
)
606 ss
<< "create(s) a circular reference, which can cause the server to freeze.\nRemoving the last link between mother pool "
607 << poolItr
->first
<< " and child pool " << poolItr
->second
;
608 sLog
.outErrorDb(ss
.str().c_str());
609 mPoolPoolGroups
[poolItr
->second
].RemoveOneRelation(poolItr
->first
);
610 mPoolSearchMap
.erase(poolItr
);
617 sLog
.outString( ">> Loaded %u pools in mother pools", count
);
622 // 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
623 void PoolManager::Initialize()
625 QueryResult
*result
= WorldDatabase
.Query("SELECT DISTINCT pool_template.entry FROM pool_template LEFT JOIN game_event_pool ON pool_template.entry=game_event_pool.pool_entry LEFT JOIN pool_pool ON pool_template.entry=pool_pool.pool_id WHERE game_event_pool.pool_entry IS NULL AND pool_pool.pool_id IS NULL");
631 Field
*fields
= result
->Fetch();
632 uint16 pool_entry
= fields
[0].GetUInt16();
633 if (!CheckPool(pool_entry
))
635 sLog
.outErrorDb("Pool Id (%u) has all creatures or gameobjects with explicit chance sum <>100 and no equal chance defined. The pool system cannot pick one to spawn.", pool_entry
);
638 SpawnPool(pool_entry
, 0, 0);
639 SpawnPool(pool_entry
, 0, TYPEID_GAMEOBJECT
);
640 SpawnPool(pool_entry
, 0, TYPEID_UNIT
);
642 } while (result
->NextRow());
646 sLog
.outBasic("Pool handling system initialized, %u pools spawned.", count
);
647 m_IsPoolSystemStarted
= true;
650 // Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
651 // If it's same, the gameobject/creature is respawned only (added back to map)
652 void PoolManager::SpawnPool(uint16 pool_id
, uint32 guid
, uint32 type
)
657 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
658 mPoolCreatureGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, guid
);
660 case TYPEID_GAMEOBJECT
:
661 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
662 mPoolGameobjectGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, guid
);
665 if (!mPoolPoolGroups
[pool_id
].isEmpty())
666 mPoolPoolGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, guid
);
670 // Call to despawn a pool, all gameobjects/creatures in this pool are removed
671 void PoolManager::DespawnPool(uint16 pool_id
)
673 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
674 mPoolCreatureGroups
[pool_id
].DespawnObject();
676 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
677 mPoolGameobjectGroups
[pool_id
].DespawnObject();
679 if (!mPoolPoolGroups
[pool_id
].isEmpty())
680 mPoolPoolGroups
[pool_id
].DespawnObject();
683 // Call to update the pool when a gameobject/creature part of pool [pool_id] is ready to respawn
684 // Here we cache only the creature/gameobject whose guid is passed as parameter
685 // Then the spawn pool call will use this cache to decide
686 void PoolManager::UpdatePool(uint16 pool_id
, uint32 guid
, uint32 type
)
688 if (uint16 motherpoolid
= IsPartOfAPool(pool_id
, 0))
689 SpawnPool(motherpoolid
, 0, 0);
691 SpawnPool(pool_id
, guid
, type
);
694 // Method that tell if the gameobject/creature is part of a pool and return the pool id if yes
695 uint16
PoolManager::IsPartOfAPool(uint32 guid
, uint32 type
)
697 if (type
== 0) // pool of pool
699 SearchMap::const_iterator itr
= mPoolSearchMap
.find(guid
);
700 if (itr
!= mPoolSearchMap
.end())
703 else if (type
== TYPEID_GAMEOBJECT
)
705 SearchMap::const_iterator itr
= mGameobjectSearchMap
.find(guid
);
706 if (itr
!= mGameobjectSearchMap
.end())
711 SearchMap::const_iterator itr
= mCreatureSearchMap
.find(guid
);
712 if (itr
!= mCreatureSearchMap
.end())
718 // Method that check chance integrity of the creatures and gameobjects in this pool
719 bool PoolManager::CheckPool(uint16 pool_id
)
721 return pool_id
<= max_pool_id
&&
722 mPoolGameobjectGroups
[pool_id
].CheckPool() &&
723 mPoolCreatureGroups
[pool_id
].CheckPool() &&
724 mPoolPoolGroups
[pool_id
].CheckPool();
727 // Method that tell if a creature or gameobject in pool_id is spawned currently
728 bool PoolManager::IsSpawnedObject(uint16 pool_id
, uint32 guid
, uint32 type
)
730 if (pool_id
> max_pool_id
)
733 return mPoolPoolGroups
[pool_id
].IsSpawnedObject(guid
);
734 else if (type
== TYPEID_GAMEOBJECT
)
735 return mPoolGameobjectGroups
[pool_id
].IsSpawnedObject(guid
);
737 return mPoolCreatureGroups
[pool_id
].IsSpawnedObject(guid
);