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
32 PoolGroup
<T
>::PoolGroup()
37 // Method to add a gameobject/creature guid to the proper list depending on pool type and chance value
39 void PoolGroup
<T
>::AddEntry(PoolObject
& poolitem
, uint32 maxentries
)
41 if (poolitem
.chance
!= 0 && maxentries
== 1)
42 ExplicitlyChanced
.push_back(poolitem
);
44 EqualChanced
.push_back(poolitem
);
47 // Method to check the chances are proper in this object pool
49 bool PoolGroup
<T
>::CheckPool(void)
51 if (EqualChanced
.size() == 0)
54 for (uint32 i
=0; i
<ExplicitlyChanced
.size(); ++i
)
55 chance
+= ExplicitlyChanced
[i
].chance
;
56 if (chance
!= 100 && chance
!= 0)
62 // Method that tell if the gameobject, creature or pool is spawned currently
64 bool PoolGroup
<T
>::IsSpawnedObject(uint32 guid
)
66 for (uint32 i
=0; i
<ExplicitlyChanced
.size(); ++i
)
67 if (ExplicitlyChanced
[i
].guid
== guid
)
68 return ExplicitlyChanced
[i
].spawned
;
69 for (uint32 i
=0; i
<EqualChanced
.size(); ++i
)
70 if (EqualChanced
[i
].guid
== guid
)
71 return EqualChanced
[i
].spawned
;
75 // Method that return a guid of a rolled creature or gameobject
76 // Note: Copy from loot system because it's very similar and only few things change
78 uint32 PoolGroup
<T
>::RollOne(void)
80 if (!ExplicitlyChanced
.empty()) // First explicitly chanced entries are checked
82 float roll
= rand_chance();
84 for (uint32 i
=0; i
<ExplicitlyChanced
.size(); ++i
)
86 roll
-= ExplicitlyChanced
[i
].chance
;
88 return ExplicitlyChanced
[i
].guid
;
91 if (!EqualChanced
.empty())
92 return EqualChanced
[irand(0, EqualChanced
.size()-1)].guid
;
94 return 0; // None found
97 // Main method to despawn a creature or gameobject in a pool
98 // If no guid is passed, the pool is just removed (event end case)
99 // If guid is filled, cache will be used and no removal will occur, it just fill the cache
101 void PoolGroup
<T
>::DespawnObject(uint32 guid
)
103 for (int i
=0; i
<EqualChanced
.size(); ++i
)
105 if (EqualChanced
[i
].spawned
)
107 if (!guid
|| EqualChanced
[i
].guid
== guid
)
110 CacheValue
= EqualChanced
[i
].guid
;
112 Despawn1Object(EqualChanced
[i
].guid
);
114 EqualChanced
[i
].spawned
= false;
121 // Method that is actualy doing the removal job on one creature
123 void PoolGroup
<Creature
>::Despawn1Object(uint32 guid
)
125 if (CreatureData
const* data
= objmgr
.GetCreatureData(guid
))
127 objmgr
.RemoveCreatureFromGrid(guid
, data
);
129 if (Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
), (Creature
*)NULL
))
130 pCreature
->AddObjectToRemoveList();
134 // Same on one gameobject
136 void PoolGroup
<GameObject
>::Despawn1Object(uint32 guid
)
138 if (GameObjectData
const* data
= objmgr
.GetGOData(guid
))
140 objmgr
.RemoveGameobjectFromGrid(guid
, data
);
142 if (GameObject
* pGameobject
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
), (GameObject
*)NULL
))
143 pGameobject
->AddObjectToRemoveList();
149 void PoolGroup
<Pool
>::Despawn1Object(uint32 child_pool_id
)
151 poolhandler
.DespawnPool(child_pool_id
);
154 // Method for a pool only to remove any found record causing a circular dependency loop
156 void PoolGroup
<Pool
>::RemoveOneRelation(uint16 child_pool_id
)
158 for (PoolObjectList::iterator itr
= ExplicitlyChanced
.begin(); itr
!= ExplicitlyChanced
.end(); ++itr
)
160 if(itr
->guid
== child_pool_id
)
162 ExplicitlyChanced
.erase(itr
);
166 for (PoolObjectList::iterator itr
= EqualChanced
.begin(); itr
!= EqualChanced
.end(); ++itr
)
168 if(itr
->guid
== child_pool_id
)
170 EqualChanced
.erase(itr
);
176 // Method that Spawn 1+ creatures or gameobject
177 // if cache is false (initialization or event start), X creatures are spawned with X <= limit (< if limit higher that the number of creatures in pool)
178 // if cache is true, this means only one has to be spawned (or respawned if the rolled one is same as cached one)
180 void PoolGroup
<T
>::SpawnObject(uint32 limit
, bool cache
)
182 if (limit
== 1) // This is the only case where explicit chance is used
184 uint32 roll
= RollOne();
185 if (cache
&& CacheValue
!= roll
)
186 Despawn1Object(CacheValue
);
187 CacheValue
= Spawn1Object(roll
);
189 else if (limit
< EqualChanced
.size() && Spawned
< limit
)
191 std::vector
<uint32
> IndexList
;
192 for (int i
=0; i
<EqualChanced
.size(); ++i
)
193 if (!EqualChanced
[i
].spawned
)
194 IndexList
.push_back(i
);
196 while (Spawned
< limit
&& IndexList
.size() > 0)
198 uint32 roll
= urand(1, IndexList
.size()) - 1;
199 uint32 index
= IndexList
[roll
];
200 if (!cache
|| (cache
&& EqualChanced
[index
].guid
!= CacheValue
))
203 Despawn1Object(CacheValue
);
204 EqualChanced
[index
].spawned
= Spawn1Object(EqualChanced
[index
].guid
);
207 EqualChanced
[index
].spawned
= ReSpawn1Object(EqualChanced
[index
].guid
);
209 if (EqualChanced
[index
].spawned
)
210 ++Spawned
; // limited group use the Spawned variable to store the number of actualy spawned creatures
211 std::vector
<uint32
>::iterator itr
= IndexList
.begin()+roll
;
212 IndexList
.erase(itr
);
216 else // Not enough objects in pool, so spawn all
218 for (int i
=0; i
<EqualChanced
.size(); ++i
)
219 EqualChanced
[i
].spawned
= Spawn1Object(EqualChanced
[i
].guid
);
223 // Method that is actualy doing the spawn job on 1 creature
225 bool PoolGroup
<Creature
>::Spawn1Object(uint32 guid
)
227 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
230 objmgr
.AddCreatureToGrid(guid
, data
);
232 // Spawn if necessary (loaded grids only)
233 Map
* map
= const_cast<Map
*>(MapManager::Instance().CreateBaseMap(data
->mapid
));
234 // We use spawn coords to spawn
235 if (!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
, data
->posY
))
237 Creature
* pCreature
= new Creature
;
238 //sLog.outDebug("Spawning creature %u",guid);
239 if (!pCreature
->LoadFromDB(guid
, map
))
253 // Same for 1 gameobject
255 bool PoolGroup
<GameObject
>::Spawn1Object(uint32 guid
)
257 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
260 objmgr
.AddGameobjectToGrid(guid
, data
);
261 // Spawn if necessary (loaded grids only)
262 // this base map checked as non-instanced and then only existed
263 Map
* map
= const_cast<Map
*>(MapManager::Instance().CreateBaseMap(data
->mapid
));
264 // We use current coords to unspawn, not spawn coords since creature can have changed grid
265 if (!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
, data
->posY
))
267 GameObject
* pGameobject
= new GameObject
;
268 //sLog.outDebug("Spawning gameobject %u", guid);
269 if (!pGameobject
->LoadFromDB(guid
, map
))
275 if (pGameobject
->isSpawnedByDefault())
276 map
->Add(pGameobject
);
286 bool PoolGroup
<Pool
>::Spawn1Object(uint32 child_pool_id
)
288 poolhandler
.SpawnPool(child_pool_id
);
292 // Method that does the respawn job on the specified creature
294 bool PoolGroup
<Creature
>::ReSpawn1Object(uint32 guid
)
296 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
299 if (Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
), (Creature
*)NULL
))
300 pCreature
->GetMap()->Add(pCreature
);
306 // Same for 1 gameobject
308 bool PoolGroup
<GameObject
>::ReSpawn1Object(uint32 guid
)
310 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
313 if (GameObject
* pGameobject
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
), (GameObject
*)NULL
))
314 pGameobject
->GetMap()->Add(pGameobject
);
320 // Nothing to do for a child Pool
322 bool PoolGroup
<Pool
>::ReSpawn1Object(uint32
/*guid*/)
328 ////////////////////////////////////////////////////////////
329 // Methods of class PoolHandler
331 PoolHandler::PoolHandler()
333 isSystemInit
= false;
336 void PoolHandler::LoadFromDB()
338 QueryResult
*result
= WorldDatabase
.Query("SELECT MAX(entry) FROM pool_template");
341 sLog
.outString(">> Table pool_template is empty.");
347 Field
*fields
= result
->Fetch();
348 max_pool_id
= fields
[0].GetUInt16();
352 mPoolTemplate
.resize(max_pool_id
+ 1);
354 result
= WorldDatabase
.Query("SELECT entry,max_limit FROM pool_template");
357 mPoolTemplate
.clear();
358 sLog
.outString(">> Table pool_template is empty:");
365 barGoLink
bar(result
->GetRowCount());
369 Field
*fields
= result
->Fetch();
373 uint16 pool_id
= fields
[0].GetUInt16();
375 PoolTemplateData
& pPoolTemplate
= mPoolTemplate
[pool_id
];
376 pPoolTemplate
.MaxLimit
= fields
[1].GetUInt32();
378 } while (result
->NextRow());
381 sLog
.outString( ">> Loaded %u objects pools", count
);
386 mPoolCreatureGroups
.resize(max_pool_id
+ 1);
387 mCreatureSearchMap
.clear();
389 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_creature");
398 sLog
.outString(">> Loaded %u creatures in pools", count
);
403 barGoLink
bar2(result
->GetRowCount());
406 Field
*fields
= result
->Fetch();
410 uint32 guid
= fields
[0].GetUInt32();
411 uint16 pool_id
= fields
[1].GetUInt16();
412 float chance
= fields
[2].GetFloat();
414 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
417 sLog
.outErrorDb("`pool_creature` has a non existing creature spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
420 if (pool_id
> max_pool_id
)
422 sLog
.outErrorDb("`pool_creature` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
425 if (chance
< 0 || chance
> 100)
427 sLog
.outErrorDb("`pool_creature` has an invalid chance (%f) for creature guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
430 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
433 PoolObject plObject
= PoolObject(guid
, chance
);
434 PoolGroup
<Creature
>& cregroup
= mPoolCreatureGroups
[pool_id
];
435 cregroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
436 SearchPair
p(guid
, pool_id
);
437 mCreatureSearchMap
.insert(p
);
439 } while (result
->NextRow());
441 sLog
.outString( ">> Loaded %u creatures in pools", count
);
447 mPoolGameobjectGroups
.resize(max_pool_id
+ 1);
448 mGameobjectSearchMap
.clear();
450 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_gameobject");
459 sLog
.outString(">> Loaded %u gameobject in pools", count
);
464 barGoLink
bar2(result
->GetRowCount());
467 Field
*fields
= result
->Fetch();
471 uint32 guid
= fields
[0].GetUInt32();
472 uint16 pool_id
= fields
[1].GetUInt16();
473 float chance
= fields
[2].GetFloat();
475 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
478 sLog
.outErrorDb("`pool_gameobject` has a non existing gameobject spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
481 GameObjectInfo
const* goinfo
= objmgr
.GetGameObjectInfo(data
->id
);
482 if (goinfo
->type
!= GAMEOBJECT_TYPE_CHEST
&&
483 goinfo
->type
!= GAMEOBJECT_TYPE_GOOBER
&&
484 goinfo
->type
!= GAMEOBJECT_TYPE_FISHINGHOLE
)
486 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
);
489 if (pool_id
> max_pool_id
)
491 sLog
.outErrorDb("`pool_gameobject` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
494 if (chance
< 0 || chance
> 100)
496 sLog
.outErrorDb("`pool_gameobject` has an invalid chance (%f) for gameobject guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
499 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
503 PoolObject plObject
= PoolObject(guid
, chance
);
504 PoolGroup
<GameObject
>& gogroup
= mPoolGameobjectGroups
[pool_id
];
505 gogroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
506 SearchPair
p(guid
, pool_id
);
507 mGameobjectSearchMap
.insert(p
);
509 } while( result
->NextRow() );
511 sLog
.outString( ">> Loaded %u gameobject in pools", count
);
516 mPoolPoolGroups
.resize(max_pool_id
+ 1);
518 result
= WorldDatabase
.Query("SELECT pool_id, mother_pool, chance FROM pool_pool");
527 sLog
.outString(">> Loaded %u pools in pools", count
);
532 barGoLink
bar2( result
->GetRowCount() );
535 Field
*fields
= result
->Fetch();
539 uint16 child_pool_id
= fields
[0].GetUInt16();
540 uint16 mother_pool_id
= fields
[1].GetUInt16();
541 float chance
= fields
[2].GetFloat();
543 if (mother_pool_id
> max_pool_id
)
545 sLog
.outErrorDb("`pool_pool` mother_pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",mother_pool_id
);
548 if (child_pool_id
> max_pool_id
)
550 sLog
.outErrorDb("`pool_pool` included pool_id (%i) is out of range compared to max pool id in `pool_template`, skipped.",child_pool_id
);
553 if (mother_pool_id
== child_pool_id
)
555 sLog
.outErrorDb("`pool_pool` pool_id (%i) includes itself, dead-lock detected, skipped.",child_pool_id
);
558 if (chance
< 0 || chance
> 100)
560 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
);
563 PoolTemplateData
*pPoolTemplateMother
= &mPoolTemplate
[mother_pool_id
];
567 PoolObject plObject
= PoolObject(child_pool_id
, chance
);
568 PoolGroup
<Pool
>& plgroup
= mPoolPoolGroups
[mother_pool_id
];
569 plgroup
.AddEntry(plObject
, pPoolTemplateMother
->MaxLimit
);
570 SearchPair
p(child_pool_id
, mother_pool_id
);
571 mPoolSearchMap
.insert(p
);
573 } while( result
->NextRow() );
575 // Now check for circular reference
576 for(uint16 i
=0; i
<max_pool_id
; ++i
)
578 std::set
<uint16
> checkedPools
;
579 for(SearchMap::iterator poolItr
= mPoolSearchMap
.find(i
); poolItr
!= mPoolSearchMap
.end(); poolItr
= mPoolSearchMap
.find(poolItr
->second
))
581 checkedPools
.insert(poolItr
->first
);
582 if(checkedPools
.find(poolItr
->second
) != checkedPools
.end())
584 std::ostringstream ss
;
586 for (std::set
<uint16
>::const_iterator itr
=checkedPools
.begin(); itr
!=checkedPools
.end(); ++itr
)
588 ss
<< "create(s) a circular reference, which can cause the server to freeze.\nRemoving the last link between mother pool "
589 << poolItr
->first
<< " and child pool " << poolItr
->second
;
590 sLog
.outErrorDb(ss
.str().c_str());
591 mPoolPoolGroups
[poolItr
->second
].RemoveOneRelation(poolItr
->first
);
592 mPoolSearchMap
.erase(poolItr
);
599 sLog
.outString( ">> Loaded %u pools in mother pools", count
);
604 // 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
605 void PoolHandler::Initialize()
607 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");
613 Field
*fields
= result
->Fetch();
614 uint16 pool_entry
= fields
[0].GetUInt16();
615 if (!CheckPool(pool_entry
))
617 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
);
620 SpawnPool(pool_entry
);
622 } while (result
->NextRow());
626 sLog
.outBasic("Pool handling system initialized, %u pools spawned.", count
);
630 // Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
631 // If it's same, the gameobject/creature is respawned only (added back to map)
632 void PoolHandler::SpawnPool(uint16 pool_id
, bool cache
)
634 if (!mPoolPoolGroups
[pool_id
].isEmpty())
635 mPoolPoolGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, cache
);
636 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
637 mPoolGameobjectGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, cache
);
638 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
639 mPoolCreatureGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, cache
);
642 // Call to despawn a pool, all gameobjects/creatures in this pool are removed
643 void PoolHandler::DespawnPool(uint16 pool_id
)
645 if (!mPoolPoolGroups
[pool_id
].isEmpty())
646 mPoolPoolGroups
[pool_id
].DespawnObject();
647 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
648 mPoolGameobjectGroups
[pool_id
].DespawnObject();
649 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
650 mPoolCreatureGroups
[pool_id
].DespawnObject();
653 // Call to update the pool when a gameobject/creature part of pool [pool_id] is ready to respawn
654 // Here we cache only the creature/gameobject whose guid is passed as parameter
655 // Then the spawn pool call will use this cache to decide
656 void PoolHandler::UpdatePool(uint16 pool_id
, uint32 guid
, uint32 type
)
658 uint16 motherpoolid
= IsPartOfAPool(pool_id
, 0);
661 mPoolPoolGroups
[motherpoolid
].DespawnObject(pool_id
);
662 else if (type
== TYPEID_GAMEOBJECT
&& !mPoolGameobjectGroups
[pool_id
].isEmpty())
663 mPoolGameobjectGroups
[pool_id
].DespawnObject(guid
);
664 else if (type
!= TYPEID_GAMEOBJECT
&& !mPoolCreatureGroups
[pool_id
].isEmpty())
665 mPoolCreatureGroups
[pool_id
].DespawnObject(guid
);
668 SpawnPool(motherpoolid
, true);
670 SpawnPool(pool_id
, true);
673 // Method that tell if the gameobject/creature is part of a pool and return the pool id if yes
674 uint16
PoolHandler::IsPartOfAPool(uint32 guid
, uint32 type
)
676 if (type
== 0) // pool of pool
678 SearchMap::const_iterator itr
= mPoolSearchMap
.find(guid
);
679 if (itr
!= mPoolSearchMap
.end())
682 else if (type
== TYPEID_GAMEOBJECT
)
684 SearchMap::const_iterator itr
= mGameobjectSearchMap
.find(guid
);
685 if (itr
!= mGameobjectSearchMap
.end())
690 SearchMap::const_iterator itr
= mCreatureSearchMap
.find(guid
);
691 if (itr
!= mCreatureSearchMap
.end())
697 // Method that check chance integrity of the creatures and gameobjects in this pool
698 bool PoolHandler::CheckPool(uint16 pool_id
)
700 return pool_id
<= max_pool_id
&&
701 mPoolGameobjectGroups
[pool_id
].CheckPool() &&
702 mPoolCreatureGroups
[pool_id
].CheckPool() &&
703 mPoolPoolGroups
[pool_id
].CheckPool();
706 // Method that tell if a creature or gameobject in pool_id is spawned currently
707 bool PoolHandler::IsSpawnedObject(uint16 pool_id
, uint32 guid
, uint32 type
)
709 if (pool_id
> max_pool_id
)
712 return mPoolPoolGroups
[pool_id
].IsSpawnedObject(guid
);
713 else if (type
== TYPEID_GAMEOBJECT
)
714 return mPoolGameobjectGroups
[pool_id
].IsSpawnedObject(guid
);
716 return mPoolCreatureGroups
[pool_id
].IsSpawnedObject(guid
);