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

(cherry picked from commit a5e73e41c0)
This commit is contained in:
Treeston
2019-08-04 12:22:57 +02:00
committed by Shauren
parent 6d5086da17
commit 51fbda4223
27 changed files with 727 additions and 667 deletions

View File

@@ -60,13 +60,6 @@ TC_GAME_API bool ActivePoolData::IsActiveObject<Pool>(uint64 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>(uint64 quest_id) const
{
return mActiveQuests.find(quest_id) != mActiveQuests.end();
}
template<>
void ActivePoolData::ActivateObject<Creature>(uint64 db_guid, uint32 pool_id)
{
@@ -88,13 +81,6 @@ void ActivePoolData::ActivateObject<Pool>(uint64 sub_pool_id, uint32 pool_id)
++mSpawnedPools[pool_id];
}
template<>
void ActivePoolData::ActivateObject<Quest>(uint64 quest_id, uint32 pool_id)
{
mActiveQuests.insert(quest_id);
++mSpawnedPools[pool_id];
}
template<>
void ActivePoolData::RemoveObject<Creature>(uint64 db_guid, uint32 pool_id)
{
@@ -122,15 +108,6 @@ void ActivePoolData::RemoveObject<Pool>(uint64 sub_pool_id, uint32 pool_id)
--val;
}
template<>
void ActivePoolData::RemoveObject<Quest>(uint64 quest_id, uint32 pool_id)
{
mActiveQuests.erase(quest_id);
uint32& val = mSpawnedPools[pool_id];
if (val > 0)
--val;
}
////////////////////////////////////////////////////////////
// Methods of template class PoolGroup
@@ -250,49 +227,6 @@ void PoolGroup<Pool>::Despawn1Object(uint64 child_pool_id)
sPoolMgr->DespawnPool(child_pool_id);
}
// Same on one quest
template<>
void PoolGroup<Quest>::Despawn1Object(uint64 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)
@@ -428,100 +362,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, uint64 triggerFrom)
{
TC_LOG_DEBUG("pool", "PoolGroup<Quest>: Spawning pool %u", poolId);
// load state from db
if (!triggerFrom)
{
CharacterDatabasePreparedStatement* 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*/)
@@ -540,10 +380,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
@@ -551,7 +387,6 @@ PoolMgr::PoolMgr() { }
void PoolMgr::Initialize()
{
mQuestSearchMap.clear();
mGameobjectSearchMap.clear();
mCreatureSearchMap.clear();
}
@@ -811,95 +646,6 @@ void PoolMgr::LoadFromDB()
}
}
TC_LOG_INFO("server.loading", "Loading Quest Pooling Data...");
{
uint32 oldMSTime = getMSTime();
WorldDatabasePreparedStatement* 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...");
{
@@ -948,70 +694,6 @@ void PoolMgr::LoadFromDB()
}
}
void PoolMgr::LoadQuestPools()
{
}
void PoolMgr::SaveQuestsToDB()
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
for (PoolGroupQuestMap::iterator itr = mPoolQuestGroups.begin(); itr != mPoolQuestGroups.end(); ++itr)
{
if (itr->second.isEmpty())
continue;
CharacterDatabasePreparedStatement* 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))
{
CharacterDatabasePreparedStatement* 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<>
@@ -1042,21 +724,11 @@ void PoolMgr::SpawnPool<Pool>(uint32 pool_id, uint64 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, uint64 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
@@ -1077,11 +749,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
@@ -1117,11 +784,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;
}
@@ -1140,4 +802,3 @@ void PoolMgr::UpdatePool(uint32 pool_id, uint64 db_guid_or_pool_id)
template void PoolMgr::UpdatePool<Pool>(uint32 pool_id, uint64 db_guid_or_pool_id);
template void PoolMgr::UpdatePool<GameObject>(uint32 pool_id, uint64 db_guid_or_pool_id);
template void PoolMgr::UpdatePool<Creature>(uint32 pool_id, uint64 db_guid_or_pool_id);
template void PoolMgr::UpdatePool<Quest>(uint32 pool_id, uint64 db_guid_or_pool_id);