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 "PoolHandler.h"
20 #include "ObjectMgr.h"
21 #include "ProgressBar.h"
23 #include "MapManager.h"
24 #include "Policies/SingletonImp.h"
26 INSTANTIATE_SINGLETON_1(PoolHandler
);
28 ////////////////////////////////////////////////////////////
29 // Methods of template class PoolGroup
31 // Method to add a gameobject/creature guid to the proper list depending on pool type and chance value
33 void PoolGroup
<T
>::AddEntry(PoolObject
& poolitem
, uint32 maxentries
)
35 if (poolitem
.chance
!= 0 && maxentries
== 1)
36 ExplicitlyChanced
.push_back(poolitem
);
38 EqualChanced
.push_back(poolitem
);
41 // Method to check the chances are proper in this object pool
43 bool PoolGroup
<T
>::CheckPool(void)
45 if (EqualChanced
.size() == 0)
48 for (uint32 i
=0; i
<ExplicitlyChanced
.size(); ++i
)
49 chance
+= ExplicitlyChanced
[i
].chance
;
50 if (chance
!= 100 && chance
!= 0)
56 // Method that tell if the gameobject, creature or pool is spawned currently
58 bool PoolGroup
<T
>::IsSpawnedObject(uint32 guid
)
60 for (uint32 i
= 0; i
< ExplicitlyChanced
.size(); ++i
)
61 if (ExplicitlyChanced
[i
].guid
== guid
)
62 return ExplicitlyChanced
[i
].spawned
;
63 for (uint32 i
= 0; i
< EqualChanced
.size(); ++i
)
64 if (EqualChanced
[i
].guid
== guid
)
65 return EqualChanced
[i
].spawned
;
70 void PoolGroup
<T
>::RollOne(int32
& index
, PoolObjectList
** store
, uint32 triggerFrom
)
72 if (!ExplicitlyChanced
.empty())
74 float roll
= (float)rand_chance();
76 for (uint32 i
= 0; i
< ExplicitlyChanced
.size(); ++i
)
78 roll
-= ExplicitlyChanced
[i
].chance
;
79 // Triggering object is marked as spawned at this time and can be also rolled (respawn case)
80 // so this need explicit check for this case
81 if (roll
< 0 && (!ExplicitlyChanced
[i
].spawned
|| ExplicitlyChanced
[i
].guid
== triggerFrom
))
84 *store
= &ExplicitlyChanced
;
90 if (!EqualChanced
.empty())
92 index
= irand(0, EqualChanced
.size()-1);
93 // Triggering object is marked as spawned at this time and can be also rolled (respawn case)
94 // so this need explicit check for this case
95 if (!EqualChanced
[index
].spawned
|| EqualChanced
[index
].guid
== triggerFrom
)
97 *store
= &EqualChanced
;
105 // Main method to despawn a creature or gameobject in a pool
106 // If no guid is passed, the pool is just removed (event end case)
107 // If guid is filled, cache will be used and no removal will occur, it just fill the cache
109 void PoolGroup
<T
>::DespawnObject(uint32 guid
)
111 for (size_t i
= 0; i
< EqualChanced
.size(); ++i
)
113 if (EqualChanced
[i
].spawned
)
115 if (!guid
|| EqualChanced
[i
].guid
== guid
)
117 Despawn1Object(EqualChanced
[i
].guid
);
118 EqualChanced
[i
].spawned
= false;
120 if (m_SpawnedPoolAmount
> 0)
121 --m_SpawnedPoolAmount
;
126 for (size_t i
= 0; i
< ExplicitlyChanced
.size(); ++i
)
128 if (ExplicitlyChanced
[i
].spawned
)
130 if (!guid
|| ExplicitlyChanced
[i
].guid
== guid
)
132 Despawn1Object(ExplicitlyChanced
[i
].guid
);
133 ExplicitlyChanced
[i
].spawned
= false;
135 if (m_SpawnedPoolAmount
> 0)
136 --m_SpawnedPoolAmount
;
142 // Method that is actualy doing the removal job on one creature
144 void PoolGroup
<Creature
>::Despawn1Object(uint32 guid
)
146 if (CreatureData
const* data
= objmgr
.GetCreatureData(guid
))
148 objmgr
.RemoveCreatureFromGrid(guid
, data
);
150 if (Creature
* pCreature
= ObjectAccessor::GetCreatureInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
)))
151 pCreature
->AddObjectToRemoveList();
155 // Same on one gameobject
157 void PoolGroup
<GameObject
>::Despawn1Object(uint32 guid
)
159 if (GameObjectData
const* data
= objmgr
.GetGOData(guid
))
161 objmgr
.RemoveGameobjectFromGrid(guid
, data
);
163 if (GameObject
* pGameobject
= ObjectAccessor::GetGameObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
)))
164 pGameobject
->AddObjectToRemoveList();
170 void PoolGroup
<Pool
>::Despawn1Object(uint32 child_pool_id
)
172 poolhandler
.DespawnPool(child_pool_id
);
175 // Method for a pool only to remove any found record causing a circular dependency loop
177 void PoolGroup
<Pool
>::RemoveOneRelation(uint16 child_pool_id
)
179 for (PoolObjectList::iterator itr
= ExplicitlyChanced
.begin(); itr
!= ExplicitlyChanced
.end(); ++itr
)
181 if(itr
->guid
== child_pool_id
)
183 ExplicitlyChanced
.erase(itr
);
187 for (PoolObjectList::iterator itr
= EqualChanced
.begin(); itr
!= EqualChanced
.end(); ++itr
)
189 if(itr
->guid
== child_pool_id
)
191 EqualChanced
.erase(itr
);
198 void PoolGroup
<T
>::SpawnObject(uint32 limit
, uint32 triggerFrom
)
200 uint32 lastDespawned
= 0;
201 int count
= limit
- m_SpawnedPoolAmount
;
203 // If triggered from some object respawn this object is still marked as spawned
204 // and also counted into m_SpawnedPoolAmount so we need increase count to be
209 // This will try to spawn the rest of pool, not guaranteed
210 for (int i
= 0; i
< count
; ++i
)
213 PoolObjectList
* store
;
215 RollOne(index
, &store
, triggerFrom
);
218 if ((*store
)[index
].guid
== lastDespawned
)
221 if ((*store
)[index
].guid
== triggerFrom
)
223 (*store
)[index
].spawned
= ReSpawn1Object(triggerFrom
);
228 (*store
)[index
].spawned
= Spawn1Object((*store
)[index
].guid
);
232 // One spawn one despawn no count increase
233 DespawnObject(triggerFrom
);
234 lastDespawned
= triggerFrom
;
238 ++m_SpawnedPoolAmount
;
242 // Method that is actualy doing the spawn job on 1 creature
244 bool PoolGroup
<Creature
>::Spawn1Object(uint32 guid
)
246 if (CreatureData
const* data
= objmgr
.GetCreatureData(guid
))
248 objmgr
.AddCreatureToGrid(guid
, data
);
250 // Spawn if necessary (loaded grids only)
251 Map
* map
= const_cast<Map
*>(MapManager::Instance().CreateBaseMap(data
->mapid
));
252 // We use spawn coords to spawn
253 if (!map
->Instanceable() && map
->IsLoaded(data
->posX
, data
->posY
))
255 Creature
* pCreature
= new Creature
;
256 //sLog.outDebug("Spawning creature %u",guid);
257 if (!pCreature
->LoadFromDB(guid
, map
))
270 // Same for 1 gameobject
272 bool PoolGroup
<GameObject
>::Spawn1Object(uint32 guid
)
274 if (GameObjectData
const* data
= objmgr
.GetGOData(guid
))
276 objmgr
.AddGameobjectToGrid(guid
, data
);
277 // Spawn if necessary (loaded grids only)
278 // this base map checked as non-instanced and then only existed
279 Map
* map
= const_cast<Map
*>(MapManager::Instance().CreateBaseMap(data
->mapid
));
280 // We use current coords to unspawn, not spawn coords since creature can have changed grid
281 if (!map
->Instanceable() && map
->IsLoaded(data
->posX
, data
->posY
))
283 GameObject
* pGameobject
= new GameObject
;
284 //sLog.outDebug("Spawning gameobject %u", guid);
285 if (!pGameobject
->LoadFromDB(guid
, map
))
292 if (pGameobject
->isSpawnedByDefault())
293 map
->Add(pGameobject
);
303 bool PoolGroup
<Pool
>::Spawn1Object(uint32 child_pool_id
)
305 poolhandler
.SpawnPool(child_pool_id
, 0, 0);
306 poolhandler
.SpawnPool(child_pool_id
, 0, TYPEID_GAMEOBJECT
);
307 poolhandler
.SpawnPool(child_pool_id
, 0, TYPEID_UNIT
);
311 // Method that does the respawn job on the specified creature
313 bool PoolGroup
<Creature
>::ReSpawn1Object(uint32 guid
)
315 if (CreatureData
const* data
= objmgr
.GetCreatureData(guid
))
317 if (Creature
* pCreature
= ObjectAccessor::GetCreatureInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
)))
318 pCreature
->GetMap()->Add(pCreature
);
324 // Same for 1 gameobject
326 bool PoolGroup
<GameObject
>::ReSpawn1Object(uint32 guid
)
328 if (GameObjectData
const* data
= objmgr
.GetGOData(guid
))
330 if (GameObject
* pGameobject
= ObjectAccessor::GetGameObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
)))
331 pGameobject
->GetMap()->Add(pGameobject
);
337 // Nothing to do for a child Pool
339 bool PoolGroup
<Pool
>::ReSpawn1Object(uint32
/*guid*/)
345 ////////////////////////////////////////////////////////////
346 // Methods of class PoolHandler
348 PoolHandler::PoolHandler()
350 m_IsPoolSystemStarted
= false;
353 void PoolHandler::LoadFromDB()
355 QueryResult
*result
= WorldDatabase
.Query("SELECT MAX(entry) FROM pool_template");
358 sLog
.outString(">> Table pool_template is empty.");
364 Field
*fields
= result
->Fetch();
365 max_pool_id
= fields
[0].GetUInt16();
369 mPoolTemplate
.resize(max_pool_id
+ 1);
371 result
= WorldDatabase
.Query("SELECT entry,max_limit FROM pool_template");
374 mPoolTemplate
.clear();
375 sLog
.outString(">> Table pool_template is empty:");
382 barGoLink
bar(result
->GetRowCount());
386 Field
*fields
= result
->Fetch();
390 uint16 pool_id
= fields
[0].GetUInt16();
392 PoolTemplateData
& pPoolTemplate
= mPoolTemplate
[pool_id
];
393 pPoolTemplate
.MaxLimit
= fields
[1].GetUInt32();
395 } while (result
->NextRow());
398 sLog
.outString( ">> Loaded %u objects pools", count
);
403 mPoolCreatureGroups
.resize(max_pool_id
+ 1);
404 mCreatureSearchMap
.clear();
406 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_creature");
415 sLog
.outString(">> Loaded %u creatures in pools", count
);
420 barGoLink
bar2(result
->GetRowCount());
423 Field
*fields
= result
->Fetch();
427 uint32 guid
= fields
[0].GetUInt32();
428 uint16 pool_id
= fields
[1].GetUInt16();
429 float chance
= fields
[2].GetFloat();
431 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
434 sLog
.outErrorDb("`pool_creature` has a non existing creature spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
437 if (pool_id
> max_pool_id
)
439 sLog
.outErrorDb("`pool_creature` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
442 if (chance
< 0 || chance
> 100)
444 sLog
.outErrorDb("`pool_creature` has an invalid chance (%f) for creature guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
447 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
450 PoolObject plObject
= PoolObject(guid
, chance
);
451 PoolGroup
<Creature
>& cregroup
= mPoolCreatureGroups
[pool_id
];
452 cregroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
453 SearchPair
p(guid
, pool_id
);
454 mCreatureSearchMap
.insert(p
);
456 } while (result
->NextRow());
458 sLog
.outString( ">> Loaded %u creatures in pools", count
);
464 mPoolGameobjectGroups
.resize(max_pool_id
+ 1);
465 mGameobjectSearchMap
.clear();
467 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_gameobject");
476 sLog
.outString(">> Loaded %u gameobject in pools", count
);
481 barGoLink
bar2(result
->GetRowCount());
484 Field
*fields
= result
->Fetch();
488 uint32 guid
= fields
[0].GetUInt32();
489 uint16 pool_id
= fields
[1].GetUInt16();
490 float chance
= fields
[2].GetFloat();
492 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
495 sLog
.outErrorDb("`pool_gameobject` has a non existing gameobject spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
498 GameObjectInfo
const* goinfo
= ObjectMgr::GetGameObjectInfo(data
->id
);
499 if (goinfo
->type
!= GAMEOBJECT_TYPE_CHEST
&&
500 goinfo
->type
!= GAMEOBJECT_TYPE_GOOBER
&&
501 goinfo
->type
!= GAMEOBJECT_TYPE_FISHINGHOLE
)
503 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
);
506 if (pool_id
> max_pool_id
)
508 sLog
.outErrorDb("`pool_gameobject` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
511 if (chance
< 0 || chance
> 100)
513 sLog
.outErrorDb("`pool_gameobject` has an invalid chance (%f) for gameobject guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
516 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
520 PoolObject plObject
= PoolObject(guid
, chance
);
521 PoolGroup
<GameObject
>& gogroup
= mPoolGameobjectGroups
[pool_id
];
522 gogroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
523 SearchPair
p(guid
, pool_id
);
524 mGameobjectSearchMap
.insert(p
);
526 } while( result
->NextRow() );
528 sLog
.outString( ">> Loaded %u gameobject in pools", count
);
533 mPoolPoolGroups
.resize(max_pool_id
+ 1);
535 result
= WorldDatabase
.Query("SELECT pool_id, mother_pool, chance FROM pool_pool");
544 sLog
.outString(">> Loaded %u pools in pools", count
);
549 barGoLink
bar2( result
->GetRowCount() );
552 Field
*fields
= result
->Fetch();
556 uint16 child_pool_id
= fields
[0].GetUInt16();
557 uint16 mother_pool_id
= fields
[1].GetUInt16();
558 float chance
= fields
[2].GetFloat();
560 if (mother_pool_id
> max_pool_id
)
562 sLog
.outErrorDb("`pool_pool` mother_pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",mother_pool_id
);
565 if (child_pool_id
> max_pool_id
)
567 sLog
.outErrorDb("`pool_pool` included pool_id (%i) is out of range compared to max pool id in `pool_template`, skipped.",child_pool_id
);
570 if (mother_pool_id
== child_pool_id
)
572 sLog
.outErrorDb("`pool_pool` pool_id (%i) includes itself, dead-lock detected, skipped.",child_pool_id
);
575 if (chance
< 0 || chance
> 100)
577 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
);
580 PoolTemplateData
*pPoolTemplateMother
= &mPoolTemplate
[mother_pool_id
];
584 PoolObject plObject
= PoolObject(child_pool_id
, chance
);
585 PoolGroup
<Pool
>& plgroup
= mPoolPoolGroups
[mother_pool_id
];
586 plgroup
.AddEntry(plObject
, pPoolTemplateMother
->MaxLimit
);
587 SearchPair
p(child_pool_id
, mother_pool_id
);
588 mPoolSearchMap
.insert(p
);
590 } while( result
->NextRow() );
592 // Now check for circular reference
593 for(uint16 i
=0; i
<max_pool_id
; ++i
)
595 std::set
<uint16
> checkedPools
;
596 for(SearchMap::iterator poolItr
= mPoolSearchMap
.find(i
); poolItr
!= mPoolSearchMap
.end(); poolItr
= mPoolSearchMap
.find(poolItr
->second
))
598 checkedPools
.insert(poolItr
->first
);
599 if(checkedPools
.find(poolItr
->second
) != checkedPools
.end())
601 std::ostringstream ss
;
603 for (std::set
<uint16
>::const_iterator itr
=checkedPools
.begin(); itr
!=checkedPools
.end(); ++itr
)
605 ss
<< "create(s) a circular reference, which can cause the server to freeze.\nRemoving the last link between mother pool "
606 << poolItr
->first
<< " and child pool " << poolItr
->second
;
607 sLog
.outErrorDb(ss
.str().c_str());
608 mPoolPoolGroups
[poolItr
->second
].RemoveOneRelation(poolItr
->first
);
609 mPoolSearchMap
.erase(poolItr
);
616 sLog
.outString( ">> Loaded %u pools in mother pools", count
);
621 // 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
622 void PoolHandler::Initialize()
624 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");
630 Field
*fields
= result
->Fetch();
631 uint16 pool_entry
= fields
[0].GetUInt16();
632 if (!CheckPool(pool_entry
))
634 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
);
637 SpawnPool(pool_entry
, 0, 0);
638 SpawnPool(pool_entry
, 0, TYPEID_GAMEOBJECT
);
639 SpawnPool(pool_entry
, 0, TYPEID_UNIT
);
641 } while (result
->NextRow());
645 sLog
.outBasic("Pool handling system initialized, %u pools spawned.", count
);
646 m_IsPoolSystemStarted
= true;
649 // Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
650 // If it's same, the gameobject/creature is respawned only (added back to map)
651 void PoolHandler::SpawnPool(uint16 pool_id
, uint32 guid
, uint32 type
)
656 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
657 mPoolCreatureGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, guid
);
659 case TYPEID_GAMEOBJECT
:
660 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
661 mPoolGameobjectGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, guid
);
664 if (!mPoolPoolGroups
[pool_id
].isEmpty())
665 mPoolPoolGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, guid
);
669 // Call to despawn a pool, all gameobjects/creatures in this pool are removed
670 void PoolHandler::DespawnPool(uint16 pool_id
)
672 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
673 mPoolCreatureGroups
[pool_id
].DespawnObject();
675 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
676 mPoolGameobjectGroups
[pool_id
].DespawnObject();
678 if (!mPoolPoolGroups
[pool_id
].isEmpty())
679 mPoolPoolGroups
[pool_id
].DespawnObject();
682 // Call to update the pool when a gameobject/creature part of pool [pool_id] is ready to respawn
683 // Here we cache only the creature/gameobject whose guid is passed as parameter
684 // Then the spawn pool call will use this cache to decide
685 void PoolHandler::UpdatePool(uint16 pool_id
, uint32 guid
, uint32 type
)
687 if (uint16 motherpoolid
= IsPartOfAPool(pool_id
, 0))
688 SpawnPool(motherpoolid
, 0, 0);
690 SpawnPool(pool_id
, guid
, type
);
693 // Method that tell if the gameobject/creature is part of a pool and return the pool id if yes
694 uint16
PoolHandler::IsPartOfAPool(uint32 guid
, uint32 type
)
696 if (type
== 0) // pool of pool
698 SearchMap::const_iterator itr
= mPoolSearchMap
.find(guid
);
699 if (itr
!= mPoolSearchMap
.end())
702 else if (type
== TYPEID_GAMEOBJECT
)
704 SearchMap::const_iterator itr
= mGameobjectSearchMap
.find(guid
);
705 if (itr
!= mGameobjectSearchMap
.end())
710 SearchMap::const_iterator itr
= mCreatureSearchMap
.find(guid
);
711 if (itr
!= mCreatureSearchMap
.end())
717 // Method that check chance integrity of the creatures and gameobjects in this pool
718 bool PoolHandler::CheckPool(uint16 pool_id
)
720 return pool_id
<= max_pool_id
&&
721 mPoolGameobjectGroups
[pool_id
].CheckPool() &&
722 mPoolCreatureGroups
[pool_id
].CheckPool() &&
723 mPoolPoolGroups
[pool_id
].CheckPool();
726 // Method that tell if a creature or gameobject in pool_id is spawned currently
727 bool PoolHandler::IsSpawnedObject(uint16 pool_id
, uint32 guid
, uint32 type
)
729 if (pool_id
> max_pool_id
)
732 return mPoolPoolGroups
[pool_id
].IsSpawnedObject(guid
);
733 else if (type
== TYPEID_GAMEOBJECT
)
734 return mPoolGameobjectGroups
[pool_id
].IsSpawnedObject(guid
);
736 return mPoolCreatureGroups
[pool_id
].IsSpawnedObject(guid
);