Core/Pooling: Quest pooling rewrite
Port From (https://github.com/TrinityCore/TrinityCore/commit/51fbda4223442635a35d3225c0797d0151ea7051)
This commit is contained in:
@@ -31,7 +31,6 @@ namespace Game
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
mQuestSearchMap.Clear();
|
||||
mGameobjectSearchMap.Clear();
|
||||
mCreatureSearchMap.Clear();
|
||||
}
|
||||
@@ -253,91 +252,6 @@ namespace Game
|
||||
}
|
||||
}
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Quest Pooling Data...");
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_QUEST_POOLS);
|
||||
SQLResult result = DB.World.Query(stmt);
|
||||
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quests in pools");
|
||||
}
|
||||
else
|
||||
{
|
||||
List<uint> creBounds;
|
||||
List<uint> goBounds;
|
||||
|
||||
Dictionary<uint, QuestTypes> poolTypeMap = new();
|
||||
uint count = 0;
|
||||
do
|
||||
{
|
||||
uint entry = result.Read<uint>(0);
|
||||
uint pool_id = result.Read<uint>(1);
|
||||
|
||||
if (!poolTypeMap.ContainsKey(pool_id))
|
||||
poolTypeMap[pool_id] = 0;
|
||||
|
||||
Quest quest = Global.ObjectMgr.GetQuestTemplate(entry);
|
||||
if (quest == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "`pool_quest` has a non existing quest template (Entry: {0}) defined for pool id ({1}), skipped.", entry, pool_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mPoolTemplate.ContainsKey(pool_id))
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "`pool_quest` pool id ({0}) is not in `pool_template`, skipped.", pool_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!quest.IsDailyOrWeekly())
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "`pool_quest` has an quest ({0}) which is not daily or weekly in pool id ({1}), use ExclusiveGroup instead, skipped.", entry, pool_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poolTypeMap[pool_id] == QuestTypes.None)
|
||||
poolTypeMap[pool_id] = quest.IsDaily() ? QuestTypes.Daily : QuestTypes.Weekly;
|
||||
|
||||
QuestTypes currType = quest.IsDaily() ? QuestTypes.Daily : QuestTypes.Weekly;
|
||||
|
||||
if (poolTypeMap[pool_id] != currType)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "`pool_quest` quest {0} is {1} but pool ({2}) is specified for {3}, mixing not allowed, skipped.",
|
||||
entry, currType, pool_id, poolTypeMap[pool_id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
creBounds = mQuestCreatureRelation.LookupByKey(entry);
|
||||
goBounds = mQuestGORelation.LookupByKey(entry);
|
||||
|
||||
if (creBounds.Empty() && goBounds.Empty())
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "`pool_quest` lists entry ({0}) as member of pool ({1}) but is not started anywhere, skipped.", entry, pool_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id];
|
||||
PoolObject plObject = new(entry, 0.0f);
|
||||
|
||||
if (!mPoolQuestGroups.ContainsKey(pool_id))
|
||||
mPoolQuestGroups[pool_id] = new PoolGroup<Quest>();
|
||||
|
||||
PoolGroup<Quest> questgroup = mPoolQuestGroups[pool_id];
|
||||
questgroup.SetPoolId(pool_id);
|
||||
questgroup.AddEntry(plObject, pPoolTemplate.MaxLimit);
|
||||
|
||||
mQuestSearchMap.Add(entry, pool_id);
|
||||
++count;
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests in pools in {1} ms", count, Time.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
|
||||
Log.outInfo(LogFilter.ServerLoading, "Starting objects pooling system...");
|
||||
{
|
||||
@@ -385,67 +299,6 @@ namespace Game
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveQuestsToDB()
|
||||
{
|
||||
SQLTransaction trans = new();
|
||||
|
||||
foreach (var questPoolGroup in mPoolQuestGroups.Values)
|
||||
{
|
||||
if (questPoolGroup.IsEmpty())
|
||||
continue;
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_QUEST_POOL_SAVE);
|
||||
stmt.AddValue(0, questPoolGroup.GetPoolId());
|
||||
trans.Append(stmt);
|
||||
}
|
||||
|
||||
foreach (var pair in mQuestSearchMap)
|
||||
{
|
||||
if (IsSpawnedObject<Quest>(pair.Key))
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_QUEST_POOL_SAVE);
|
||||
stmt.AddValue(0, pair.Value);
|
||||
stmt.AddValue(1, pair.Key);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
public void ChangeDailyQuests()
|
||||
{
|
||||
foreach (var questPoolGroup in mPoolQuestGroups.Values)
|
||||
{
|
||||
Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)questPoolGroup.GetFirstEqualChancedObjectId());
|
||||
if (quest != null)
|
||||
{
|
||||
if (quest.IsWeekly())
|
||||
continue;
|
||||
|
||||
UpdatePool<Quest>(questPoolGroup.GetPoolId(), 1); // anything non-zero means don't load from db
|
||||
}
|
||||
}
|
||||
|
||||
SaveQuestsToDB();
|
||||
}
|
||||
|
||||
public void ChangeWeeklyQuests()
|
||||
{
|
||||
foreach (var questPoolGroup in mPoolQuestGroups.Values)
|
||||
{
|
||||
Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)questPoolGroup.GetFirstEqualChancedObjectId());
|
||||
if (quest != null)
|
||||
{
|
||||
if (quest.IsDaily())
|
||||
continue;
|
||||
|
||||
UpdatePool<Quest>(questPoolGroup.GetPoolId(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
SaveQuestsToDB();
|
||||
}
|
||||
|
||||
void SpawnPool<T>(uint pool_id, ulong db_guid)
|
||||
{
|
||||
switch (typeof(T).Name)
|
||||
@@ -462,10 +315,6 @@ namespace Game
|
||||
if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].IsEmpty())
|
||||
mPoolPoolGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid);
|
||||
break;
|
||||
case "Quest":
|
||||
if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].IsEmpty())
|
||||
mPoolQuestGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +323,6 @@ namespace Game
|
||||
SpawnPool<Pool>(pool_id, 0);
|
||||
SpawnPool<GameObject>(pool_id, 0);
|
||||
SpawnPool<Creature>(pool_id, 0);
|
||||
SpawnPool<Quest>(pool_id, 0);
|
||||
}
|
||||
|
||||
public void DespawnPool(uint pool_id)
|
||||
@@ -487,9 +335,6 @@ namespace Game
|
||||
|
||||
if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].IsEmpty())
|
||||
mPoolPoolGroups[pool_id].DespawnObject(mSpawnedData);
|
||||
|
||||
if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].IsEmpty())
|
||||
mPoolQuestGroups[pool_id].DespawnObject(mSpawnedData);
|
||||
}
|
||||
|
||||
public bool CheckPool(uint pool_id)
|
||||
@@ -503,9 +348,6 @@ namespace Game
|
||||
if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].CheckPool())
|
||||
return false;
|
||||
|
||||
if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].CheckPool())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -528,8 +370,6 @@ namespace Game
|
||||
return mGameobjectSearchMap.LookupByKey(db_guid);
|
||||
case "Pool":
|
||||
return mPoolSearchMap.LookupByKey(db_guid);
|
||||
case "Quest":
|
||||
return mQuestSearchMap.LookupByKey(db_guid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -566,11 +406,9 @@ namespace Game
|
||||
Dictionary<uint, PoolGroup<Creature>> mPoolCreatureGroups = new();
|
||||
Dictionary<uint, PoolGroup<GameObject>> mPoolGameobjectGroups = new();
|
||||
Dictionary<uint, PoolGroup<Pool>> mPoolPoolGroups = new();
|
||||
Dictionary<uint, PoolGroup<Quest>> mPoolQuestGroups = new();
|
||||
Dictionary<ulong, uint> mCreatureSearchMap = new();
|
||||
Dictionary<ulong, uint> mGameobjectSearchMap = new();
|
||||
Dictionary<ulong, uint> mPoolSearchMap = new();
|
||||
Dictionary<ulong, uint> mQuestSearchMap = new();
|
||||
|
||||
// dynamic data
|
||||
ActivePoolData mSpawnedData = new();
|
||||
@@ -684,37 +522,6 @@ namespace Game
|
||||
case "Pool":
|
||||
Global.PoolMgr.DespawnPool((uint)guid);
|
||||
break;
|
||||
case "Quest":
|
||||
// Creatures
|
||||
var questMap = Global.ObjectMgr.GetCreatureQuestRelationMap();
|
||||
var qr = Global.PoolMgr.mQuestCreatureRelation.LookupByKey(guid);
|
||||
foreach (var creature in qr)
|
||||
{
|
||||
if (!questMap.ContainsKey(creature))
|
||||
continue;
|
||||
|
||||
foreach (var quest in questMap[creature].ToList())
|
||||
{
|
||||
if (quest == guid)
|
||||
questMap.Remove(creature, quest);
|
||||
}
|
||||
}
|
||||
|
||||
// Gameobjects
|
||||
questMap = Global.ObjectMgr.GetGOQuestRelationMap();
|
||||
qr = Global.PoolMgr.mQuestGORelation.LookupByKey(guid);
|
||||
foreach (var go in qr)
|
||||
{
|
||||
if (!questMap.ContainsKey(go))
|
||||
continue;
|
||||
|
||||
foreach (var quest in questMap[go])
|
||||
{
|
||||
if (quest == guid)
|
||||
questMap.Remove(go, quest);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,12 +550,6 @@ namespace Game
|
||||
|
||||
public void SpawnObject(ActivePoolData spawns, uint limit, ulong triggerFrom)
|
||||
{
|
||||
if (typeof(T).Name == "Quest")
|
||||
{
|
||||
SpawnQuestObject(spawns, limit, triggerFrom);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = (int)(limit - spawns.GetActiveObjectCount(poolId));
|
||||
|
||||
// If triggered from some object respawn this object is still marked as spawned
|
||||
@@ -807,71 +608,6 @@ namespace Game
|
||||
DespawnObject(spawns, triggerFrom);
|
||||
}
|
||||
|
||||
void SpawnQuestObject(ActivePoolData spawns, uint limit, ulong triggerFrom)
|
||||
{
|
||||
Log.outDebug(LogFilter.Pool, "PoolGroup<Quest>: Spawning pool {0}", poolId);
|
||||
// load state from db
|
||||
if (triggerFrom == 0)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_POOL_QUEST_SAVE);
|
||||
stmt.AddValue(0, poolId);
|
||||
SQLResult result = DB.Characters.Query(stmt);
|
||||
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint questId = result.Read<uint>(0);
|
||||
spawns.ActivateObject<Quest>(questId, poolId);
|
||||
PoolObject tempObj = new(questId, 0.0f);
|
||||
Spawn1Object(tempObj);
|
||||
--limit;
|
||||
} while (result.NextRow() && limit != 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<ulong> currentQuests = spawns.GetActiveQuests();
|
||||
List<ulong> newQuests = new();
|
||||
|
||||
// always try to select different quests
|
||||
foreach (var poolObject in EqualChanced)
|
||||
{
|
||||
if (spawns.IsActiveObject<Quest>(poolObject.guid))
|
||||
continue;
|
||||
newQuests.Add(poolObject.guid);
|
||||
}
|
||||
|
||||
// clear the pool
|
||||
DespawnObject(spawns);
|
||||
|
||||
// recycle minimal amount of quests if possible count is lower than limit
|
||||
while (limit > newQuests.Count && !currentQuests.Empty())
|
||||
{
|
||||
ulong questId = currentQuests.SelectRandom();
|
||||
newQuests.Add(questId);
|
||||
currentQuests.Remove(questId);
|
||||
}
|
||||
|
||||
if (newQuests.Empty())
|
||||
return;
|
||||
|
||||
// activate <limit> random quests
|
||||
do
|
||||
{
|
||||
ulong questId = newQuests.SelectRandom();
|
||||
spawns.ActivateObject<Quest>(questId, poolId);
|
||||
PoolObject tempObj = new(questId, 0.0f);
|
||||
Spawn1Object(tempObj);
|
||||
newQuests.Remove(questId);
|
||||
--limit;
|
||||
} while (limit != 0 && !newQuests.Empty());
|
||||
|
||||
// if we are here it means the pool is initialized at startup and did not have previous saved state
|
||||
if (triggerFrom == 0)
|
||||
Global.PoolMgr.SaveQuestsToDB();
|
||||
}
|
||||
|
||||
void Spawn1Object(PoolObject obj)
|
||||
{
|
||||
switch (typeof(T).Name)
|
||||
@@ -916,25 +652,6 @@ namespace Game
|
||||
case "Pool":
|
||||
Global.PoolMgr.SpawnPool((uint)obj.guid);
|
||||
break;
|
||||
case "Quest":
|
||||
// Creatures
|
||||
var questMap = Global.ObjectMgr.GetCreatureQuestRelationMap();
|
||||
var qr = Global.PoolMgr.mQuestCreatureRelation.LookupByKey(obj.guid);
|
||||
foreach (var creature in qr)
|
||||
{
|
||||
Log.outDebug(LogFilter.Pool, "PoolGroup<Quest>: Adding quest {0} to creature {1}", obj.guid, creature);
|
||||
questMap.Add(creature, (uint)obj.guid);
|
||||
}
|
||||
|
||||
// Gameobjects
|
||||
questMap = Global.ObjectMgr.GetGOQuestRelationMap();
|
||||
qr = Global.PoolMgr.mQuestGORelation.LookupByKey(obj.guid);
|
||||
foreach (var go in qr)
|
||||
{
|
||||
Log.outDebug(LogFilter.Pool, "PoolGroup<Quest>: Adding quest {0} to GO {1}", obj.guid, go);
|
||||
questMap.Add(go, (uint)obj.guid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,8 +694,6 @@ namespace Game
|
||||
return mSpawnedGameobjects.Contains(db_guid);
|
||||
case "Pool":
|
||||
return mSpawnedPools.ContainsKey(db_guid);
|
||||
case "Quest":
|
||||
return mActiveQuests.Contains(db_guid);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -997,9 +712,6 @@ namespace Game
|
||||
case "Pool":
|
||||
mSpawnedPools[db_guid] = 0;
|
||||
break;
|
||||
case "Quest":
|
||||
mActiveQuests.Add(db_guid);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@@ -1022,9 +734,6 @@ namespace Game
|
||||
case "Pool":
|
||||
mSpawnedPools.Remove(db_guid);
|
||||
break;
|
||||
case "Quest":
|
||||
mActiveQuests.Remove(db_guid);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@@ -1033,11 +742,8 @@ namespace Game
|
||||
--mSpawnedPools[pool_id];
|
||||
}
|
||||
|
||||
public List<ulong> GetActiveQuests() { return mActiveQuests; } // a copy of the set
|
||||
|
||||
List<ulong> mSpawnedCreatures = new();
|
||||
List<ulong> mSpawnedGameobjects = new();
|
||||
List<ulong> mActiveQuests = new();
|
||||
Dictionary<ulong, uint> mSpawnedPools = new();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* 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 3 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/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.Entities;
|
||||
using Game.Maps;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
public class QuestPoolManager : Singleton<QuestPoolManager>
|
||||
{
|
||||
List<QuestPool> _dailyPools = new();
|
||||
List<QuestPool> _weeklyPools = new();
|
||||
List<QuestPool> _monthlyPools = new();
|
||||
Dictionary<uint, QuestPool> _poolLookup = new(); // questId -> pool
|
||||
|
||||
QuestPoolManager() { }
|
||||
|
||||
public static void RegeneratePool(QuestPool pool)
|
||||
{
|
||||
Cypher.Assert(!pool.members.Empty(), $"Quest pool {pool.poolId} is empty");
|
||||
Cypher.Assert(pool.numActive <= pool.members.Count, $"Quest Pool {pool.poolId} requests {pool.numActive} spawns, but has only {pool.members.Count} members.");
|
||||
|
||||
int n = pool.members.Count - 1;
|
||||
pool.activeQuests.Clear();
|
||||
for (uint i = 0; i < pool.numActive; ++i)
|
||||
{
|
||||
uint j = RandomHelper.URand(i, n);
|
||||
if (i != j)
|
||||
{
|
||||
var leftList = pool.members[i];
|
||||
pool.members[i] = pool.members[j];
|
||||
pool.members[j] = leftList;
|
||||
}
|
||||
|
||||
foreach (uint quest in pool.members[i])
|
||||
pool.activeQuests.Add(quest);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveToDB(QuestPool pool, SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement delStmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_POOL_QUEST_SAVE);
|
||||
delStmt.AddValue(0, pool.poolId);
|
||||
trans.Append(delStmt);
|
||||
|
||||
foreach (uint questId in pool.activeQuests)
|
||||
{
|
||||
PreparedStatement insStmt = DB.Characters.GetPreparedStatement(CharStatements.INS_POOL_QUEST_SAVE);
|
||||
insStmt.AddValue(0, pool.poolId);
|
||||
insStmt.AddValue(1, questId);
|
||||
trans.Append(insStmt);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromDB()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
Dictionary<uint, Tuple<List<QuestPool>, int>> lookup = new(); // poolId -> (list, index)
|
||||
|
||||
_poolLookup.Clear();
|
||||
_dailyPools.Clear();
|
||||
_weeklyPools.Clear();
|
||||
_monthlyPools.Clear();
|
||||
|
||||
// load template data from world DB
|
||||
{
|
||||
SQLResult result = DB.World.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.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quest pools. DB table `quest_pool_members` is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (result.IsNull(2))
|
||||
{
|
||||
Log.outError(LogFilter.Sql, $"Table `quest_pool_members` contains reference to non-existing pool {result.Read<uint>(1)}. Skipped.");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint questId = result.Read<uint>(0);
|
||||
uint poolId = result.Read<uint>(1);
|
||||
uint poolIndex = result.Read<uint>(2);
|
||||
uint numActive = result.Read<uint>(3);
|
||||
|
||||
Quest quest = Global.ObjectMgr.GetQuestTemplate(questId);
|
||||
if (quest == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Table `quest_pool_members` contains reference to non-existing quest %u. Skipped.", questId);
|
||||
continue;
|
||||
}
|
||||
if (!quest.IsDailyOrWeekly() && !quest.IsMonthly())
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Table `quest_pool_members` contains reference to quest %u, which is neither daily, weekly nor monthly. Skipped.", questId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lookup.ContainsKey(poolId))
|
||||
{
|
||||
var poolList = quest.IsDaily() ? _dailyPools : quest.IsWeekly() ? _weeklyPools : _monthlyPools;
|
||||
poolList.Add(new QuestPool() { poolId = poolId, numActive = numActive });
|
||||
|
||||
lookup.Add(poolId, new Tuple<List<QuestPool>, int>(poolList, poolList.Count - 1));
|
||||
}
|
||||
|
||||
var pair = lookup[poolId];
|
||||
|
||||
var members = pair.Item1[pair.Item2].members;
|
||||
members.Add(poolIndex, questId);
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
// load saved spawns from character DB
|
||||
{
|
||||
SQLResult result = DB.Characters.Query("SELECT pool_id, quest_id FROM pool_quest_save");
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
List<uint> unknownPoolIds = new();
|
||||
do
|
||||
{
|
||||
uint poolId = result.Read<uint>(0);
|
||||
uint questId = result.Read<uint>(1);
|
||||
|
||||
var it = lookup.LookupByKey(poolId);
|
||||
if (it == null || it.Item1 == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Table `pool_quest_save` contains reference to non-existant quest pool %u. Deleted.", poolId);
|
||||
unknownPoolIds.Add(poolId);
|
||||
continue;
|
||||
}
|
||||
|
||||
it.Item1[it.Item2].activeQuests.Add(questId);
|
||||
} while (result.NextRow());
|
||||
|
||||
SQLTransaction trans0 = new SQLTransaction();
|
||||
foreach (uint poolId in unknownPoolIds)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_POOL_QUEST_SAVE);
|
||||
stmt.AddValue(0, poolId);
|
||||
trans0.Append(stmt);
|
||||
}
|
||||
DB.Characters.CommitTransaction(trans0);
|
||||
}
|
||||
}
|
||||
|
||||
// post-processing and sanity checks
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
foreach (var pair in lookup)
|
||||
{
|
||||
if (pair.Value.Item1 == null)
|
||||
continue;
|
||||
|
||||
QuestPool pool = pair.Value.Item1[pair.Value.Item2];
|
||||
if (pool.members.Count < pool.numActive)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, $"Table `quest_pool_template` contains quest pool {pool.poolId} requesting {pool.numActive} spawns, but only has {pool.members.Count} members. Requested spawns reduced.");
|
||||
pool.numActive = (uint)pool.members.Count;
|
||||
}
|
||||
|
||||
bool doRegenerate = pool.activeQuests.Empty();
|
||||
if (!doRegenerate)
|
||||
{
|
||||
List<uint> accountedFor = new();
|
||||
uint activeCount = 0;
|
||||
for (uint i = (uint)pool.members.Count; (i--) != 0;)
|
||||
{
|
||||
var member = pool.members[i];
|
||||
if (member.Empty())
|
||||
{
|
||||
Log.outError(LogFilter.Sql, $"Table `quest_pool_members` contains no entries at index {i} for quest pool {pool.poolId}. Index removed.");
|
||||
pool.members.Remove(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the first member is active
|
||||
bool status = pool.activeQuests.Contains(member[0]);
|
||||
// temporarily remove any spawns that are accounted for
|
||||
if (status)
|
||||
{
|
||||
accountedFor.Add(member[0]);
|
||||
pool.activeQuests.Remove(member[0]);
|
||||
}
|
||||
|
||||
// now check if all other members also have the same status, and warn if not
|
||||
foreach (var id in member)
|
||||
{
|
||||
bool otherStatus = pool.activeQuests.Contains(id);
|
||||
if (status != otherStatus)
|
||||
Log.outWarn(LogFilter.Sql, $"Table `pool_quest_save` {(status ? "does not have" : "has")} quest {id} (in pool {pool.poolId}, index {i}) saved, but its index is{(status ? "" : " not")} " +
|
||||
$"active (because quest {member[0]} is{(status ? "" : " not")} in the table). Set quest {id} to {(status ? "" : "in")}active.");
|
||||
if (otherStatus)
|
||||
pool.activeQuests.Remove(id);
|
||||
if (status)
|
||||
accountedFor.Add(id);
|
||||
}
|
||||
|
||||
if (status)
|
||||
++activeCount;
|
||||
}
|
||||
|
||||
// warn for any remaining active spawns (not part of the pool)
|
||||
foreach (uint quest in pool.activeQuests)
|
||||
Log.outWarn(LogFilter.Sql, $"Table `pool_quest_save` has saved quest {quest} for pool {pool.poolId}, but that quest is not part of the pool. Skipped.");
|
||||
|
||||
// only the previously-found spawns should actually be active
|
||||
pool.activeQuests = accountedFor;
|
||||
|
||||
if (activeCount != pool.numActive)
|
||||
{
|
||||
doRegenerate = true;
|
||||
Log.outError(LogFilter.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);
|
||||
}
|
||||
|
||||
foreach (var memberKey in pool.members.Keys)
|
||||
{
|
||||
foreach (uint quest in pool.members[memberKey])
|
||||
{
|
||||
QuestPool refe = _poolLookup[quest];
|
||||
if (refe != null)
|
||||
{
|
||||
Log.outError(LogFilter.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, refe.poolId);
|
||||
continue;
|
||||
}
|
||||
refe = pool;
|
||||
}
|
||||
}
|
||||
}
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, $"Loaded {_dailyPools.Count} daily, {_weeklyPools.Count} weekly and {_monthlyPools.Count} monthly quest pools in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
|
||||
}
|
||||
|
||||
void Regenerate(List<QuestPool> pools)
|
||||
{
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
foreach (QuestPool pool in pools)
|
||||
{
|
||||
RegeneratePool(pool);
|
||||
SaveToDB(pool, trans);
|
||||
}
|
||||
DB.Characters.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
|
||||
public QuestPool FindQuestPool(uint poolId)
|
||||
{
|
||||
bool lambda(QuestPool p) { return p.poolId == poolId; };
|
||||
|
||||
var questPool = _dailyPools.Find(lambda);
|
||||
if (questPool != null)
|
||||
return questPool;
|
||||
|
||||
questPool = _weeklyPools.Find(lambda);
|
||||
if (questPool != null)
|
||||
return questPool;
|
||||
|
||||
questPool = _monthlyPools.Find(lambda);
|
||||
if (questPool != null)
|
||||
return questPool;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsQuestActive(uint questId)
|
||||
{
|
||||
var it = _poolLookup.LookupByKey(questId);
|
||||
if (it == null) // not pooled
|
||||
return true;
|
||||
|
||||
return it.activeQuests.Contains(questId);
|
||||
}
|
||||
|
||||
public void ChangeDailyQuests() { Regenerate(_dailyPools); }
|
||||
public void ChangeWeeklyQuests() { Regenerate(_weeklyPools); }
|
||||
public void ChangeMonthlyQuests() { Regenerate(_monthlyPools); }
|
||||
|
||||
public bool IsQuestPooled(uint questId) { return _poolLookup.ContainsKey(questId); }
|
||||
}
|
||||
|
||||
public class QuestPool
|
||||
{
|
||||
public uint poolId;
|
||||
public uint numActive;
|
||||
public MultiMap<uint, uint> members = new();
|
||||
public List<uint> activeQuests = new();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user