TrinityCore
Loading...
Searching...
No Matches
QuestPools.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "QuestPools.h"
19#include "Containers.h"
20#include "DatabaseEnv.h"
21#include "Log.h"
22#include "ObjectMgr.h"
23#include "QuestDef.h"
24#include "Timer.h"
25#include "Transaction.h"
26#include <unordered_map>
27#include <unordered_set>
28
30{
32 return &instance;
33}
34
35static void RegeneratePool(QuestPool& pool)
36{
37 ASSERT(!pool.members.empty(), "Quest pool %u is empty", pool.poolId);
38 uint32 const n = pool.members.size()-1;
39 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()));
40 pool.activeQuests.clear();
41 for (uint32 i = 0; i < pool.numActive; ++i)
42 {
43 uint32 j = urand(i,n);
44 if (i != j)
45 std::swap(pool.members[i], pool.members[j]);
46 for (uint32 quest : pool.members[i])
47 pool.activeQuests.insert(quest);
48 }
49}
50
51static void SaveToDB(QuestPool const& pool, CharacterDatabaseTransaction trans)
52{
54 delStmt->setUInt32(0, pool.poolId);
55 trans->Append(delStmt);
56
57 for (uint32 questId : pool.activeQuests)
58 {
60 insStmt->setUInt32(0, pool.poolId);
61 insStmt->setUInt32(1, questId);
62 trans->Append(insStmt);
63 }
64}
65
67{
68 uint32 oldMSTime = getMSTime();
69 std::unordered_map<uint32, std::pair<std::vector<QuestPool>*, uint32>> lookup; // poolId -> (vector, index)
70
71 _poolLookup.clear();
72 _dailyPools.clear();
73 _weeklyPools.clear();
74 _monthlyPools.clear();
75
76 // load template data from world DB
77 {
78 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");
79 if (!result)
80 {
81 TC_LOG_INFO("server.loading", ">> Loaded 0 quest pools. DB table `quest_pool_members` is empty.");
82 return;
83 }
84
85 do
86 {
87 Field* fields = result->Fetch();
88 if (fields[2].IsNull())
89 {
90 TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to non-existing pool {}. Skipped.", fields[1].GetUInt32());
91 continue;
92 }
93
94 uint32 questId = fields[0].GetUInt32();
95 uint32 poolId = fields[1].GetUInt32();
96 uint32 poolIndex = fields[2].GetUInt8();
97 uint32 numActive = fields[3].GetUInt32();
98
99 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
100 if (!quest)
101 {
102 TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to non-existing quest {}. Skipped.", questId);
103 continue;
104 }
105 if (!quest->IsDailyOrWeekly() && !quest->IsMonthly())
106 {
107 TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains reference to quest {}, which is neither daily, weekly nor monthly. Skipped.", questId);
108 continue;
109 }
110
111 auto& pair = lookup[poolId];
112 if (!pair.first)
113 {
114 pair.first = (quest->IsDaily() ? &_dailyPools : quest->IsWeekly() ? &_weeklyPools : &_monthlyPools);
115 pair.first->emplace_back();
116 pair.second = (pair.first->size()-1);
117
118 pair.first->back().poolId = poolId;
119 pair.first->back().numActive = numActive;
120 }
121
122 QuestPool::Members& members = (*pair.first)[pair.second].members;
123 if (!(poolIndex < members.size()))
124 members.resize(poolIndex+1);
125 members[poolIndex].push_back(questId);
126 } while (result->NextRow());
127 }
128
129 // load saved spawns from character DB
130 {
131 QueryResult result = CharacterDatabase.Query("SELECT pool_id, quest_id FROM pool_quest_save");
132 if (result)
133 {
134 std::unordered_set<uint32> unknownPoolIds;
135 do
136 {
137 Field* fields = result->Fetch();
138
139 uint32 poolId = fields[0].GetUInt32();
140 uint32 questId = fields[1].GetUInt32();
141
142 auto it = lookup.find(poolId);
143 if (it == lookup.end() || !it->second.first)
144 {
145 TC_LOG_ERROR("sql.sql", "Table `pool_quest_save` contains reference to non-existant quest pool {}. Deleted.", poolId);
146 unknownPoolIds.insert(poolId);
147 continue;
148 }
149
150 (*it->second.first)[it->second.second].activeQuests.insert(questId);
151 } while (result->NextRow());
152
153 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
154 for (uint32 poolId : unknownPoolIds)
155 {
157 stmt->setUInt32(0, poolId);
158 trans->Append(stmt);
159 }
160 CharacterDatabase.CommitTransaction(trans);
161 }
162 }
163
164 // post-processing and sanity checks
165 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
166 for (auto pair : lookup)
167 {
168 if (!pair.second.first)
169 continue;
170 QuestPool& pool = (*pair.second.first)[pair.second.second];
171 if (pool.members.size() < pool.numActive)
172 {
173 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());
174 pool.numActive = pool.members.size();
175 }
176
177 bool doRegenerate = pool.activeQuests.empty();
178 if (!doRegenerate)
179 {
180 std::unordered_set<uint32> accountedFor;
181 uint32 activeCount = 0;
182 for (size_t i = pool.members.size(); (i--);)
183 {
184 QuestPool::Member& member = pool.members[i];
185 if (member.empty())
186 {
187 TC_LOG_ERROR("sql.sql", "Table `quest_pool_members` contains no entries at index {} for quest pool {}. Index removed.", i, pool.poolId);
188 std::swap(pool.members[i], pool.members.back());
189 pool.members.pop_back();
190 continue;
191 }
192
193 // check if the first member is active
194 auto itFirst = pool.activeQuests.find(member[0]);
195 bool status = (itFirst != pool.activeQuests.end());
196 // temporarily remove any spawns that are accounted for
197 if (status)
198 {
199 accountedFor.insert(member[0]);
200 pool.activeQuests.erase(itFirst);
201 }
202
203 // now check if all other members also have the same status, and warn if not
204 for (auto it = member.begin(); (++it) != member.end();)
205 {
206 auto itOther = pool.activeQuests.find(*it);
207 bool otherStatus = (itOther != pool.activeQuests.end());
208 if (status != otherStatus)
209 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"));
210 if (otherStatus)
211 pool.activeQuests.erase(itOther);
212 if (status)
213 accountedFor.insert(*it);
214 }
215
216 if (status)
217 ++activeCount;
218 }
219
220 // warn for any remaining active spawns (not part of the pool)
221 for (uint32 quest : pool.activeQuests)
222 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);
223
224 // only the previously-found spawns should actually be active
225 std::swap(pool.activeQuests, accountedFor);
226
227 if (activeCount != pool.numActive)
228 {
229 doRegenerate = true;
230 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);
231 }
232 }
233
234 if (doRegenerate)
235 {
236 RegeneratePool(pool);
237 SaveToDB(pool, trans);
238 }
239
240 for (QuestPool::Member const& member : pool.members)
241 {
242 for (uint32 quest : member)
243 {
244 QuestPool*& ref = _poolLookup[quest];
245 if (ref)
246 {
247 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);
248 continue;
249 }
250 ref = &pool;
251 }
252 }
253 }
254 CharacterDatabase.CommitTransaction(trans);
255
256 TC_LOG_INFO("server.loading", ">> Loaded {} daily, {} weekly and {} monthly quest pools in {} ms", _dailyPools.size(), _weeklyPools.size(), _monthlyPools.size(), GetMSTimeDiffToNow(oldMSTime));
257}
258
259void QuestPoolMgr::Regenerate(std::vector<QuestPool>& pools)
260{
261 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
262 for (QuestPool& pool : pools)
263 {
264 RegeneratePool(pool);
265 SaveToDB(pool, trans);
266 }
267 CharacterDatabase.CommitTransaction(trans);
268}
269
270// the storage structure ends up making this kind of inefficient
271// we don't use it in practice (only in debug commands), so that's fine
273{
274 auto lambda = [poolId](QuestPool const& p) { return (p.poolId == poolId); };
275 auto it = std::find_if(_dailyPools.begin(), _dailyPools.end(), lambda);
276 if (it != _dailyPools.end())
277 return &*it;
278 it = std::find_if(_weeklyPools.begin(), _weeklyPools.end(), lambda);
279 if (it != _weeklyPools.end())
280 return &*it;
281 it = std::find_if(_monthlyPools.begin(), _monthlyPools.end(), lambda);
282 if (it != _monthlyPools.end())
283 return &*it;
284 return nullptr;
285}
286
288{
289 auto it = _poolLookup.find(questId);
290 if (it == _poolLookup.end()) // not pooled
291 return true;
292
293 return (it->second->activeQuests.find(questId) != it->second->activeQuests.end());
294}
@ CHAR_INS_POOL_QUEST_SAVE
@ CHAR_DEL_POOL_QUEST_SAVE
SQLTransaction< CharacterDatabaseConnection > CharacterDatabaseTransaction
std::shared_ptr< ResultSet > QueryResult
DatabaseWorkerPool< CharacterDatabaseConnection > CharacterDatabase
Accessor to the character database.
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
uint32_t uint32
Definition Define.h:133
#define ASSERT
Definition Errors.h:68
#define TC_LOG_WARN(filterType__,...)
Definition Log.h:162
#define TC_LOG_ERROR(filterType__,...)
Definition Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition Log.h:159
#define sObjectMgr
Definition ObjectMgr.h:1721
static void RegeneratePool(QuestPool &pool)
static void SaveToDB(QuestPool const &pool, CharacterDatabaseTransaction trans)
uint32 urand(uint32 min, uint32 max)
Definition Random.cpp:42
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition Timer.h:57
uint32 getMSTime()
Definition Timer.h:33
Class used to access individual fields of database query result.
Definition Field.h:92
uint8 GetUInt8() const
Definition Field.cpp:29
uint32 GetUInt32() const
Definition Field.cpp:61
void setUInt32(uint8 index, uint32 value)
std::vector< QuestPool > _dailyPools
Definition QuestPools.h:57
std::vector< QuestPool > _weeklyPools
Definition QuestPools.h:58
bool IsQuestActive(uint32 questId) const
std::unordered_map< uint32, QuestPool * > _poolLookup
Definition QuestPools.h:60
std::vector< QuestPool > _monthlyPools
Definition QuestPools.h:59
static QuestPoolMgr * instance()
void LoadFromDB()
void Regenerate(std::vector< QuestPool > &pools)
QuestPool const * FindQuestPool(uint32 poolId) const
bool IsDaily() const
Definition QuestDef.h:289
bool IsWeekly() const
Definition QuestDef.h:290
bool IsDailyOrWeekly() const
Definition QuestDef.h:293
bool IsMonthly() const
Definition QuestDef.h:291
std::vector< Member > Members
Definition QuestPools.h:29
uint32 numActive
Definition QuestPools.h:32
std::unordered_set< uint32 > activeQuests
Definition QuestPools.h:34
std::vector< uint32 > Member
Definition QuestPools.h:28
uint32 poolId
Definition QuestPools.h:31
Members members
Definition QuestPools.h:33