aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Pools
diff options
context:
space:
mode:
authorTreeston <treeston.mmoc@gmail.com>2019-08-04 12:22:57 +0200
committerGitHub <noreply@github.com>2019-08-04 12:22:57 +0200
commita5e73e41c0e813e674bb0a644e0052052435494e (patch)
treecabbeadc1e07635f1a8fc53599895613cbe61723 /src/server/game/Pools
parent15e85f882fdb7b5d1d48302907e76c993ee4e923 (diff)
Core/Pooling: Quest pooling rewrite: (PR#23627)
- Split quest pooling from PoolMgr (into QuestPoolMgr) - Proper saving/restoring on server restart - No more hacking into sObjectMgr to insert/remove available quests
Diffstat (limited to 'src/server/game/Pools')
-rw-r--r--src/server/game/Pools/PoolMgr.cpp338
-rw-r--r--src/server/game/Pools/PoolMgr.h30
-rw-r--r--src/server/game/Pools/QuestPools.cpp296
-rw-r--r--src/server/game/Pools/QuestPools.h65
4 files changed, 361 insertions, 368 deletions
diff --git a/src/server/game/Pools/PoolMgr.cpp b/src/server/game/Pools/PoolMgr.cpp
index 22e9e5b4049..810895d3420 100644
--- a/src/server/game/Pools/PoolMgr.cpp
+++ b/src/server/game/Pools/PoolMgr.cpp
@@ -54,13 +54,6 @@ TC_GAME_API bool ActivePoolData::IsActiveObject<Pool>(uint32 sub_pool_id) const
return mSpawnedPools.find(sub_pool_id) != mSpawnedPools.end();
}
-// Method that tell if a quest can be started
-template<>
-TC_GAME_API bool ActivePoolData::IsActiveObject<Quest>(uint32 quest_id) const
-{
- return mActiveQuests.find(quest_id) != mActiveQuests.end();
-}
-
template<>
void ActivePoolData::ActivateObject<Creature>(uint32 db_guid, uint32 pool_id)
{
@@ -83,13 +76,6 @@ void ActivePoolData::ActivateObject<Pool>(uint32 sub_pool_id, uint32 pool_id)
}
template<>
-void ActivePoolData::ActivateObject<Quest>(uint32 quest_id, uint32 pool_id)
-{
- mActiveQuests.insert(quest_id);
- ++mSpawnedPools[pool_id];
-}
-
-template<>
void ActivePoolData::RemoveObject<Creature>(uint32 db_guid, uint32 pool_id)
{
mSpawnedCreatures.erase(db_guid);
@@ -116,15 +102,6 @@ void ActivePoolData::RemoveObject<Pool>(uint32 sub_pool_id, uint32 pool_id)
--val;
}
-template<>
-void ActivePoolData::RemoveObject<Quest>(uint32 quest_id, uint32 pool_id)
-{
- mActiveQuests.erase(quest_id);
- uint32& val = mSpawnedPools[pool_id];
- if (val > 0)
- --val;
-}
-
////////////////////////////////////////////////////////////
// Methods of template class PoolGroup
@@ -244,49 +221,6 @@ void PoolGroup<Pool>::Despawn1Object(uint32 child_pool_id)
sPoolMgr->DespawnPool(child_pool_id);
}
-// Same on one quest
-template<>
-void PoolGroup<Quest>::Despawn1Object(uint32 quest_id)
-{
- // Creatures
- QuestRelations* questMap = sObjectMgr->GetCreatureQuestRelationMap();
- PooledQuestRelationBoundsNC qr = sPoolMgr->mQuestCreatureRelation.equal_range(quest_id);
- for (PooledQuestRelation::iterator itr = qr.first; itr != qr.second; ++itr)
- {
- QuestRelations::iterator qitr = questMap->find(itr->second);
- if (qitr == questMap->end())
- continue;
- QuestRelations::iterator lastElement = questMap->upper_bound(itr->second);
- for (; qitr != lastElement; ++qitr)
- {
- if (qitr->first == itr->second && qitr->second == itr->first)
- {
- questMap->erase(qitr); // iterator is now no more valid
- break; // but we can exit loop since the element is found
- }
- }
- }
-
- // Gameobjects
- questMap = sObjectMgr->GetGOQuestRelationMap();
- qr = sPoolMgr->mQuestGORelation.equal_range(quest_id);
- for (PooledQuestRelation::iterator itr = qr.first; itr != qr.second; ++itr)
- {
- QuestRelations::iterator qitr = questMap->find(itr->second);
- if (qitr == questMap->end())
- continue;
- QuestRelations::iterator lastElement = questMap->upper_bound(itr->second);
- for (; qitr != lastElement; ++qitr)
- {
- if (qitr->first == itr->second && qitr->second == itr->first)
- {
- questMap->erase(qitr); // iterator is now no more valid
- break; // but we can exit loop since the element is found
- }
- }
- }
-}
-
// Method for a pool only to remove any found record causing a circular dependency loop
template<>
void PoolGroup<Pool>::RemoveOneRelation(uint32 child_pool_id)
@@ -434,100 +368,6 @@ void PoolGroup<Pool>::Spawn1Object(PoolObject* obj)
sPoolMgr->SpawnPool(obj->guid);
}
-// Same for 1 quest
-template<>
-void PoolGroup<Quest>::Spawn1Object(PoolObject* obj)
-{
- // Creatures
- QuestRelations* questMap = sObjectMgr->GetCreatureQuestRelationMap();
- PooledQuestRelationBoundsNC qr = sPoolMgr->mQuestCreatureRelation.equal_range(obj->guid);
- for (PooledQuestRelation::iterator itr = qr.first; itr != qr.second; ++itr)
- {
- TC_LOG_DEBUG("pool", "PoolGroup<Quest>: Adding quest %u to creature %u", itr->first, itr->second);
- questMap->insert(QuestRelations::value_type(itr->second, itr->first));
- }
-
- // Gameobjects
- questMap = sObjectMgr->GetGOQuestRelationMap();
- qr = sPoolMgr->mQuestGORelation.equal_range(obj->guid);
- for (PooledQuestRelation::iterator itr = qr.first; itr != qr.second; ++itr)
- {
- TC_LOG_DEBUG("pool", "PoolGroup<Quest>: Adding quest %u to GO %u", itr->first, itr->second);
- questMap->insert(QuestRelations::value_type(itr->second, itr->first));
- }
-}
-
-template <>
-void PoolGroup<Quest>::SpawnObject(ActivePoolData& spawns, uint32 limit, uint32 triggerFrom)
-{
- TC_LOG_DEBUG("pool", "PoolGroup<Quest>: Spawning pool %u", poolId);
- // load state from db
- if (!triggerFrom)
- {
- PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_POOL_QUEST_SAVE);
-
- stmt->setUInt32(0, poolId);
-
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
-
- if (result)
- {
- do
- {
- uint32 questId = result->Fetch()[0].GetUInt32();
- spawns.ActivateObject<Quest>(questId, poolId);
- PoolObject tempObj(questId, 0.0f);
- Spawn1Object(&tempObj);
- --limit;
- } while (result->NextRow() && limit);
- return;
- }
- }
-
- ActivePoolObjects currentQuests = spawns.GetActiveQuests();
- ActivePoolObjects newQuests;
-
- // always try to select different quests
- for (PoolObjectList::iterator itr = EqualChanced.begin(); itr != EqualChanced.end(); ++itr)
- {
- if (spawns.IsActiveObject<Quest>(itr->guid))
- continue;
- newQuests.insert(itr->guid);
- }
-
- // clear the pool
- DespawnObject(spawns);
-
- // recycle minimal amount of quests if possible count is lower than limit
- if (limit > newQuests.size() && !currentQuests.empty())
- {
- do
- {
- uint32 questId = Trinity::Containers::SelectRandomContainerElement(currentQuests);
- newQuests.insert(questId);
- currentQuests.erase(questId);
- } while (newQuests.size() < limit && !currentQuests.empty()); // failsafe
- }
-
- if (newQuests.empty())
- return;
-
- // activate <limit> random quests
- do
- {
- uint32 questId = Trinity::Containers::SelectRandomContainerElement(newQuests);
- spawns.ActivateObject<Quest>(questId, poolId);
- PoolObject tempObj(questId, 0.0f);
- Spawn1Object(&tempObj);
- newQuests.erase(questId);
- --limit;
- } while (limit && !newQuests.empty());
-
- // if we are here it means the pool is initialized at startup and did not have previous saved state
- if (!triggerFrom)
- sPoolMgr->SaveQuestsToDB();
-}
-
// Method that does the respawn job on the specified creature
template <>
void PoolGroup<Creature>::ReSpawn1Object(PoolObject* /*obj*/)
@@ -546,10 +386,6 @@ void PoolGroup<GameObject>::ReSpawn1Object(PoolObject* /*obj*/)
template <>
void PoolGroup<Pool>::ReSpawn1Object(PoolObject* /*obj*/) { }
-// Nothing to do for a quest
-template <>
-void PoolGroup<Quest>::ReSpawn1Object(PoolObject* /*obj*/) { }
-
////////////////////////////////////////////////////////////
// Methods of class PoolMgr
@@ -557,7 +393,6 @@ PoolMgr::PoolMgr() { }
void PoolMgr::Initialize()
{
- mQuestSearchMap.clear();
mGameobjectSearchMap.clear();
mCreatureSearchMap.clear();
}
@@ -816,95 +651,6 @@ void PoolMgr::LoadFromDB()
}
}
- TC_LOG_INFO("server.loading", "Loading Quest Pooling Data...");
- {
- uint32 oldMSTime = getMSTime();
-
- PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_QUEST_POOLS);
- PreparedQueryResult result = WorldDatabase.Query(stmt);
-
- if (!result)
- {
- TC_LOG_INFO("server.loading", ">> Loaded 0 quests in pools");
- }
- else
- {
- PooledQuestRelationBounds creBounds;
- PooledQuestRelationBounds goBounds;
-
- enum eQuestTypes
- {
- QUEST_NONE = 0,
- QUEST_DAILY = 1,
- QUEST_WEEKLY = 2
- };
-
- std::map<uint32, int32> poolTypeMap;
- uint32 count = 0;
- do
- {
- Field* fields = result->Fetch();
-
- uint32 entry = fields[0].GetUInt32();
- uint32 pool_id = fields[1].GetUInt32();
-
- Quest const* quest = sObjectMgr->GetQuestTemplate(entry);
- if (!quest)
- {
- TC_LOG_ERROR("sql.sql", "`pool_quest` has a non existing quest template (Entry: %u) defined for pool id (%u), skipped.", entry, pool_id);
- continue;
- }
-
- auto it = mPoolTemplate.find(pool_id);
- if (it == mPoolTemplate.end())
- {
- TC_LOG_ERROR("sql.sql", "`pool_quest` pool id (%u) is not in `pool_template`, skipped.", pool_id);
- continue;
- }
-
- if (!quest->IsDailyOrWeekly())
- {
- TC_LOG_ERROR("sql.sql", "`pool_quest` has an quest (%u) which is not daily or weekly in pool id (%u), use ExclusiveGroup instead, skipped.", entry, pool_id);
- continue;
- }
-
- if (poolTypeMap[pool_id] == QUEST_NONE)
- poolTypeMap[pool_id] = quest->IsDaily() ? QUEST_DAILY : QUEST_WEEKLY;
-
- int32 currType = quest->IsDaily() ? QUEST_DAILY : QUEST_WEEKLY;
-
- if (poolTypeMap[pool_id] != currType)
- {
- TC_LOG_ERROR("sql.sql", "`pool_quest` quest %u is %s but pool (%u) is specified for %s, mixing not allowed, skipped.",
- entry, currType == QUEST_DAILY ? "QUEST_DAILY" : "QUEST_WEEKLY", pool_id, poolTypeMap[pool_id] == QUEST_DAILY ? "QUEST_DAILY" : "QUEST_WEEKLY");
- continue;
- }
-
- creBounds = mQuestCreatureRelation.equal_range(entry);
- goBounds = mQuestGORelation.equal_range(entry);
-
- if (creBounds.first == creBounds.second && goBounds.first == goBounds.second)
- {
- TC_LOG_ERROR("sql.sql", "`pool_quest` lists entry (%u) as member of pool (%u) but is not started anywhere, skipped.", entry, pool_id);
- continue;
- }
-
- PoolTemplateData* pPoolTemplate = &mPoolTemplate[pool_id];
- PoolObject plObject = PoolObject(entry, 0.0f);
- PoolGroup<Quest>& questgroup = mPoolQuestGroups[pool_id];
- questgroup.SetPoolId(pool_id);
- questgroup.AddEntry(plObject, pPoolTemplate->MaxLimit);
- SearchPair p(entry, pool_id);
- mQuestSearchMap.insert(p);
-
- ++count;
- }
- while (result->NextRow());
-
- TC_LOG_INFO("server.loading", ">> Loaded %u quests in pools in %u ms", count, 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
TC_LOG_INFO("server.loading", "Starting objects pooling system...");
{
@@ -953,69 +699,6 @@ void PoolMgr::LoadFromDB()
}
}
-void PoolMgr::LoadQuestPools()
-{
-}
-
-void PoolMgr::SaveQuestsToDB()
-{
- SQLTransaction trans = CharacterDatabase.BeginTransaction();
-
- for (PoolGroupQuestMap::iterator itr = mPoolQuestGroups.begin(); itr != mPoolQuestGroups.end(); ++itr)
- {
- if (itr->second.isEmpty())
- continue;
- PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_POOL_SAVE);
- stmt->setUInt32(0, itr->second.GetPoolId());
- trans->Append(stmt);
- }
-
- for (SearchMap::iterator itr = mQuestSearchMap.begin(); itr != mQuestSearchMap.end(); ++itr)
- {
- if (IsSpawnedObject<Quest>(itr->first))
- {
- PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_QUEST_POOL_SAVE);
- stmt->setUInt32(0, itr->second);
- stmt->setUInt32(1, itr->first);
- trans->Append(stmt);
- }
- }
-
- CharacterDatabase.CommitTransaction(trans);
-}
-
-void PoolMgr::ChangeDailyQuests()
-{
- for (PoolGroupQuestMap::iterator itr = mPoolQuestGroups.begin(); itr != mPoolQuestGroups.end(); ++itr)
- {
- if (Quest const* quest = sObjectMgr->GetQuestTemplate(itr->second.GetFirstEqualChancedObjectId()))
- {
- if (quest->IsWeekly())
- continue;
-
- UpdatePool<Quest>(itr->second.GetPoolId(), 1); // anything non-zero means don't load from db
- }
- }
-
- SaveQuestsToDB();
-}
-
-void PoolMgr::ChangeWeeklyQuests()
-{
- for (PoolGroupQuestMap::iterator itr = mPoolQuestGroups.begin(); itr != mPoolQuestGroups.end(); ++itr)
- {
- if (Quest const* quest = sObjectMgr->GetQuestTemplate(itr->second.GetFirstEqualChancedObjectId()))
- {
- if (quest->IsDaily())
- continue;
-
- UpdatePool<Quest>(itr->second.GetPoolId(), 1);
- }
- }
-
- SaveQuestsToDB();
-}
-
// Call to spawn a pool, if cache if true the method will spawn only if cached entry is different
// If it's same, the creature is respawned only (added back to map)
template<>
@@ -1046,21 +729,11 @@ void PoolMgr::SpawnPool<Pool>(uint32 pool_id, uint32 sub_pool_id)
it->second.SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, sub_pool_id);
}
-// Call to spawn a pool
-template<>
-void PoolMgr::SpawnPool<Quest>(uint32 pool_id, uint32 quest_id)
-{
- auto it = mPoolQuestGroups.find(pool_id);
- if (it != mPoolQuestGroups.end() && !it->second.isEmpty())
- it->second.SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, quest_id);
-}
-
void PoolMgr::SpawnPool(uint32 pool_id)
{
SpawnPool<Pool>(pool_id, 0);
SpawnPool<GameObject>(pool_id, 0);
SpawnPool<Creature>(pool_id, 0);
- SpawnPool<Quest>(pool_id, 0);
}
// Call to despawn a pool, all gameobjects/creatures in this pool are removed
@@ -1081,11 +754,6 @@ void PoolMgr::DespawnPool(uint32 pool_id)
if (it != mPoolPoolGroups.end() && !it->second.isEmpty())
it->second.DespawnObject(mSpawnedData);
}
- {
- auto it = mPoolQuestGroups.find(pool_id);
- if (it != mPoolQuestGroups.end() && !it->second.isEmpty())
- it->second.DespawnObject(mSpawnedData);
- }
}
// Selects proper template overload to call based on passed type
@@ -1121,11 +789,6 @@ bool PoolMgr::CheckPool(uint32 pool_id) const
if (it != mPoolPoolGroups.end() && !it->second.CheckPool())
return false;
}
- {
- auto it = mPoolQuestGroups.find(pool_id);
- if (it != mPoolQuestGroups.end() && !it->second.CheckPool())
- return false;
- }
return true;
}
@@ -1144,4 +807,3 @@ void PoolMgr::UpdatePool(uint32 pool_id, uint32 db_guid_or_pool_id)
template void PoolMgr::UpdatePool<Pool>(uint32 pool_id, uint32 db_guid_or_pool_id);
template void PoolMgr::UpdatePool<GameObject>(uint32 pool_id, uint32 db_guid_or_pool_id);
template void PoolMgr::UpdatePool<Creature>(uint32 pool_id, uint32 db_guid_or_pool_id);
-template void PoolMgr::UpdatePool<Quest>(uint32 pool_id, uint32 db_guid_or_pool_id);
diff --git a/src/server/game/Pools/PoolMgr.h b/src/server/game/Pools/PoolMgr.h
index 6afe78206e0..7cd34ea5ae5 100644
--- a/src/server/game/Pools/PoolMgr.h
+++ b/src/server/game/Pools/PoolMgr.h
@@ -23,7 +23,6 @@
#include "Creature.h"
#include "GameObject.h"
#include "SpawnData.h"
-#include "QuestDef.h"
struct PoolTemplateData
{
@@ -57,12 +56,9 @@ class TC_GAME_API ActivePoolData
template<typename T>
void RemoveObject(uint32 db_guid_or_pool_id, uint32 pool_id);
-
- ActivePoolObjects GetActiveQuests() const { return mActiveQuests; } // a copy of the set
private:
ActivePoolObjects mSpawnedCreatures;
ActivePoolObjects mSpawnedGameobjects;
- ActivePoolObjects mActiveQuests;
ActivePoolPools mSpawnedPools;
};
@@ -97,10 +93,6 @@ class TC_GAME_API PoolGroup
PoolObjectList EqualChanced;
};
-typedef std::multimap<uint32, uint32> PooledQuestRelation;
-typedef std::pair<PooledQuestRelation::const_iterator, PooledQuestRelation::const_iterator> PooledQuestRelationBounds;
-typedef std::pair<PooledQuestRelation::iterator, PooledQuestRelation::iterator> PooledQuestRelationBoundsNC;
-
class TC_GAME_API PoolMgr
{
private:
@@ -111,8 +103,6 @@ class TC_GAME_API PoolMgr
static PoolMgr* instance();
void LoadFromDB();
- void LoadQuestPools();
- void SaveQuestsToDB();
void Initialize();
@@ -131,12 +121,6 @@ class TC_GAME_API PoolMgr
template<typename T>
void UpdatePool(uint32 pool_id, uint32 db_guid_or_pool_id);
- void ChangeDailyQuests();
- void ChangeWeeklyQuests();
-
- PooledQuestRelation mQuestCreatureRelation;
- PooledQuestRelation mQuestGORelation;
-
private:
template<typename T>
void SpawnPool(uint32 pool_id, uint32 db_guid_or_pool_id);
@@ -145,7 +129,6 @@ class TC_GAME_API PoolMgr
typedef std::unordered_map<uint32, PoolGroup<Creature>> PoolGroupCreatureMap;
typedef std::unordered_map<uint32, PoolGroup<GameObject>> PoolGroupGameObjectMap;
typedef std::unordered_map<uint32, PoolGroup<Pool>> PoolGroupPoolMap;
- typedef std::unordered_map<uint32, PoolGroup<Quest>> PoolGroupQuestMap;
typedef std::pair<uint32, uint32> SearchPair;
typedef std::map<uint32, uint32> SearchMap;
@@ -153,11 +136,9 @@ class TC_GAME_API PoolMgr
PoolGroupCreatureMap mPoolCreatureGroups;
PoolGroupGameObjectMap mPoolGameobjectGroups;
PoolGroupPoolMap mPoolPoolGroups;
- PoolGroupQuestMap mPoolQuestGroups;
SearchMap mCreatureSearchMap;
SearchMap mGameobjectSearchMap;
SearchMap mPoolSearchMap;
- SearchMap mQuestSearchMap;
// dynamic data
ActivePoolData mSpawnedData;
@@ -187,17 +168,6 @@ inline uint32 PoolMgr::IsPartOfAPool<GameObject>(uint32 db_guid) const
return 0;
}
-// Method that tell if the quest is part of another pool and return the pool id if yes
-template<>
-inline uint32 PoolMgr::IsPartOfAPool<Quest>(uint32 pool_id) const
-{
- SearchMap::const_iterator itr = mQuestSearchMap.find(pool_id);
- if (itr != mQuestSearchMap.end())
- return itr->second;
-
- return 0;
-}
-
// Method that tell if the pool is part of another pool and return the pool id if yes
template<>
inline uint32 PoolMgr::IsPartOfAPool<Pool>(uint32 pool_id) const
diff --git a/src/server/game/Pools/QuestPools.cpp b/src/server/game/Pools/QuestPools.cpp
new file mode 100644
index 00000000000..2665357f230
--- /dev/null
+++ b/src/server/game/Pools/QuestPools.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/>
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "QuestPools.h"
+#include "Containers.h"
+#include "DatabaseEnv.h"
+#include "Log.h"
+#include "ObjectMgr.h"
+#include "QuestDef.h"
+#include "Timer.h"
+#include "Transaction.h"
+#include <unordered_map>
+#include <unordered_set>
+
+/*static*/ QuestPoolMgr* QuestPoolMgr::instance()
+{
+ static QuestPoolMgr instance;
+ return &instance;
+}
+
+static void RegeneratePool(QuestPool& pool)
+{
+ ASSERT(!pool.members.empty(), "Quest pool %u is empty", pool.poolId);
+ uint32 const n = pool.members.size()-1;
+ ASSERT(pool.numActive <= pool.members.size(), "Quest Pool %u requests %u spawns, but has only %u members.", pool.poolId, pool.numActive, uint32(pool.members.size()));
+ pool.activeQuests.clear();
+ for (uint32 i = 0; i < pool.numActive; ++i)
+ {
+ uint32 j = urand(i,n);
+ if (i != j)
+ std::swap(pool.members[i], pool.members[j]);
+ for (uint32 quest : pool.members[i])
+ pool.activeQuests.insert(quest);
+ }
+}
+
+static void SaveToDB(QuestPool const& pool, SQLTransaction trans)
+{
+ PreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_POOL_QUEST_SAVE);
+ delStmt->setUInt32(0, pool.poolId);
+ trans->Append(delStmt);
+
+ for (uint32 questId : pool.activeQuests)
+ {
+ PreparedStatement* insStmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_POOL_QUEST_SAVE);
+ insStmt->setUInt32(0, pool.poolId);
+ insStmt->setUInt32(1, questId);
+ trans->Append(insStmt);
+ }
+}
+
+void QuestPoolMgr::LoadFromDB()
+{
+ uint32 oldMSTime = getMSTime();
+ std::unordered_map<uint32, std::pair<std::vector<QuestPool>*, uint32>> lookup; // poolId -> (vector, index)
+
+ _poolLookup.clear();
+ _dailyPools.clear();
+ _weeklyPools.clear();
+ _monthlyPools.clear();
+
+ // load template data from world DB
+ {
+ QueryResult result = WorldDatabase.Query("SELECT qpm.questId, qpm.poolId, qpm.poolIndex, qpt.numActive FROM quest_pool_members qpm LEFT JOIN quest_pool_template qpt ON qpm.poolId = qpt.poolId");
+ if (!result)
+ {
+ TC_LOG_INFO("server.loading", ">> Loaded 0 quest pools. DB table `quest_pool_members` is empty.");
+ return;
+ }
+
+ do
+ {
+ Field* fields = result->Fetch();
+ if (fields[2].IsNull())
+ {
+ TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to non-existing pool %u. Skipped.", fields[1].GetUInt32());
+ continue;
+ }
+
+ uint32 questId = fields[0].GetUInt32();
+ uint32 poolId = fields[1].GetUInt32();
+ uint32 poolIndex = fields[2].GetUInt8();
+ uint32 numActive = fields[3].GetUInt32();
+
+ Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
+ if (!quest)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to non-existing quest %u. Skipped.", questId);
+ continue;
+ }
+ if (!quest->IsDailyOrWeekly() && !quest->IsMonthly())
+ {
+ TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to quest %u, which is neither daily, weekly nor monthly. Skipped.", questId);
+ continue;
+ }
+
+ auto& pair = lookup[poolId];
+ if (!pair.first)
+ {
+ pair.first = (quest->IsDaily() ? &_dailyPools : quest->IsWeekly() ? &_weeklyPools : &_monthlyPools);
+ pair.first->emplace_back();
+ pair.second = (pair.first->size()-1);
+
+ pair.first->back().poolId = poolId;
+ pair.first->back().numActive = numActive;
+ }
+
+ QuestPool::Members& members = (*pair.first)[pair.second].members;
+ if (!(poolIndex < members.size()))
+ members.resize(poolIndex+1);
+ members[poolIndex].push_back(questId);
+ } while (result->NextRow());
+ }
+
+ // load saved spawns from character DB
+ {
+ QueryResult result = CharacterDatabase.Query("SELECT pool_id, quest_id FROM pool_quest_save");
+ if (result)
+ {
+ std::unordered_set<uint32> unknownPoolIds;
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 poolId = fields[0].GetUInt32();
+ uint32 questId = fields[1].GetUInt32();
+
+ auto it = lookup.find(poolId);
+ if (it == lookup.end() || !it->second.first)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `pool_quest_save` contains reference to non-existant quest pool %u. Deleted.", poolId);
+ unknownPoolIds.insert(poolId);
+ continue;
+ }
+
+ (*it->second.first)[it->second.second].activeQuests.insert(questId);
+ } while (result->NextRow());
+
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ for (uint32 poolId : unknownPoolIds)
+ {
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_POOL_QUEST_SAVE);
+ stmt->setUInt32(0, poolId);
+ trans->Append(stmt);
+ }
+ CharacterDatabase.CommitTransaction(trans);
+ }
+ }
+
+ // post-processing and sanity checks
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ for (auto pair : lookup)
+ {
+ if (!pair.second.first)
+ continue;
+ QuestPool& pool = (*pair.second.first)[pair.second.second];
+ if (pool.members.size() < pool.numActive)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `quest_pool_template` contains quest pool %u requesting %u spawns, but only has %zu members. Requested spawns reduced.", pool.poolId, pool.numActive, pool.members.size());
+ pool.numActive = pool.members.size();
+ }
+
+ bool doRegenerate = pool.activeQuests.empty();
+ if (!doRegenerate)
+ {
+ std::unordered_set<uint32> accountedFor;
+ uint32 activeCount = 0;
+ for (size_t i = pool.members.size(); (i--);)
+ {
+ QuestPool::Member& member = pool.members[i];
+ if (member.empty())
+ {
+ TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains no entries at index %zu for quest pool %u. Index removed.", i, pool.poolId);
+ std::swap(pool.members[i], pool.members.back());
+ pool.members.pop_back();
+ continue;
+ }
+
+ // check if the first member is active
+ auto itFirst = pool.activeQuests.find(member[0]);
+ bool status = (itFirst != pool.activeQuests.end());
+ // temporarily remove any spawns that are accounted for
+ if (status)
+ {
+ accountedFor.insert(member[0]);
+ pool.activeQuests.erase(itFirst);
+ }
+
+ // now check if all other members also have the same status, and warn if not
+ for (auto it = member.begin(); (++it) != member.end();)
+ {
+ auto itOther = pool.activeQuests.find(*it);
+ bool otherStatus = (itOther != pool.activeQuests.end());
+ if (status != otherStatus)
+ TC_LOG_WARN("sql.sql", "Table `pool_quest_save` %s quest %u (in pool %u, index %zu) saved, but its index is%s active (because quest %u is%s in the table). Set quest %u to %sactive.", (status ? "does not have" : "has"), *it, pool.poolId, i, (status ? "" : " not"), member[0], (status ? "" : " not"), *it, (status ? "" : "in"));
+ if (otherStatus)
+ pool.activeQuests.erase(itOther);
+ if (status)
+ accountedFor.insert(*it);
+ }
+
+ if (status)
+ ++activeCount;
+ }
+
+ // warn for any remaining active spawns (not part of the pool)
+ for (uint32 quest : pool.activeQuests)
+ TC_LOG_WARN("sql.sql", "Table `pool_quest_save` has saved quest %u for pool %u, but that quest is not part of the pool. Skipped.", quest, pool.poolId);
+
+ // only the previously-found spawns should actually be active
+ std::swap(pool.activeQuests, accountedFor);
+
+ if (activeCount != pool.numActive)
+ {
+ doRegenerate = true;
+ TC_LOG_ERROR("sql.sql", "Table `pool_quest_save` has %u active members saved for pool %u, which requests %u active members. Pool spawns re-generated.", activeCount, pool.poolId, pool.numActive);
+ }
+ }
+
+ if (doRegenerate)
+ {
+ RegeneratePool(pool);
+ SaveToDB(pool, trans);
+ }
+
+ for (QuestPool::Member const& member : pool.members)
+ {
+ for (uint32 quest : member)
+ {
+ QuestPool*& ref = _poolLookup[quest];
+ if (ref)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` lists quest %u as member of pool %u, but it is already a member of pool %u. Skipped.", quest, pool.poolId, ref->poolId);
+ continue;
+ }
+ ref = &pool;
+ }
+ }
+ }
+ CharacterDatabase.CommitTransaction(trans);
+
+ TC_LOG_INFO("server.loading", ">> Loaded %zu daily, %zu weekly and %zu monthly quest pools in %u ms", _dailyPools.size(), _weeklyPools.size(), _monthlyPools.size(), GetMSTimeDiffToNow(oldMSTime));
+}
+
+void QuestPoolMgr::Regenerate(std::vector<QuestPool>& pools)
+{
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ for (QuestPool& pool : pools)
+ {
+ RegeneratePool(pool);
+ SaveToDB(pool, trans);
+ }
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+// the storage structure ends up making this kind of inefficient
+// we don't use it in practice (only in debug commands), so that's fine
+QuestPool const* QuestPoolMgr::FindQuestPool(uint32 poolId) const
+{
+ auto lambda = [poolId](QuestPool const& p) { return (p.poolId == poolId); };
+ auto it = std::find_if(_dailyPools.begin(), _dailyPools.end(), lambda);
+ if (it != _dailyPools.end())
+ return &*it;
+ it = std::find_if(_weeklyPools.begin(), _weeklyPools.end(), lambda);
+ if (it != _weeklyPools.end())
+ return &*it;
+ it = std::find_if(_monthlyPools.begin(), _monthlyPools.end(), lambda);
+ if (it != _monthlyPools.end())
+ return &*it;
+ return nullptr;
+}
+
+bool QuestPoolMgr::IsQuestActive(uint32 questId) const
+{
+ auto it = _poolLookup.find(questId);
+ if (it == _poolLookup.end()) // not pooled
+ return true;
+
+ return (it->second->activeQuests.find(questId) != it->second->activeQuests.end());
+}
+
+
diff --git a/src/server/game/Pools/QuestPools.h b/src/server/game/Pools/QuestPools.h
new file mode 100644
index 00000000000..a9ef6d26adb
--- /dev/null
+++ b/src/server/game/Pools/QuestPools.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/>
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITY_QUESTPOOLS_H
+#define TRINITY_QUESTPOOLS_H
+
+#include "Define.h"
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+struct QuestPool
+{
+ using Member = std::vector<uint32>;
+ using Members = std::vector<Member>;
+
+ uint32 poolId;
+ uint32 numActive;
+ Members members;
+ std::unordered_set<uint32> activeQuests;
+};
+
+class TC_GAME_API QuestPoolMgr
+{
+ private:
+ QuestPoolMgr() {}
+ ~QuestPoolMgr() {}
+
+ public:
+ static QuestPoolMgr* instance();
+
+ void LoadFromDB();
+ void ChangeDailyQuests() { Regenerate(_dailyPools); }
+ void ChangeWeeklyQuests() { Regenerate(_weeklyPools); }
+ void ChangeMonthlyQuests() { Regenerate(_monthlyPools); }
+
+ QuestPool const* FindQuestPool(uint32 poolId) const;
+ bool IsQuestPooled(uint32 questId) const { return _poolLookup.find(questId) != _poolLookup.end(); }
+ bool IsQuestActive(uint32 questId) const;
+
+ private:
+ void Regenerate(std::vector<QuestPool>& pools);
+ std::vector<QuestPool> _dailyPools;
+ std::vector<QuestPool> _weeklyPools;
+ std::vector<QuestPool> _monthlyPools;
+ std::unordered_map<uint32, QuestPool*> _poolLookup; // questId -> pool
+};
+
+#define sQuestPoolMgr QuestPoolMgr::instance()
+
+#endif