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
))
131 pCreature
->CleanupsBeforeDelete();
132 pCreature
->AddObjectToRemoveList();
137 // Same on one gameobject
139 void PoolGroup
<GameObject
>::Despawn1Object(uint32 guid
)
141 if (GameObjectData
const* data
= objmgr
.GetGOData(guid
))
143 objmgr
.RemoveGameobjectFromGrid(guid
, data
);
145 if (GameObject
* pGameobject
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
), (GameObject
*)NULL
))
146 pGameobject
->AddObjectToRemoveList();
152 void PoolGroup
<Pool
>::Despawn1Object(uint32 child_pool_id
)
154 poolhandler
.DespawnPool(child_pool_id
);
157 // Method for a pool only to remove any found record causing a circular dependency loop
159 void PoolGroup
<Pool
>::RemoveOneRelation(uint16 child_pool_id
)
161 for (PoolObjectList::iterator itr
= ExplicitlyChanced
.begin(); itr
!= ExplicitlyChanced
.end(); ++itr
)
163 if(itr
->guid
== child_pool_id
)
165 ExplicitlyChanced
.erase(itr
);
169 for (PoolObjectList::iterator itr
= EqualChanced
.begin(); itr
!= EqualChanced
.end(); ++itr
)
171 if(itr
->guid
== child_pool_id
)
173 EqualChanced
.erase(itr
);
179 // Method that Spawn 1+ creatures or gameobject
180 // 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)
181 // if cache is true, this means only one has to be spawned (or respawned if the rolled one is same as cached one)
183 void PoolGroup
<T
>::SpawnObject(uint32 limit
, bool cache
)
185 if (limit
== 1) // This is the only case where explicit chance is used
187 uint32 roll
= RollOne();
188 if (cache
&& CacheValue
!= roll
)
189 Despawn1Object(CacheValue
);
190 CacheValue
= Spawn1Object(roll
);
192 else if (limit
< EqualChanced
.size() && Spawned
< limit
)
194 std::vector
<uint32
> IndexList
;
195 for (int i
=0; i
<EqualChanced
.size(); ++i
)
196 if (!EqualChanced
[i
].spawned
)
197 IndexList
.push_back(i
);
199 while (Spawned
< limit
&& IndexList
.size() > 0)
201 uint32 roll
= urand(1, IndexList
.size()) - 1;
202 uint32 index
= IndexList
[roll
];
203 if (!cache
|| (cache
&& EqualChanced
[index
].guid
!= CacheValue
))
206 Despawn1Object(CacheValue
);
207 EqualChanced
[index
].spawned
= Spawn1Object(EqualChanced
[index
].guid
);
210 EqualChanced
[index
].spawned
= ReSpawn1Object(EqualChanced
[index
].guid
);
212 if (EqualChanced
[index
].spawned
)
213 ++Spawned
; // limited group use the Spawned variable to store the number of actualy spawned creatures
214 std::vector
<uint32
>::iterator itr
= IndexList
.begin()+roll
;
215 IndexList
.erase(itr
);
219 else // Not enough objects in pool, so spawn all
221 for (int i
=0; i
<EqualChanced
.size(); ++i
)
222 EqualChanced
[i
].spawned
= Spawn1Object(EqualChanced
[i
].guid
);
226 // Method that is actualy doing the spawn job on 1 creature
228 bool PoolGroup
<Creature
>::Spawn1Object(uint32 guid
)
230 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
233 objmgr
.AddCreatureToGrid(guid
, data
);
235 // Spawn if necessary (loaded grids only)
236 Map
* map
= const_cast<Map
*>(MapManager::Instance().GetBaseMap(data
->mapid
));
237 // We use spawn coords to spawn
238 if (!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
, data
->posY
))
240 Creature
* pCreature
= new Creature
;
241 //sLog.outDebug("Spawning creature %u",guid);
242 if (!pCreature
->LoadFromDB(guid
, map
))
256 // Same for 1 gameobject
258 bool PoolGroup
<GameObject
>::Spawn1Object(uint32 guid
)
260 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
263 objmgr
.AddGameobjectToGrid(guid
, data
);
264 // Spawn if necessary (loaded grids only)
265 // this base map checked as non-instanced and then only existed
266 Map
* map
= const_cast<Map
*>(MapManager::Instance().GetBaseMap(data
->mapid
));
267 // We use current coords to unspawn, not spawn coords since creature can have changed grid
268 if (!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
, data
->posY
))
270 GameObject
* pGameobject
= new GameObject
;
271 //sLog.outDebug("Spawning gameobject %u", guid);
272 if (!pGameobject
->LoadFromDB(guid
, map
))
278 if (pGameobject
->isSpawnedByDefault())
279 map
->Add(pGameobject
);
289 bool PoolGroup
<Pool
>::Spawn1Object(uint32 child_pool_id
)
291 poolhandler
.SpawnPool(child_pool_id
);
295 // Method that does the respawn job on the specified creature
297 bool PoolGroup
<Creature
>::ReSpawn1Object(uint32 guid
)
299 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
302 if (Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_UNIT
), (Creature
*)NULL
))
303 pCreature
->GetMap()->Add(pCreature
);
309 // Same for 1 gameobject
311 bool PoolGroup
<GameObject
>::ReSpawn1Object(uint32 guid
)
313 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
316 if (GameObject
* pGameobject
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(guid
, data
->id
, HIGHGUID_GAMEOBJECT
), (GameObject
*)NULL
))
317 pGameobject
->GetMap()->Add(pGameobject
);
323 // Nothing to do for a child Pool
325 bool PoolGroup
<Pool
>::ReSpawn1Object(uint32
/*guid*/)
331 ////////////////////////////////////////////////////////////
332 // Methods of class PoolHandler
334 PoolHandler::PoolHandler()
336 isSystemInit
= false;
339 void PoolHandler::LoadFromDB()
341 QueryResult
*result
= WorldDatabase
.Query("SELECT MAX(entry) FROM pool_template");
344 sLog
.outString(">> Table pool_template is empty.");
350 Field
*fields
= result
->Fetch();
351 max_pool_id
= fields
[0].GetUInt16();
355 mPoolTemplate
.resize(max_pool_id
+ 1);
357 result
= WorldDatabase
.Query("SELECT entry,max_limit FROM pool_template");
360 mPoolTemplate
.clear();
361 sLog
.outString(">> Table pool_template is empty:");
368 barGoLink
bar(result
->GetRowCount());
372 Field
*fields
= result
->Fetch();
376 uint16 pool_id
= fields
[0].GetUInt16();
378 PoolTemplateData
& pPoolTemplate
= mPoolTemplate
[pool_id
];
379 pPoolTemplate
.MaxLimit
= fields
[1].GetUInt32();
381 } while (result
->NextRow());
384 sLog
.outString( ">> Loaded %u objects pools", count
);
389 mPoolCreatureGroups
.resize(max_pool_id
+ 1);
390 mCreatureSearchMap
.clear();
392 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_creature");
401 sLog
.outString(">> Loaded %u creatures in pools", count
);
406 barGoLink
bar2(result
->GetRowCount());
409 Field
*fields
= result
->Fetch();
413 uint32 guid
= fields
[0].GetUInt32();
414 uint16 pool_id
= fields
[1].GetUInt16();
415 float chance
= fields
[2].GetFloat();
417 CreatureData
const* data
= objmgr
.GetCreatureData(guid
);
420 sLog
.outErrorDb("`pool_creature` has a non existing creature spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
423 if (pool_id
> max_pool_id
)
425 sLog
.outErrorDb("`pool_creature` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
428 if (chance
< 0 || chance
> 100)
430 sLog
.outErrorDb("`pool_creature` has an invalid chance (%f) for creature guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
433 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
436 PoolObject plObject
= PoolObject(guid
, chance
);
437 PoolGroup
<Creature
>& cregroup
= mPoolCreatureGroups
[pool_id
];
438 cregroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
439 SearchPair
p(guid
, pool_id
);
440 mCreatureSearchMap
.insert(p
);
442 } while (result
->NextRow());
444 sLog
.outString( ">> Loaded %u creatures in pools", count
);
450 mPoolGameobjectGroups
.resize(max_pool_id
+ 1);
451 mGameobjectSearchMap
.clear();
453 result
= WorldDatabase
.Query("SELECT guid, pool_entry, chance FROM pool_gameobject");
462 sLog
.outString(">> Loaded %u gameobject in pools", count
);
467 barGoLink
bar2(result
->GetRowCount());
470 Field
*fields
= result
->Fetch();
474 uint32 guid
= fields
[0].GetUInt32();
475 uint16 pool_id
= fields
[1].GetUInt16();
476 float chance
= fields
[2].GetFloat();
478 GameObjectData
const* data
= objmgr
.GetGOData(guid
);
481 sLog
.outErrorDb("`pool_gameobject` has a non existing gameobject spawn (GUID: %u) defined for pool id (%u), skipped.", guid
, pool_id
);
484 GameObjectInfo
const* goinfo
= objmgr
.GetGameObjectInfo(data
->id
);
485 if (goinfo
->type
!= GAMEOBJECT_TYPE_CHEST
&&
486 goinfo
->type
!= GAMEOBJECT_TYPE_GOOBER
&&
487 goinfo
->type
!= GAMEOBJECT_TYPE_FISHINGHOLE
)
489 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
);
492 if (pool_id
> max_pool_id
)
494 sLog
.outErrorDb("`pool_gameobject` pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",pool_id
);
497 if (chance
< 0 || chance
> 100)
499 sLog
.outErrorDb("`pool_gameobject` has an invalid chance (%f) for gameobject guid (%u) in pool id (%i), skipped.", chance
, guid
, pool_id
);
502 PoolTemplateData
*pPoolTemplate
= &mPoolTemplate
[pool_id
];
506 PoolObject plObject
= PoolObject(guid
, chance
);
507 PoolGroup
<GameObject
>& gogroup
= mPoolGameobjectGroups
[pool_id
];
508 gogroup
.AddEntry(plObject
, pPoolTemplate
->MaxLimit
);
509 SearchPair
p(guid
, pool_id
);
510 mGameobjectSearchMap
.insert(p
);
512 } while( result
->NextRow() );
514 sLog
.outString( ">> Loaded %u gameobject in pools", count
);
519 mPoolPoolGroups
.resize(max_pool_id
+ 1);
521 result
= WorldDatabase
.Query("SELECT pool_id, mother_pool, chance FROM pool_pool");
530 sLog
.outString(">> Loaded %u pools in pools", count
);
535 barGoLink
bar2( result
->GetRowCount() );
538 Field
*fields
= result
->Fetch();
542 uint16 child_pool_id
= fields
[0].GetUInt16();
543 uint16 mother_pool_id
= fields
[1].GetUInt16();
544 float chance
= fields
[2].GetFloat();
546 if (mother_pool_id
> max_pool_id
)
548 sLog
.outErrorDb("`pool_pool` mother_pool id (%i) is out of range compared to max pool id in `pool_template`, skipped.",mother_pool_id
);
551 if (child_pool_id
> max_pool_id
)
553 sLog
.outErrorDb("`pool_pool` included pool_id (%i) is out of range compared to max pool id in `pool_template`, skipped.",child_pool_id
);
556 if (mother_pool_id
== child_pool_id
)
558 sLog
.outErrorDb("`pool_pool` pool_id (%i) includes itself, dead-lock detected, skipped.",child_pool_id
);
561 if (chance
< 0 || chance
> 100)
563 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
);
566 PoolTemplateData
*pPoolTemplateMother
= &mPoolTemplate
[mother_pool_id
];
570 PoolObject plObject
= PoolObject(child_pool_id
, chance
);
571 PoolGroup
<Pool
>& plgroup
= mPoolPoolGroups
[mother_pool_id
];
572 plgroup
.AddEntry(plObject
, pPoolTemplateMother
->MaxLimit
);
573 SearchPair
p(child_pool_id
, mother_pool_id
);
574 mPoolSearchMap
.insert(p
);
576 } while( result
->NextRow() );
578 // Now check for circular reference
579 for(uint16 i
=0; i
<max_pool_id
; ++i
)
581 std::set
<uint16
> checkedPools
;
582 for(SearchMap::iterator poolItr
= mPoolSearchMap
.find(i
); poolItr
!= mPoolSearchMap
.end(); poolItr
= mPoolSearchMap
.find(poolItr
->second
))
584 checkedPools
.insert(poolItr
->first
);
585 if(checkedPools
.find(poolItr
->second
) != checkedPools
.end())
587 std::ostringstream ss
;
589 for (std::set
<uint16
>::const_iterator itr
=checkedPools
.begin(); itr
!=checkedPools
.end(); ++itr
)
591 ss
<< "create(s) a circular reference, which can cause the server to freeze.\nRemoving the last link between mother pool "
592 << poolItr
->first
<< " and child pool " << poolItr
->second
;
593 sLog
.outErrorDb(ss
.str().c_str());
594 mPoolPoolGroups
[poolItr
->second
].RemoveOneRelation(poolItr
->first
);
595 mPoolSearchMap
.erase(poolItr
);
602 sLog
.outString( ">> Loaded %u pools in mother pools", count
);
607 // 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
608 void PoolHandler::Initialize()
610 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");
616 Field
*fields
= result
->Fetch();
617 uint16 pool_entry
= fields
[0].GetUInt16();
618 if (!CheckPool(pool_entry
))
620 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
);
623 SpawnPool(pool_entry
);
625 } while (result
->NextRow());
629 sLog
.outBasic("Pool handling system initialized, %u pools spawned.", count
);
633 // Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
634 // If it's same, the gameobject/creature is respawned only (added back to map)
635 void PoolHandler::SpawnPool(uint16 pool_id
, bool cache
)
637 if (!mPoolPoolGroups
[pool_id
].isEmpty())
638 mPoolPoolGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, cache
);
639 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
640 mPoolGameobjectGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, cache
);
641 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
642 mPoolCreatureGroups
[pool_id
].SpawnObject(mPoolTemplate
[pool_id
].MaxLimit
, cache
);
645 // Call to despawn a pool, all gameobjects/creatures in this pool are removed
646 void PoolHandler::DespawnPool(uint16 pool_id
)
648 if (!mPoolPoolGroups
[pool_id
].isEmpty())
649 mPoolPoolGroups
[pool_id
].DespawnObject();
650 if (!mPoolGameobjectGroups
[pool_id
].isEmpty())
651 mPoolGameobjectGroups
[pool_id
].DespawnObject();
652 if (!mPoolCreatureGroups
[pool_id
].isEmpty())
653 mPoolCreatureGroups
[pool_id
].DespawnObject();
656 // Call to update the pool when a gameobject/creature part of pool [pool_id] is ready to respawn
657 // Here we cache only the creature/gameobject whose guid is passed as parameter
658 // Then the spawn pool call will use this cache to decide
659 void PoolHandler::UpdatePool(uint16 pool_id
, uint32 guid
, uint32 type
)
661 uint16 motherpoolid
= IsPartOfAPool(pool_id
, 0);
664 mPoolPoolGroups
[motherpoolid
].DespawnObject(pool_id
);
665 else if (type
== TYPEID_GAMEOBJECT
&& !mPoolGameobjectGroups
[pool_id
].isEmpty())
666 mPoolGameobjectGroups
[pool_id
].DespawnObject(guid
);
667 else if (type
!= TYPEID_GAMEOBJECT
&& !mPoolCreatureGroups
[pool_id
].isEmpty())
668 mPoolCreatureGroups
[pool_id
].DespawnObject(guid
);
671 SpawnPool(motherpoolid
, true);
673 SpawnPool(pool_id
, true);
676 // Method that tell if the gameobject/creature is part of a pool and return the pool id if yes
677 uint16
PoolHandler::IsPartOfAPool(uint32 guid
, uint32 type
)
679 if (type
== 0) // pool of pool
681 SearchMap::const_iterator itr
= mPoolSearchMap
.find(guid
);
682 if (itr
!= mPoolSearchMap
.end())
685 else if (type
== TYPEID_GAMEOBJECT
)
687 SearchMap::const_iterator itr
= mGameobjectSearchMap
.find(guid
);
688 if (itr
!= mGameobjectSearchMap
.end())
693 SearchMap::const_iterator itr
= mCreatureSearchMap
.find(guid
);
694 if (itr
!= mCreatureSearchMap
.end())
700 // Method that check chance integrity of the creatures and gameobjects in this pool
701 bool PoolHandler::CheckPool(uint16 pool_id
)
703 return pool_id
<= max_pool_id
&&
704 mPoolGameobjectGroups
[pool_id
].CheckPool() &&
705 mPoolCreatureGroups
[pool_id
].CheckPool() &&
706 mPoolPoolGroups
[pool_id
].CheckPool();
709 // Method that tell if a creature or gameobject in pool_id is spawned currently
710 bool PoolHandler::IsSpawnedObject(uint16 pool_id
, uint32 guid
, uint32 type
)
712 if (pool_id
> max_pool_id
)
715 return mPoolPoolGroups
[pool_id
].IsSpawnedObject(guid
);
716 else if (type
== TYPEID_GAMEOBJECT
)
717 return mPoolGameobjectGroups
[pool_id
].IsSpawnedObject(guid
);
719 return mPoolCreatureGroups
[pool_id
].IsSpawnedObject(guid
);