diff options
author | Treeston <treeston.mmoc@gmail.com> | 2019-08-04 12:22:57 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-04 12:22:57 +0200 |
commit | a5e73e41c0e813e674bb0a644e0052052435494e (patch) | |
tree | cabbeadc1e07635f1a8fc53599895613cbe61723 /src/server/game/Pools | |
parent | 15e85f882fdb7b5d1d48302907e76c993ee4e923 (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.cpp | 338 | ||||
-rw-r--r-- | src/server/game/Pools/PoolMgr.h | 30 | ||||
-rw-r--r-- | src/server/game/Pools/QuestPools.cpp | 296 | ||||
-rw-r--r-- | src/server/game/Pools/QuestPools.h | 65 |
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 |