/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* 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 .
*/
#include "QuestPools.h"
#include "Containers.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "QuestDef.h"
#include "Timer.h"
#include
#include
/*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, CharacterDatabaseTransaction trans)
{
CharacterDatabasePreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_POOL_QUEST_SAVE);
delStmt->setUInt32(0, pool.poolId);
trans->Append(delStmt);
for (uint32 questId : pool.activeQuests)
{
CharacterDatabasePreparedStatement* 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>> 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 {}. 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 {}. Skipped.", questId);
continue;
}
if (!quest->IsDailyOrWeekly() && !quest->IsMonthly())
{
TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to quest {}, 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 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 {}. Deleted.", poolId);
unknownPoolIds.insert(poolId);
continue;
}
(*it->second.first)[it->second.second].activeQuests.insert(questId);
} while (result->NextRow());
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
for (uint32 poolId : unknownPoolIds)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_POOL_QUEST_SAVE);
stmt->setUInt32(0, poolId);
trans->Append(stmt);
}
CharacterDatabase.CommitTransaction(trans);
}
}
// post-processing and sanity checks
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
for (auto const& 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 {} requesting {} spawns, but only has {} 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 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 {} for quest pool {}. 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` {} quest {} (in pool {}, index {}) saved, but its index is{} active (because quest {} is{} in the table). Set quest {} to {}active.", (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 {} for pool {}, 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 {} active members saved for pool {}, which requests {} 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 {} as member of pool {}, but it is already a member of pool {}. Skipped.", quest, pool.poolId, ref->poolId);
continue;
}
ref = &pool;
}
}
}
CharacterDatabase.CommitTransaction(trans);
TC_LOG_INFO("server.loading", ">> Loaded {} daily, {} weekly and {} monthly quest pools in {} ms", _dailyPools.size(), _weeklyPools.size(), _monthlyPools.size(), GetMSTimeDiffToNow(oldMSTime));
}
void QuestPoolMgr::Regenerate(std::vector& pools)
{
CharacterDatabaseTransaction 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());
}