/* * Copyright (C) 2012-2017 CypherCore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Framework.Constants; using Framework.Database; using Game.Entities; using Game.Maps; using System; using System.Collections.Generic; using System.Linq; namespace Game { public class PoolManager : Singleton { PoolManager() { } public void Initialize() { mQuestSearchMap.Clear(); mGameobjectSearchMap.Clear(); mCreatureSearchMap.Clear(); } public void LoadFromDB() { // Pool templates { uint oldMSTime = Time.GetMSTime(); SQLResult result = DB.World.Query("SELECT entry, max_limit FROM pool_template"); if (result.IsEmpty()) { mPoolTemplate.Clear(); Log.outInfo(LogFilter.ServerLoading, "Loaded 0 object pools. DB table `pool_template` is empty."); return; } uint count = 0; do { uint pool_id = result.Read(0); PoolTemplateData pPoolTemplate = new PoolTemplateData(); pPoolTemplate.MaxLimit = result.Read(1); mPoolTemplate[pool_id] = pPoolTemplate; ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} objects pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } // Creatures Log.outInfo(LogFilter.ServerLoading, "Loading Creatures Pooling Data..."); { uint oldMSTime = Time.GetMSTime(); // 1 2 3 SQLResult result = DB.World.Query("SELECT guid, pool_entry, chance FROM pool_creature"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creatures in pools. DB table `pool_creature` is empty."); } else { uint count = 0; do { ulong guid = result.Read(0); uint pool_id = result.Read(1); float chance = result.Read(2); CreatureData data = Global.ObjectMgr.GetCreatureData(guid); if (data == null) { Log.outError(LogFilter.Sql, "`pool_creature` has a non existing creature spawn (GUID: {0}) defined for pool id ({1}), skipped.", guid, pool_id); continue; } if (!mPoolTemplate.ContainsKey(pool_id)) { Log.outError(LogFilter.Sql, "`pool_creature` pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", pool_id); continue; } if (chance < 0 || chance > 100) { Log.outError(LogFilter.Sql, "`pool_creature` has an invalid chance ({0}) for creature guid ({1}) in pool id ({2}), skipped.", chance, guid, pool_id); continue; } PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id]; PoolObject plObject = new PoolObject(guid, chance); if (!mPoolCreatureGroups.ContainsKey(pool_id)) mPoolCreatureGroups[pool_id] = new PoolGroup(); PoolGroup cregroup = mPoolCreatureGroups[pool_id]; cregroup.SetPoolId(pool_id); cregroup.AddEntry(plObject, pPoolTemplate.MaxLimit); mCreatureSearchMap.Add(guid, pool_id); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } } // Gameobjects Log.outInfo(LogFilter.ServerLoading, "Loading Gameobject Pooling Data..."); { uint oldMSTime = Time.GetMSTime(); // 1 2 3 SQLResult result = DB.World.Query("SELECT guid, pool_entry, chance FROM pool_gameobject"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobjects in pools. DB table `pool_gameobject` is empty."); } else { uint count = 0; do { ulong guid = result.Read(0); uint pool_id = result.Read(1); float chance = result.Read(2); GameObjectData data = Global.ObjectMgr.GetGOData(guid); if (data == null) { Log.outError(LogFilter.Sql, "`pool_gameobject` has a non existing gameobject spawn (GUID: {0}) defined for pool id ({1}), skipped.", guid, pool_id); continue; } GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(data.id); if (goinfo.type != GameObjectTypes.Chest && goinfo.type != GameObjectTypes.FishingHole && goinfo.type != GameObjectTypes.GatheringNode && goinfo.type != GameObjectTypes.Goober) { Log.outError(LogFilter.Sql, "`pool_gameobject` has a not lootable gameobject spawn (GUID: {0}, type: {1}) defined for pool id ({2}), skipped.", guid, goinfo.type, pool_id); continue; } if (!mPoolTemplate.ContainsKey(pool_id)) { Log.outError(LogFilter.Sql, "`pool_gameobject` pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", pool_id); continue; } if (chance < 0 || chance > 100) { Log.outError(LogFilter.Sql, "`pool_gameobject` has an invalid chance ({0}) for gameobject guid ({1}) in pool id ({2}), skipped.", chance, guid, pool_id); continue; } PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id]; PoolObject plObject = new PoolObject(guid, chance); if (!mPoolGameobjectGroups.ContainsKey(pool_id)) mPoolGameobjectGroups[pool_id] = new PoolGroup(); PoolGroup gogroup = mPoolGameobjectGroups[pool_id]; gogroup.SetPoolId(pool_id); gogroup.AddEntry(plObject, pPoolTemplate.MaxLimit); mGameobjectSearchMap.Add(guid, pool_id); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject in pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } } // Pool of pools Log.outInfo(LogFilter.ServerLoading, "Loading Mother Pooling Data..."); { uint oldMSTime = Time.GetMSTime(); // 1 2 3 SQLResult result = DB.World.Query("SELECT pool_id, mother_pool, chance FROM pool_pool"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 pools in pools"); } else { uint count = 0; do { uint child_pool_id = result.Read(0); uint mother_pool_id = result.Read(1); float chance = result.Read(2); if (!mPoolTemplate.ContainsKey(mother_pool_id)) { Log.outError(LogFilter.Sql, "`pool_pool` mother_pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", mother_pool_id); continue; } if (!mPoolTemplate.ContainsKey(child_pool_id)) { Log.outError(LogFilter.Sql, "`pool_pool` included pool_id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", child_pool_id); continue; } if (mother_pool_id == child_pool_id) { Log.outError(LogFilter.Sql, "`pool_pool` pool_id ({0}) includes itself, dead-lock detected, skipped.", child_pool_id); continue; } if (chance < 0 || chance > 100) { Log.outError(LogFilter.Sql, "`pool_pool` has an invalid chance ({0}) for pool id ({1}) in mother pool id ({2}), skipped.", chance, child_pool_id, mother_pool_id); continue; } PoolTemplateData pPoolTemplateMother = mPoolTemplate[mother_pool_id]; PoolObject plObject = new PoolObject(child_pool_id, chance); if (!mPoolPoolGroups.ContainsKey(mother_pool_id)) mPoolPoolGroups[mother_pool_id] = new PoolGroup(); PoolGroup plgroup = mPoolPoolGroups[mother_pool_id]; plgroup.SetPoolId(mother_pool_id); plgroup.AddEntry(plObject, pPoolTemplateMother.MaxLimit); mPoolSearchMap.Add(child_pool_id, mother_pool_id); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pools in mother pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } } Log.outInfo(LogFilter.ServerLoading, "Loading Quest Pooling Data..."); { uint oldMSTime = Time.GetMSTime(); PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_QUEST_POOLS); SQLResult result = DB.World.Query(stmt); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quests in pools"); } else { List creBounds; List goBounds; Dictionary poolTypeMap = new Dictionary(); uint count = 0; do { uint entry = result.Read(0); uint pool_id = result.Read(1); if (!poolTypeMap.ContainsKey(pool_id)) poolTypeMap[pool_id] = 0; Quest quest = Global.ObjectMgr.GetQuestTemplate(entry); if (quest == null) { Log.outError(LogFilter.Sql, "`pool_quest` has a non existing quest template (Entry: {0}) defined for pool id ({1}), skipped.", entry, pool_id); continue; } if (!mPoolTemplate.ContainsKey(pool_id)) { Log.outError(LogFilter.Sql, "`pool_quest` pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", pool_id); continue; } if (!quest.IsDailyOrWeekly()) { Log.outError(LogFilter.Sql, "`pool_quest` has an quest ({0}) which is not daily or weekly in pool id ({1}), use ExclusiveGroup instead, skipped.", entry, pool_id); continue; } if (poolTypeMap[pool_id] == eQuestTypes.None) poolTypeMap[pool_id] = quest.IsDaily() ? eQuestTypes.Daily : eQuestTypes.Weekly; eQuestTypes currType = quest.IsDaily() ? eQuestTypes.Daily : eQuestTypes.Weekly; if (poolTypeMap[pool_id] != currType) { Log.outError(LogFilter.Sql, "`pool_quest` quest {0} is {1} but pool ({2}) is specified for {3}, mixing not allowed, skipped.", entry, currType, pool_id, poolTypeMap[pool_id]); continue; } creBounds = mQuestCreatureRelation.LookupByKey(entry); goBounds = mQuestGORelation.LookupByKey(entry); if (creBounds.Empty() && goBounds.Empty()) { Log.outError(LogFilter.Sql, "`pool_quest` lists entry ({0}) as member of pool ({1}) but is not started anywhere, skipped.", entry, pool_id); continue; } PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id]; PoolObject plObject = new PoolObject(entry, 0.0f); if (!mPoolQuestGroups.ContainsKey(pool_id)) mPoolQuestGroups[pool_id] = new PoolGroup(); PoolGroup questgroup = mPoolQuestGroups[pool_id]; questgroup.SetPoolId(pool_id); questgroup.AddEntry(plObject, pPoolTemplate.MaxLimit); mQuestSearchMap.Add(entry, pool_id); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests in pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } } // 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 Log.outInfo(LogFilter.ServerLoading, "Starting objects pooling system..."); { uint oldMSTime = Time.GetMSTime(); SQLResult result = DB.World.Query("SELECT DISTINCT pool_template.entry, pool_pool.pool_id, pool_pool.mother_pool 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"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Pool handling system initialized, 0 pools spawned."); } else { uint count = 0; do { uint pool_entry = result.Read(0); uint pool_pool_id = result.Read(1); if (!CheckPool(pool_entry)) { if (pool_pool_id != 0) // 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, // however that could recursively invalidate entire chain of mother pools. It can be done in the future but for now we'll do nothing. Log.outError(LogFilter.Sql, "Pool Id {0} has no equal chance pooled entites defined and explicit chance sum is not 100. This broken pool is a child pool of Id {1} and cannot be safely removed.", pool_entry, result.Read(2)); else Log.outError(LogFilter.Sql, "Pool Id {0} has no equal chance pooled entites defined and explicit chance sum is not 100. The pool will not be spawned.", pool_entry); continue; } // Don't spawn child pools, they are spawned recursively by their parent pools if (pool_pool_id == 0) { SpawnPool(pool_entry); count++; } } while (result.NextRow()); Log.outDebug(LogFilter.Pool, "Pool handling system initialized, {0} pools spawned in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } } } public void SaveQuestsToDB() { SQLTransaction trans = new SQLTransaction(); foreach (var questPoolGroup in mPoolQuestGroups.Values) { if (questPoolGroup.isEmpty()) continue; PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_QUEST_POOL_SAVE); stmt.AddValue(0, questPoolGroup.GetPoolId()); trans.Append(stmt); } foreach (var pair in mQuestSearchMap) { if (IsSpawnedObject(pair.Key)) { PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_QUEST_POOL_SAVE); stmt.AddValue(0, pair.Value); stmt.AddValue(1, pair.Key); trans.Append(stmt); } } DB.Characters.CommitTransaction(trans); } public void ChangeDailyQuests() { foreach (var questPoolGroup in mPoolQuestGroups.Values) { Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)questPoolGroup.GetFirstEqualChancedObjectId()); if (quest != null) { if (quest.IsWeekly()) continue; UpdatePool(questPoolGroup.GetPoolId(), 1); // anything non-zero means don't load from db } } SaveQuestsToDB(); } public void ChangeWeeklyQuests() { foreach (var questPoolGroup in mPoolQuestGroups.Values) { Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)questPoolGroup.GetFirstEqualChancedObjectId()); if (quest != null) { if (quest.IsDaily()) continue; UpdatePool(questPoolGroup.GetPoolId(), 1); } } SaveQuestsToDB(); } void SpawnPool(uint pool_id, ulong db_guid) { switch (typeof(T).Name) { case "Creature": if (mPoolCreatureGroups.ContainsKey(pool_id) && !mPoolCreatureGroups[pool_id].isEmpty()) mPoolCreatureGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); break; case "GameObject": if (mPoolGameobjectGroups.ContainsKey(pool_id) && !mPoolGameobjectGroups[pool_id].isEmpty()) mPoolGameobjectGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); break; case "Pool": if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].isEmpty()) mPoolPoolGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); break; case "Quest": if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].isEmpty()) mPoolQuestGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); break; } } public void SpawnPool(uint pool_id) { SpawnPool(pool_id, 0); SpawnPool(pool_id, 0); SpawnPool(pool_id, 0); SpawnPool(pool_id, 0); } public void DespawnPool(uint pool_id) { if (mPoolCreatureGroups.ContainsKey(pool_id) && !mPoolCreatureGroups[pool_id].isEmpty()) mPoolCreatureGroups[pool_id].DespawnObject(mSpawnedData); if (mPoolGameobjectGroups.ContainsKey(pool_id) && !mPoolGameobjectGroups[pool_id].isEmpty()) mPoolGameobjectGroups[pool_id].DespawnObject(mSpawnedData); if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].isEmpty()) mPoolPoolGroups[pool_id].DespawnObject(mSpawnedData); if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].isEmpty()) mPoolQuestGroups[pool_id].DespawnObject(mSpawnedData); } public bool CheckPool(uint pool_id) { return mPoolTemplate.ContainsKey(pool_id) && (!mPoolGameobjectGroups.ContainsKey(pool_id) || mPoolGameobjectGroups[pool_id].CheckPool()) && (!mPoolCreatureGroups.ContainsKey(pool_id) || mPoolCreatureGroups[pool_id].CheckPool()) && (!mPoolPoolGroups.ContainsKey(pool_id) || mPoolPoolGroups[pool_id].CheckPool()) && (!mPoolQuestGroups.ContainsKey(pool_id) || mPoolQuestGroups[pool_id].CheckPool()); } public void UpdatePool(uint pool_id, ulong db_guid_or_pool_id) { uint motherpoolid = IsPartOfAPool(pool_id); if (motherpoolid != 0) SpawnPool(motherpoolid, pool_id); else SpawnPool(pool_id, db_guid_or_pool_id); } public uint IsPartOfAPool(ulong db_guid) { switch (typeof(T).Name) { case "Creature": return mCreatureSearchMap.LookupByKey(db_guid); case "GameObject": return mGameobjectSearchMap.LookupByKey(db_guid); case "Pool": return mPoolSearchMap.LookupByKey(db_guid); case "Quest": return mQuestSearchMap.LookupByKey(db_guid); } return 0; } public enum eQuestTypes { None = 0, Daily = 1, Weekly = 2 } public bool IsSpawnedObject(ulong db_guid_or_pool_id) { return mSpawnedData.IsActiveObject(db_guid_or_pool_id); } public MultiMap mQuestCreatureRelation = new MultiMap(); public MultiMap mQuestGORelation = new MultiMap(); Dictionary mPoolTemplate = new Dictionary(); Dictionary> mPoolCreatureGroups = new Dictionary>(); Dictionary> mPoolGameobjectGroups = new Dictionary>(); Dictionary> mPoolPoolGroups = new Dictionary>(); Dictionary> mPoolQuestGroups = new Dictionary>(); Dictionary mCreatureSearchMap = new Dictionary(); Dictionary mGameobjectSearchMap = new Dictionary(); Dictionary mPoolSearchMap = new Dictionary(); Dictionary mQuestSearchMap = new Dictionary(); // dynamic data ActivePoolData mSpawnedData = new ActivePoolData(); } public class PoolGroup { public PoolGroup() { poolId = 0; } public void AddEntry(PoolObject poolitem, uint maxentries) { if (poolitem.chance != 0 && maxentries == 1) ExplicitlyChanced.Add(poolitem); else EqualChanced.Add(poolitem); } public bool CheckPool() { if (EqualChanced.Empty()) { float chance = 0; for (int i = 0; i < ExplicitlyChanced.Count; ++i) chance += ExplicitlyChanced[i].chance; if (chance != 100 && chance != 0) return false; } return true; } PoolObject RollOne(ActivePoolData spawns, ulong triggerFrom) { if (!ExplicitlyChanced.Empty()) { float roll = (float)RandomHelper.randChance(); for (int i = 0; i < ExplicitlyChanced.Count; ++i) { roll -= ExplicitlyChanced[i].chance; // Triggering object is marked as spawned at this time and can be also rolled (respawn case) // so this need explicit check for this case if (roll < 0 && (ExplicitlyChanced[i].guid == triggerFrom || !spawns.IsActiveObject(ExplicitlyChanced[i].guid))) return ExplicitlyChanced[i]; } } if (!EqualChanced.Empty()) { int index = RandomHelper.IRand(0, EqualChanced.Count - 1); // Triggering object is marked as spawned at this time and can be also rolled (respawn case) // so this need explicit check for this case if (EqualChanced[index].guid == triggerFrom || !spawns.IsActiveObject(EqualChanced[index].guid)) return EqualChanced[index]; } return null; } public void DespawnObject(ActivePoolData spawns, ulong guid = 0) { for (int i = 0; i < EqualChanced.Count; ++i) { // if spawned if (spawns.IsActiveObject(EqualChanced[i].guid)) { if (guid == 0 || EqualChanced[i].guid == guid) { Despawn1Object(EqualChanced[i].guid); spawns.RemoveObject(EqualChanced[i].guid, poolId); } } } for (int i = 0; i < ExplicitlyChanced.Count; ++i) { // spawned if (spawns.IsActiveObject(ExplicitlyChanced[i].guid)) { if (guid == 0 || ExplicitlyChanced[i].guid == guid) { Despawn1Object(ExplicitlyChanced[i].guid); spawns.RemoveObject(ExplicitlyChanced[i].guid, poolId); } } } } void Despawn1Object(ulong guid) { switch (typeof(T).Name) { case "Creature": { var data = Global.ObjectMgr.GetCreatureData(guid); if (data != null) { Global.ObjectMgr.RemoveCreatureFromGrid(guid, data); Map map = Global.MapMgr.CreateBaseMap(data.mapid); if (!map.Instanceable()) { var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid); foreach (var creature in creatureBounds) creature.AddObjectToRemoveList(); } } break; } case "GameObject": { var data = Global.ObjectMgr.GetGOData(guid); if (data != null) { Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data); Map map = Global.MapMgr.CreateBaseMap(data.mapid); if (!map.Instanceable()) { var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid); foreach (var go in gameobjectBounds) go.AddObjectToRemoveList(); } } break; } case "Pool": Global.PoolMgr.DespawnPool((uint)guid); break; case "Quest": // Creatures var questMap = Global.ObjectMgr.GetCreatureQuestRelationMap(); var qr = Global.PoolMgr.mQuestCreatureRelation.LookupByKey(guid); foreach (var creature in qr) { if (!questMap.ContainsKey(creature)) continue; foreach (var quest in questMap[creature].ToList()) { if (quest == guid) questMap.Remove(creature, quest); } } // Gameobjects questMap = Global.ObjectMgr.GetGOQuestRelationMap(); qr = Global.PoolMgr.mQuestGORelation.LookupByKey(guid); foreach (var go in qr) { if (!questMap.ContainsKey(go)) continue; foreach (var quest in questMap[go]) { if (quest == guid) questMap.Remove(go, quest); } } break; } } public void RemoveOneRelation(uint child_pool_id) { if (typeof(T).Name != "Pool") return; foreach (var poolObject in ExplicitlyChanced) { if (poolObject.guid == child_pool_id) { ExplicitlyChanced.Remove(poolObject); break; } } foreach (var poolObject in EqualChanced) { if (poolObject.guid == child_pool_id) { EqualChanced.Remove(poolObject); break; } } } public void SpawnObject(ActivePoolData spawns, uint limit, ulong triggerFrom) { if (typeof(T).Name == "Quest") { SpawnQuestObject(spawns, limit, triggerFrom); return; } ulong lastDespawned = 0; int count = (int)(limit - spawns.GetActiveObjectCount(poolId)); // If triggered from some object respawn this object is still marked as spawned // and also counted into m_SpawnedPoolAmount so we need increase count to be // spawned by 1 if (triggerFrom != 0) ++count; // This will try to spawn the rest of pool, not guaranteed for (int i = 0; i < count; ++i) { PoolObject obj = RollOne(spawns, triggerFrom); if (obj == null) continue; if (obj.guid == lastDespawned) continue; if (obj.guid == triggerFrom) { ReSpawn1Object(obj); triggerFrom = 0; continue; } spawns.ActivateObject(obj.guid, poolId); Spawn1Object(obj); if (triggerFrom != 0) { // One spawn one despawn no count increase DespawnObject(spawns, triggerFrom); lastDespawned = triggerFrom; triggerFrom = 0; } } } void SpawnQuestObject(ActivePoolData spawns, uint limit, ulong triggerFrom) { Log.outDebug(LogFilter.Pool, "PoolGroup: Spawning pool {0}", poolId); // load state from db if (triggerFrom == 0) { PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_POOL_QUEST_SAVE); stmt.AddValue(0, poolId); SQLResult result = DB.Characters.Query(stmt); if (!result.IsEmpty()) { do { uint questId = result.Read(0); spawns.ActivateObject(questId, poolId); PoolObject tempObj = new PoolObject(questId, 0.0f); Spawn1Object(tempObj); --limit; } while (result.NextRow() && limit != 0); return; } } List currentQuests = spawns.GetActiveQuests(); List newQuests = new List(); // always try to select different quests foreach (var poolObject in EqualChanced) { if (spawns.IsActiveObject(poolObject.guid)) continue; newQuests.Add(poolObject.guid); } // clear the pool DespawnObject(spawns); // recycle minimal amount of quests if possible count is lower than limit if (limit > newQuests.Count && !currentQuests.Empty()) { do { ulong questId = currentQuests.SelectRandom(); newQuests.Add(questId); currentQuests.Remove(questId); } while (newQuests.Count < limit && !currentQuests.Empty()); // failsafe } if (newQuests.Empty()) return; // activate random quests do { ulong questId = newQuests.SelectRandom(); spawns.ActivateObject(questId, poolId); PoolObject tempObj = new PoolObject(questId, 0.0f); Spawn1Object(tempObj); newQuests.Remove(questId); --limit; } while (limit != 0 && !newQuests.Empty()); // if we are here it means the pool is initialized at startup and did not have previous saved state if (triggerFrom == 0) Global.PoolMgr.SaveQuestsToDB(); } void Spawn1Object(PoolObject obj) { switch (typeof(T).Name) { case "Creature": CreatureData data = Global.ObjectMgr.GetCreatureData(obj.guid); if (data != null) { Global.ObjectMgr.AddCreatureToGrid(obj.guid, data); // Spawn if necessary (loaded grids only) Map map = Global.MapMgr.CreateBaseMap(data.mapid); // We use spawn coords to spawn if (!map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) { Creature creature = new Creature(); if (!creature.LoadCreatureFromDB(obj.guid, map)) return; } } break; case "GameObject": GameObjectData data_ = Global.ObjectMgr.GetGOData(obj.guid); if (data_ != null) { Global.ObjectMgr.AddGameObjectToGrid(obj.guid, data_); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed Map map = Global.MapMgr.CreateBaseMap(data_.mapid); // We use current coords to unspawn, not spawn coords since creature can have changed grid if (!map.Instanceable() && map.IsGridLoaded(data_.posX, data_.posY)) { GameObject pGameobject = new GameObject(); if (!pGameobject.LoadGameObjectFromDB(obj.guid, map, false)) return; else { if (pGameobject.isSpawnedByDefault()) map.AddToMap(pGameobject); } } } break; case "Pool": Global.PoolMgr.SpawnPool((uint)obj.guid); break; case "Quest": // Creatures var questMap = Global.ObjectMgr.GetCreatureQuestRelationMap(); var qr = Global.PoolMgr.mQuestCreatureRelation.LookupByKey(obj.guid); foreach (var creature in qr) { Log.outDebug(LogFilter.Pool, "PoolGroup: Adding quest {0} to creature {1}", obj.guid, creature); questMap.Add(creature, (uint)obj.guid); } // Gameobjects questMap = Global.ObjectMgr.GetGOQuestRelationMap(); qr = Global.PoolMgr.mQuestGORelation.LookupByKey(obj.guid); foreach (var go in qr) { Log.outDebug(LogFilter.Pool, "PoolGroup: Adding quest {0} to GO {1}", obj.guid, go); questMap.Add(go, (uint)obj.guid); } break; } } void ReSpawn1Object(PoolObject obj) { // GameObject/Creature is still on map, nothing to do } public void SetPoolId(uint pool_id) { poolId = pool_id; } public bool isEmpty() { return ExplicitlyChanced.Empty() && EqualChanced.Empty(); } public ulong GetFirstEqualChancedObjectId() { if (EqualChanced.Empty()) return 0; return EqualChanced.FirstOrDefault().guid; } public uint GetPoolId() { return poolId; } uint poolId; List ExplicitlyChanced = new List(); List EqualChanced = new List(); } public class ActivePoolData { public uint GetActiveObjectCount(uint pool_id) { return mSpawnedPools.LookupByKey(pool_id); } public bool IsActiveObject(ulong db_guid) { switch (typeof(T).Name) { case "Creature": return mSpawnedCreatures.Contains(db_guid); case "GameObject": return mSpawnedGameobjects.Contains(db_guid); case "Pool": return mSpawnedPools.ContainsKey(db_guid); case "Quest": return mActiveQuests.Contains(db_guid); default: return false; } } public void ActivateObject(ulong db_guid, uint pool_id) { switch (typeof(T).Name) { case "Creature": mSpawnedCreatures.Add(db_guid); break; case "GameObject": mSpawnedGameobjects.Add(db_guid); break; case "Pool": mSpawnedPools[db_guid] = 0; break; case "Quest": mActiveQuests.Add(db_guid); break; default: return; } if (!mSpawnedPools.ContainsKey(pool_id)) mSpawnedPools[pool_id] = 0; ++mSpawnedPools[pool_id]; } public void RemoveObject(ulong db_guid, uint pool_id) { switch (typeof(T).Name) { case "Creature": mSpawnedCreatures.Remove(db_guid); break; case "GameObject": mSpawnedGameobjects.Remove(db_guid); break; case "Pool": mSpawnedPools.Remove(db_guid); break; case "Quest": mActiveQuests.Remove(db_guid); break; default: return; } uint val = mSpawnedPools[pool_id]; if (val > 0) --val; } public List GetActiveQuests() { return mActiveQuests; } // a copy of the set List mSpawnedCreatures = new List(); List mSpawnedGameobjects = new List(); List mActiveQuests = new List(); Dictionary mSpawnedPools = new Dictionary(); } public class PoolObject { public PoolObject(ulong _guid, float _chance) { guid = _guid; chance = Math.Abs(_chance); } public ulong guid; public float chance; } public struct PoolTemplateData { public uint MaxLimit; } public class Pool { } // for Pool of Pool case }