diff options
author | Treeston <treeston.mmoc@gmail.com> | 2019-08-04 12:22:57 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2021-12-18 15:28:45 +0100 |
commit | 51fbda4223442635a35d3225c0797d0151ea7051 (patch) | |
tree | 58c157637d44c568f7d720dddbce2c28c7ee9427 | |
parent | 6d5086da1747816e1a4e8518e16ab1923de22e1a (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
(cherry picked from commit a5e73e41c0e813e674bb0a644e0052052435494e)
27 files changed, 721 insertions, 661 deletions
diff --git a/sql/updates/world/master/2021_12_18_05_world_2019_08_04_00_world.sql b/sql/updates/world/master/2021_12_18_05_world_2019_08_04_00_world.sql new file mode 100644 index 00000000000..2c82d1d2491 --- /dev/null +++ b/sql/updates/world/master/2021_12_18_05_world_2019_08_04_00_world.sql @@ -0,0 +1,65 @@ +-- quest pools no longer support nesting, but they also don't need it +DROP TABLE IF EXISTS `quest_pool_members`; +CREATE TABLE `quest_pool_members` ( + `questId` int(10) unsigned not null, + `poolId` int(10) unsigned not null, + `poolIndex` tinyint(2) unsigned not null COMMENT 'Multiple quests with the same index will always spawn together!', + `description` varchar(255) default null, + PRIMARY KEY (`questId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TEMPORARY TABLE `_temp_pool_quests` ( + `sortIndex` int auto_increment not null, + `questId` int(10) unsigned not null, + `subPool` int(10) unsigned, + `topPool` int(10) unsigned not null, + `description` varchar(255) default null, + PRIMARY KEY (`sortIndex`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- build a lookup table of questid <-> nested pool <-> toplevel pool +INSERT INTO `_temp_pool_quests` (`questId`,`subPool`,`topPool`,`description`) +SELECT pq.`entry` as `questId`, IF(pp.`poolSpawnId` is null, null, pq.`pool_entry`) as `subPool`, IFNULL(pp.`poolSpawnId`,pq.`pool_entry`) AS `topPool`, pq.`description` +FROM `pool_quest` as pq LEFT JOIN `pool_members` pp ON (pp.`type`=2) and (pp.`spawnId` = pq.`pool_entry`) +ORDER BY `topPool` ASC, `subPool` ASC; + +-- delete any nested pools whose members we'll remove +DELETE FROM `pool_template` WHERE `entry` IN (SELECT DISTINCT `subPool` FROM `_temp_pool_quests` WHERE `subPool` is not null); +DELETE FROM `pool_members` WHERE `type`=2 AND `spawnId` IN (SELECT DISTINCT `subPool` FROM `_temp_pool_quests` WHERE `subPool` is not null); + +-- ensure quests without a subPool have different subPool values +UPDATE `_temp_pool_quests` SET `subPool`=`sortIndex` WHERE `subPool` is null; + +SET @pool_index = 0; +SET @last_pool = 0; +SET @last_subpool = 0; + +-- poolIndex is chosen as follows: +-- *) if we're starting a new pool, the index is 0 +-- *) if the preceding element had the same subpool, the index remains the same +-- *) if the preceding element had a different subpool, the index increases by 1 +INSERT INTO `quest_pool_members` (`questId`, `poolId`, `poolIndex`, `description`) +SELECT + `questId`, + `topPool` as `poolId`, + (CASE WHEN @last_subpool = `subPool` THEN @pool_index ELSE (@pool_index := (((@last_subpool := `subPool`) and 0) + (CASE WHEN @last_pool = `topPool` THEN @pool_index+1 ELSE ((@last_pool := `topPool`) and 0) END))) END) as `poolIndex`, + `description` +FROM `_temp_pool_quests`; + +-- drop the old table +DROP TABLE `pool_quest`; + +DROP TABLE IF EXISTS `quest_pool_template`; +CREATE TABLE `quest_pool_template` ( + `poolId` mediumint(8) unsigned not null, + `numActive` int(10) unsigned not null COMMENT 'Number of indices to have active at any time', + `description` varchar(255) default null, + PRIMARY KEY (`poolId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- copy any quest pool templates over +INSERT INTO `quest_pool_template` (`poolId`, `numActive`, `description`) +SELECT DISTINCT pt.`entry`,pt.`max_limit`,pt.`description` FROM `quest_pool_members` qpm LEFT JOIN `pool_template` pt ON (qpm.`poolId` = pt.`entry`); + +-- and delete them from the original table +DELETE pt FROM `pool_template` pt LEFT JOIN `quest_pool_template` qpt ON qpt.`poolId`=pt.`entry` WHERE qpt.`poolId` is not null; diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp index fab84bb5fbd..bbcc4153f97 100644 --- a/src/common/Utilities/Util.cpp +++ b/src/common/Utilities/Util.cpp @@ -207,6 +207,15 @@ std::string TimeToTimestampStr(time_t t) return Trinity::StringFormat("%04d-%02d-%02d_%02d-%02d-%02d", aTm.tm_year + 1900, aTm.tm_mon + 1, aTm.tm_mday, aTm.tm_hour, aTm.tm_min, aTm.tm_sec); } +std::string TimeToHumanReadable(time_t t) +{ + tm time; + localtime_r(&t, &time); + char buf[30]; + strftime(buf, 30, "%c", &time); + return std::string(buf); +} + /// Check if the string is a valid ip address representation bool IsIPAddress(char const* ipaddress) { diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index 2c06cb936e6..879ec4e0f67 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -66,6 +66,7 @@ TC_COMMON_API tm TimeBreakdown(time_t t); TC_COMMON_API std::string secsToTimeString(uint64 timeInSecs, bool shortText = false, bool hoursOnly = false); TC_COMMON_API uint32 TimeStringToSecs(std::string const& timestring); TC_COMMON_API std::string TimeToTimestampStr(time_t t); +TC_COMMON_API std::string TimeToHumanReadable(time_t t); // Percentage calculation template <class T, class U> diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index ff1c1b3e7e6..0879d50c074 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -31,8 +31,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() "ig.gemItemId1, ig.gemBonuses1, ig.gemContext1, ig.gemScalingLevel1, ig.gemItemId2, ig.gemBonuses2, ig.gemContext2, ig.gemScalingLevel2, ig.gemItemId3, ig.gemBonuses3, ig.gemContext3, ig.gemScalingLevel3, " \ "im.fixedScalingLevel, im.artifactKnowledgeLevel" - PrepareStatement(CHAR_DEL_QUEST_POOL_SAVE, "DELETE FROM pool_quest_save WHERE pool_id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_QUEST_POOL_SAVE, "INSERT INTO pool_quest_save (pool_id, quest_id) VALUES (?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_POOL_QUEST_SAVE, "DELETE FROM pool_quest_save WHERE pool_id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_POOL_QUEST_SAVE, "INSERT INTO pool_quest_save (pool_id, quest_id) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM, "DELETE FROM guild_bank_item WHERE guildid = ? AND TabId = ? AND SlotId = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_EXPIRED_BANS, "UPDATE character_banned SET active = 0 WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate <> bandate", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHECK_NAME, "SELECT 1 FROM characters WHERE name = ?", CONNECTION_BOTH); @@ -548,7 +548,6 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_PINFO_XP, "SELECT a.xp, b.guid FROM characters a LEFT JOIN guild_member b ON a.guid = b.guid WHERE a.guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ, orientation FROM character_homebind WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_GUID_NAME_BY_ACC, "SELECT guid, name, online FROM characters WHERE account = ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_POOL_QUEST_SAVE, "SELECT quest_id FROM pool_quest_save WHERE pool_id = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_CUSTOMIZE_INFO, "SELECT name, race, class, gender, at_login FROM characters WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, "SELECT at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_INSTANCE, "SELECT data, completedEncounters, entranceId FROM instance WHERE map = ? AND id = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 7460f5d8e28..e1862ce8eaf 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -28,8 +28,8 @@ enum CharacterDatabaseStatements : uint32 name for a suiting suffix. */ - CHAR_DEL_QUEST_POOL_SAVE, - CHAR_INS_QUEST_POOL_SAVE, + CHAR_DEL_POOL_QUEST_SAVE, + CHAR_INS_POOL_QUEST_SAVE, CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM, CHAR_DEL_EXPIRED_BANS, CHAR_SEL_CHECK_NAME, @@ -436,7 +436,6 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_PINFO_BANS, CHAR_SEL_CHAR_HOMEBIND, CHAR_SEL_CHAR_GUID_NAME_BY_ACC, - CHAR_SEL_POOL_QUEST_SAVE, CHAR_SEL_CHAR_CUSTOMIZE_INFO, CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, CHAR_SEL_INSTANCE, diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 9352eeeedca..abd602636e5 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -23,7 +23,6 @@ void WorldDatabaseConnection::DoPrepareStatements() if (!m_reconnecting) m_stmts.resize(MAX_WORLDDATABASE_STATEMENTS); - PrepareStatement(WORLD_SEL_QUEST_POOLS, "SELECT entry, pool_entry FROM pool_quest", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_LINKED_RESPAWN, "DELETE FROM linked_respawn WHERE guid = ? AND linkType = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_DEL_LINKED_RESPAWN_MASTER, "DELETE FROM linked_respawn WHERE linkedGuid = ? AND linkType = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_REP_LINKED_RESPAWN, "REPLACE INTO linked_respawn (guid, linkedGuid, linkType) VALUES (?, ?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h index 2922fbde8f4..cf2688c193c 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.h +++ b/src/server/database/Database/Implementation/WorldDatabase.h @@ -28,7 +28,6 @@ enum WorldDatabaseStatements : uint32 name for a suiting suffix. */ - WORLD_SEL_QUEST_POOLS, WORLD_DEL_LINKED_RESPAWN, WORLD_DEL_LINKED_RESPAWN_MASTER, WORLD_REP_LINKED_RESPAWN, diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index b7c17ae085f..e5fb9f6a7dc 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1864,24 +1864,12 @@ void Creature::LoadTemplateRoot() bool Creature::hasQuest(uint32 quest_id) const { - QuestRelationBounds qr = sObjectMgr->GetCreatureQuestRelationBounds(GetEntry()); - for (QuestRelations::const_iterator itr = qr.first; itr != qr.second; ++itr) - { - if (itr->second == quest_id) - return true; - } - return false; + return sObjectMgr->GetCreatureQuestRelations(GetEntry()).HasQuest(quest_id); } bool Creature::hasInvolvedQuest(uint32 quest_id) const { - QuestRelationBounds qir = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(GetEntry()); - for (QuestRelations::const_iterator itr = qir.first; itr != qir.second; ++itr) - { - if (itr->second == quest_id) - return true; - } - return false; + return sObjectMgr->GetCreatureQuestInvolvedRelations(GetEntry()).HasQuest(quest_id); } /*static*/ bool Creature::DeleteFromDB(ObjectGuid::LowType spawnId) diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 50ed73a832d..6540087ce07 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -1330,24 +1330,12 @@ bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap /*********************************************************/ bool GameObject::hasQuest(uint32 quest_id) const { - QuestRelationBounds qr = sObjectMgr->GetGOQuestRelationBounds(GetEntry()); - for (QuestRelations::const_iterator itr = qr.first; itr != qr.second; ++itr) - { - if (itr->second == quest_id) - return true; - } - return false; + return sObjectMgr->GetGOQuestRelations(GetEntry()).HasQuest(quest_id); } bool GameObject::hasInvolvedQuest(uint32 quest_id) const { - QuestRelationBounds qir = sObjectMgr->GetGOQuestInvolvedRelationBounds(GetEntry()); - for (QuestRelations::const_iterator itr = qir.first; itr != qir.second; ++itr) - { - if (itr->second == quest_id) - return true; - } - return false; + return sObjectMgr->GetGOQuestInvolvedRelations(GetEntry()).HasQuest(quest_id); } bool GameObject::IsTransport() const diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 22afdf05479..0906e47c203 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -14918,15 +14918,15 @@ int32 Player::GetQuestLevel(Quest const* quest) const void Player::PrepareQuestMenu(ObjectGuid guid) { - QuestRelationBounds objectQR; - QuestRelationBounds objectQIR; + QuestRelationResult objectQR; + QuestRelationResult objectQIR; // pets also can have quests Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid); if (creature) { - objectQR = sObjectMgr->GetCreatureQuestRelationBounds(creature->GetEntry()); - objectQIR = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(creature->GetEntry()); + objectQR = sObjectMgr->GetCreatureQuestRelations(creature->GetEntry()); + objectQIR = sObjectMgr->GetCreatureQuestInvolvedRelations(creature->GetEntry()); } else { @@ -14937,8 +14937,8 @@ void Player::PrepareQuestMenu(ObjectGuid guid) GameObject* gameObject = _map->GetGameObject(guid); if (gameObject) { - objectQR = sObjectMgr->GetGOQuestRelationBounds(gameObject->GetEntry()); - objectQIR = sObjectMgr->GetGOQuestInvolvedRelationBounds(gameObject->GetEntry()); + objectQR = sObjectMgr->GetGOQuestRelations(gameObject->GetEntry()); + objectQIR = sObjectMgr->GetGOQuestInvolvedRelations(gameObject->GetEntry()); } else return; @@ -14947,9 +14947,8 @@ void Player::PrepareQuestMenu(ObjectGuid guid) QuestMenu &qm = PlayerTalkClass->GetQuestMenu(); qm.ClearMenu(); - for (QuestRelations::const_iterator i = objectQIR.first; i != objectQIR.second; ++i) + for (uint32 quest_id : objectQIR) { - uint32 quest_id = i->second; QuestStatus status = GetQuestStatus(quest_id); if (status == QUEST_STATUS_COMPLETE) qm.AddMenuItem(quest_id, 4); @@ -14959,9 +14958,8 @@ void Player::PrepareQuestMenu(ObjectGuid guid) // qm.AddMenuItem(quest_id, 2); } - for (QuestRelations::const_iterator i = objectQR.first; i != objectQR.second; ++i) + for (uint32 quest_id : objectQR) { - uint32 quest_id = i->second; Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id); if (!quest) continue; @@ -15024,7 +15022,7 @@ bool Player::IsActiveQuest(uint32 quest_id) const Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* quest) const { - QuestRelationBounds objectQR; + QuestRelationResult quests; uint32 nextQuestID = quest->GetNextQuestInChain(); switch (guid.GetHigh()) @@ -15037,7 +15035,7 @@ Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* quest) const case HighGuid::Vehicle: { if (Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid)) - objectQR = sObjectMgr->GetCreatureQuestRelationBounds(creature->GetEntry()); + quests = sObjectMgr->GetCreatureQuestRelations(creature->GetEntry()); else return nullptr; break; @@ -15049,7 +15047,7 @@ Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* quest) const Map* _map = IsInWorld() ? GetMap() : sMapMgr->FindMap(GetMapId(), GetInstanceId()); ASSERT(_map); if (GameObject* gameObject = _map->GetGameObject(guid)) - objectQR = sObjectMgr->GetGOQuestRelationBounds(gameObject->GetEntry()); + quests = sObjectMgr->GetGOQuestRelations(gameObject->GetEntry()); else return nullptr; break; @@ -15058,12 +15056,9 @@ Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* quest) const return nullptr; } - // for unit and go state - for (QuestRelations::const_iterator itr = objectQR.first; itr != objectQR.second; ++itr) - { - if (itr->second == nextQuestID) + if (uint32 nextQuestID = quest->GetNextQuestInChain()) + if (quests.HasQuest(nextQuestID)) return sObjectMgr->GetQuestTemplate(nextQuestID); - } return nullptr; } @@ -16567,8 +16562,7 @@ void Player::SendQuestUpdate(uint32 questId) QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) { - QuestRelationBounds qr; - QuestRelationBounds qir; + QuestRelationResult qr, qir; switch (questgiver->GetTypeId()) { @@ -16577,8 +16571,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) if (GameObjectAI* ai = questgiver->ToGameObject()->AI()) if (Optional<QuestGiverStatus> questStatus = ai->GetDialogStatus(this)) return *questStatus; - qr = sObjectMgr->GetGOQuestRelationBounds(questgiver->GetEntry()); - qir = sObjectMgr->GetGOQuestInvolvedRelationBounds(questgiver->GetEntry()); + qr = sObjectMgr->GetGOQuestRelations(questgiver->GetEntry()); + qir = sObjectMgr->GetGOQuestInvolvedRelations(questgiver->GetEntry()); break; } case TYPEID_UNIT: @@ -16586,8 +16580,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) if (CreatureAI* ai = questgiver->ToCreature()->AI()) if (Optional<QuestGiverStatus> questStatus = ai->GetDialogStatus(this)) return *questStatus; - qr = sObjectMgr->GetCreatureQuestRelationBounds(questgiver->GetEntry()); - qir = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(questgiver->GetEntry()); + qr = sObjectMgr->GetCreatureQuestRelations(questgiver->GetEntry()); + qir = sObjectMgr->GetCreatureQuestInvolvedRelations(questgiver->GetEntry()); break; } default: @@ -16599,9 +16593,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) QuestGiverStatus result = QuestGiverStatus::None; - for (QuestRelations::const_iterator i = qir.first; i != qir.second; ++i) + for (uint32 questId : qir) { - uint32 questId = i->second; Quest const* quest = sObjectMgr->GetQuestTemplate(questId); if (!quest) continue; @@ -16635,9 +16628,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) } } - for (QuestRelations::const_iterator i = qr.first; i != qr.second; ++i) + for (uint32 questId : qr) { - uint32 questId = i->second; Quest const* quest = sObjectMgr->GetQuestTemplate(questId); if (!quest) continue; diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp index b7f7998e911..4a09390301e 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -1487,7 +1487,7 @@ void GameEventMgr::UpdateEventQuests(uint16 event_id, bool activate) QuestRelList::iterator itr; for (itr = mGameEventCreatureQuests[event_id].begin(); itr != mGameEventCreatureQuests[event_id].end(); ++itr) { - QuestRelations* CreatureQuestMap = sObjectMgr->GetCreatureQuestRelationMap(); + QuestRelations* CreatureQuestMap = sObjectMgr->GetCreatureQuestRelationMapHACK(); if (activate) // Add the pair(id, quest) to the multimap CreatureQuestMap->insert(QuestRelations::value_type(itr->first, itr->second)); else @@ -1512,7 +1512,7 @@ void GameEventMgr::UpdateEventQuests(uint16 event_id, bool activate) } for (itr = mGameEventGameObjectQuests[event_id].begin(); itr != mGameEventGameObjectQuests[event_id].end(); ++itr) { - QuestRelations* GameObjectQuestMap = sObjectMgr->GetGOQuestRelationMap(); + QuestRelations* GameObjectQuestMap = sObjectMgr->GetGOQuestRelationMapHACK(); if (activate) // Add the pair(id, quest) to the multimap GameObjectQuestMap->insert(QuestRelations::value_type(itr->first, itr->second)); else diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 2502bb8c61f..01125c54a61 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -8367,7 +8367,7 @@ void ObjectMgr::DeleteGameObjectData(ObjectGuid::LowType guid) _gameObjectDataStore.erase(guid); } -void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReverse* reverseMap, std::string const& table, bool starter, bool go) +void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReverse* reverseMap, std::string const& table) { uint32 oldMSTime = getMSTime(); @@ -8375,7 +8375,7 @@ void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReve uint32 count = 0; - QueryResult result = WorldDatabase.PQuery("SELECT id, quest, pool_entry FROM %s qr LEFT JOIN pool_quest pq ON qr.quest = pq.entry", table.c_str()); + QueryResult result = WorldDatabase.PQuery("SELECT id, quest FROM %s", table.c_str()); if (!result) { @@ -8383,15 +8383,10 @@ void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReve return; } - PooledQuestRelation* poolRelationMap = go ? &sPoolMgr->mQuestGORelation : &sPoolMgr->mQuestCreatureRelation; - if (starter) - poolRelationMap->clear(); - do { uint32 id = result->Fetch()[0].GetUInt32(); uint32 quest = result->Fetch()[1].GetUInt32(); - uint32 poolId = result->Fetch()[2].GetUInt32(); if (!_questTemplates.count(quest)) { @@ -8399,15 +8394,9 @@ void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReve continue; } - if (!poolId || !starter) - { - map.insert(QuestRelations::value_type(id, quest)); - if (reverseMap) - reverseMap->insert(QuestRelationsReverse::value_type(quest, id)); - } - else - poolRelationMap->insert(PooledQuestRelation::value_type(quest, id)); - + map.insert(QuestRelations::value_type(id, quest)); + if (reverseMap) + reverseMap->insert(QuestRelationsReverse::value_type(quest, id)); ++count; } while (result->NextRow()); @@ -8416,7 +8405,7 @@ void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReve void ObjectMgr::LoadGameobjectQuestStarters() { - LoadQuestRelationsHelper(_goQuestRelations, nullptr, "gameobject_queststarter", true, true); + LoadQuestRelationsHelper(_goQuestRelations, nullptr, "gameobject_queststarter"); for (QuestRelations::iterator itr = _goQuestRelations.begin(); itr != _goQuestRelations.end(); ++itr) { @@ -8430,7 +8419,7 @@ void ObjectMgr::LoadGameobjectQuestStarters() void ObjectMgr::LoadGameobjectQuestEnders() { - LoadQuestRelationsHelper(_goQuestInvolvedRelations, &_goQuestInvolvedRelationsReverse, "gameobject_questender", false, true); + LoadQuestRelationsHelper(_goQuestInvolvedRelations, &_goQuestInvolvedRelationsReverse, "gameobject_questender"); for (QuestRelations::iterator itr = _goQuestInvolvedRelations.begin(); itr != _goQuestInvolvedRelations.end(); ++itr) { @@ -8444,7 +8433,7 @@ void ObjectMgr::LoadGameobjectQuestEnders() void ObjectMgr::LoadCreatureQuestStarters() { - LoadQuestRelationsHelper(_creatureQuestRelations, nullptr, "creature_queststarter", true, false); + LoadQuestRelationsHelper(_creatureQuestRelations, nullptr, "creature_queststarter"); for (QuestRelations::iterator itr = _creatureQuestRelations.begin(); itr != _creatureQuestRelations.end(); ++itr) { @@ -8458,7 +8447,7 @@ void ObjectMgr::LoadCreatureQuestStarters() void ObjectMgr::LoadCreatureQuestEnders() { - LoadQuestRelationsHelper(_creatureQuestInvolvedRelations, &_creatureQuestInvolvedRelationsReverse, "creature_questender", false, false); + LoadQuestRelationsHelper(_creatureQuestInvolvedRelations, &_creatureQuestInvolvedRelationsReverse, "creature_questender"); for (QuestRelations::iterator itr = _creatureQuestInvolvedRelations.begin(); itr != _creatureQuestInvolvedRelations.end(); ++itr) { @@ -8470,6 +8459,17 @@ void ObjectMgr::LoadCreatureQuestEnders() } } +void QuestRelationResult::Iterator::_skip() +{ + while ((_it != _end) && !Quest::IsTakingQuestEnabled(_it->second)) + ++_it; +} + +bool QuestRelationResult::HasQuest(uint32 questId) const +{ + return (std::find_if(_begin, _end, [questId](QuestRelations::value_type const& pair) { return (pair.second == questId); }) != _end) && (!_onlyActive || Quest::IsTakingQuestEnabled(questId)); +} + void ObjectMgr::LoadReservedPlayersNames() { uint32 oldMSTime = getMSTime(); diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 2ed245776aa..db973b2fd56 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -37,6 +37,7 @@ #include "SharedDefines.h" #include "Trainer.h" #include "VehicleDefines.h" +#include <iterator> #include <map> #include <unordered_map> @@ -556,8 +557,54 @@ typedef std::unordered_map<uint32, TrinityString> TrinityStringContainer; typedef std::multimap<uint32, uint32> QuestRelations; // unit/go -> quest typedef std::multimap<uint32, uint32> QuestRelationsReverse; // quest -> unit/go -typedef std::pair<QuestRelations::const_iterator, QuestRelations::const_iterator> QuestRelationBounds; -typedef std::pair<QuestRelationsReverse::const_iterator, QuestRelationsReverse::const_iterator> QuestRelationReverseBounds; + +struct QuestRelationResult +{ + public: + struct Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = QuestRelations::mapped_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = void; + + Iterator(QuestRelations::const_iterator it, QuestRelations::const_iterator end, bool onlyActive) + : _it(it), _end(end), _onlyActive(onlyActive) + { + skip(); + } + + bool operator==(Iterator const& other) const { return _it == other._it; } + bool operator!=(Iterator const& other) const { return _it != other._it; } + + Iterator& operator++() { ++_it; skip(); return *this; } + Iterator operator++(int) { Iterator t = *this; ++*this; return t; } + + value_type operator*() const { return _it->second; } + + private: + void skip() { if (_onlyActive) _skip(); } + void _skip(); + + QuestRelations::const_iterator _it, _end; + bool _onlyActive; + }; + + QuestRelationResult() : _onlyActive(false) {} + QuestRelationResult(std::pair<QuestRelations::const_iterator, QuestRelations::const_iterator> range, bool onlyActive) + : _begin(range.first), _end(range.second), _onlyActive(onlyActive) {} + + Iterator begin() const { return { _begin, _end, _onlyActive }; } + Iterator end() const { return { _end, _end, _onlyActive }; } + + bool HasQuest(uint32 questId) const; + + private: + QuestRelations::const_iterator _begin, _end; + bool _onlyActive; +}; typedef std::multimap<int32, uint32> ExclusiveQuestGroups; // exclusiveGroupId -> quest typedef std::pair<ExclusiveQuestGroups::const_iterator, ExclusiveQuestGroups::const_iterator> ExclusiveQuestGroupsBounds; @@ -1220,45 +1267,14 @@ class TC_GAME_API ObjectMgr void LoadCreatureQuestStarters(); void LoadCreatureQuestEnders(); - QuestRelations* GetGOQuestRelationMap() - { - return &_goQuestRelations; - } - - QuestRelationBounds GetGOQuestRelationBounds(uint32 go_entry) const - { - return _goQuestRelations.equal_range(go_entry); - } - - QuestRelationBounds GetGOQuestInvolvedRelationBounds(uint32 go_entry) const - { - return _goQuestInvolvedRelations.equal_range(go_entry); - } - - QuestRelationReverseBounds GetGOQuestInvolvedRelationReverseBounds(uint32 questId) const - { - return _goQuestInvolvedRelationsReverse.equal_range(questId); - } - - QuestRelations* GetCreatureQuestRelationMap() - { - return &_creatureQuestRelations; - } - - QuestRelationBounds GetCreatureQuestRelationBounds(uint32 creature_entry) const - { - return _creatureQuestRelations.equal_range(creature_entry); - } - - QuestRelationBounds GetCreatureQuestInvolvedRelationBounds(uint32 creature_entry) const - { - return _creatureQuestInvolvedRelations.equal_range(creature_entry); - } - - QuestRelationReverseBounds GetCreatureQuestInvolvedRelationReverseBounds(uint32 questId) const - { - return _creatureQuestInvolvedRelationsReverse.equal_range(questId); - } + QuestRelations* GetGOQuestRelationMapHACK() { return &_goQuestRelations; } + QuestRelationResult GetGOQuestRelations(uint32 entry) const { return GetQuestRelationsFrom(_goQuestRelations, entry, true); } + QuestRelationResult GetGOQuestInvolvedRelations(uint32 entry) const { return GetQuestRelationsFrom(_goQuestInvolvedRelations, entry, false); } + Trinity::IteratorPair<QuestRelationsReverse::const_iterator> GetGOQuestInvolvedRelationReverseBounds(uint32 questId) const { return _goQuestInvolvedRelationsReverse.equal_range(questId); } + QuestRelations* GetCreatureQuestRelationMapHACK() { return &_creatureQuestRelations; } + QuestRelationResult GetCreatureQuestRelations(uint32 entry) const { return GetQuestRelationsFrom(_creatureQuestRelations, entry, true); } + QuestRelationResult GetCreatureQuestInvolvedRelations(uint32 entry) const { return GetQuestRelationsFrom(_creatureQuestInvolvedRelations, entry, false); } + Trinity::IteratorPair<QuestRelationsReverse::const_iterator> GetCreatureQuestInvolvedRelationReverseBounds(uint32 questId) const { return _creatureQuestInvolvedRelationsReverse.equal_range(questId); } ExclusiveQuestGroupsBounds GetExclusiveQuestGroupBounds(int32 exclusiveGroupId) const { @@ -1805,7 +1821,8 @@ class TC_GAME_API ObjectMgr private: void LoadScripts(ScriptsType type); - void LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReverse* reverseMap, std::string const& table, bool starter, bool go); + void LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReverse* reverseMap, std::string const& table); + QuestRelationResult GetQuestRelationsFrom(QuestRelations const& map, uint32 key, bool onlyActive) const { return { map.equal_range(key), onlyActive }; } void PlayerCreateInfoAddItemHelper(uint32 race_, uint32 class_, uint32 itemId, int32 count); MailLevelRewardContainer _mailLevelRewardStore; diff --git a/src/server/game/Handlers/QueryHandler.cpp b/src/server/game/Handlers/QueryHandler.cpp index c146cefcb91..eca0a1e7556 100644 --- a/src/server/game/Handlers/QueryHandler.cpp +++ b/src/server/game/Handlers/QueryHandler.cpp @@ -258,15 +258,13 @@ void WorldSession::HandleQueryQuestCompletionNPCs(WorldPackets::Query::QueryQues questCompletionNPC.QuestID = questID; - auto creatures = sObjectMgr->GetCreatureQuestInvolvedRelationReverseBounds(questID); - for (auto it = creatures.first; it != creatures.second; ++it) - questCompletionNPC.NPCs.push_back(it->second); + for (auto const& creatures : sObjectMgr->GetCreatureQuestInvolvedRelationReverseBounds(questID)) + questCompletionNPC.NPCs.push_back(creatures.second); - auto gos = sObjectMgr->GetGOQuestInvolvedRelationReverseBounds(questID); - for (auto it = gos.first; it != gos.second; ++it) - questCompletionNPC.NPCs.push_back(it->second | 0x80000000); // GO mask + for (auto const& gos : sObjectMgr->GetGOQuestInvolvedRelationReverseBounds(questID)) + questCompletionNPC.NPCs.push_back(gos.second | 0x80000000); // GO mask - response.QuestCompletionNPCs.push_back(questCompletionNPC); + response.QuestCompletionNPCs.push_back(std::move(questCompletionNPC)); } SendPacket(response.Write()); diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp index 467b6b0d421..19e9d914a83 100644 --- a/src/server/game/Handlers/QuestHandler.cpp +++ b/src/server/game/Handlers/QuestHandler.cpp @@ -33,6 +33,7 @@ #include "PoolMgr.h" #include "QuestDef.h" #include "QuestPackets.h" +#include "QuestPools.h" #include "ReputationMgr.h" #include "ScriptMgr.h" #include "World.h" @@ -641,7 +642,7 @@ void WorldSession::HandlePushQuestToParty(WorldPackets::Quest::PushQuestToParty& } // in pool and not currently available (wintergrasp weekly, dalaran weekly) - can't share - if (sPoolMgr->IsPartOfAPool<Quest>(packet.QuestID) && !sPoolMgr->IsSpawnedObject<Quest>(packet.QuestID)) + if (!sQuestPoolMgr->IsQuestActive(packet.QuestID)) { sender->SendPushToPartyResponse(sender, QuestPushReason::NotDaily); return; diff --git a/src/server/game/Pools/PoolMgr.cpp b/src/server/game/Pools/PoolMgr.cpp index 89d6e1ab450..10af28c306d 100644 --- a/src/server/game/Pools/PoolMgr.cpp +++ b/src/server/game/Pools/PoolMgr.cpp @@ -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) { @@ -89,13 +82,6 @@ void ActivePoolData::ActivateObject<Pool>(uint64 sub_pool_id, uint32 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) { mSpawnedCreatures.erase(db_guid); @@ -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); diff --git a/src/server/game/Pools/PoolMgr.h b/src/server/game/Pools/PoolMgr.h index 839f16cdc4e..215cbdb0f8b 100644 --- a/src/server/game/Pools/PoolMgr.h +++ b/src/server/game/Pools/PoolMgr.h @@ -61,12 +61,9 @@ class TC_GAME_API ActivePoolData template<typename T> void RemoveObject(uint64 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; }; @@ -101,10 +98,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: @@ -115,8 +108,6 @@ class TC_GAME_API PoolMgr static PoolMgr* instance(); void LoadFromDB(); - void LoadQuestPools(); - void SaveQuestsToDB(); void Initialize(); @@ -135,12 +126,6 @@ class TC_GAME_API PoolMgr template<typename T> void UpdatePool(uint32 pool_id, uint64 db_guid_or_pool_id); - void ChangeDailyQuests(); - void ChangeWeeklyQuests(); - - PooledQuestRelation mQuestCreatureRelation; - PooledQuestRelation mQuestGORelation; - private: template<typename T> void SpawnPool(uint32 pool_id, uint64 db_guid_or_pool_id); @@ -149,7 +134,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<uint64, uint32> SearchPair; typedef std::map<uint64, uint32> SearchMap; @@ -157,11 +141,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; @@ -191,17 +173,6 @@ inline uint32 PoolMgr::IsPartOfAPool<GameObject>(uint64 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>(uint64 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>(uint64 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..e60eda8441d --- /dev/null +++ b/src/server/game/Pools/QuestPools.cpp @@ -0,0 +1,295 @@ +/* + * 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 <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 <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, 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, 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()); + + 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 %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) +{ + 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()); +} + + diff --git a/src/server/game/Pools/QuestPools.h b/src/server/game/Pools/QuestPools.h new file mode 100644 index 00000000000..d889e933a7d --- /dev/null +++ b/src/server/game/Pools/QuestPools.h @@ -0,0 +1,65 @@ +/* + * 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 <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 diff --git a/src/server/game/Quests/QuestDef.cpp b/src/server/game/Quests/QuestDef.cpp index 8f2a01dae38..e85f559ec2a 100644 --- a/src/server/game/Quests/QuestDef.cpp +++ b/src/server/game/Quests/QuestDef.cpp @@ -24,6 +24,7 @@ #include "ObjectMgr.h" #include "Player.h" #include "QuestPackets.h" +#include "QuestPools.h" #include "SpellMgr.h" #include "World.h" #include "WorldSession.h" @@ -308,6 +309,14 @@ uint32 Quest::XPValue(Player const* player) const return 0; } +/*static*/ bool Quest::IsTakingQuestEnabled(uint32 questId) +{ + if (!sQuestPoolMgr->IsQuestActive(questId)) + return false; + + return true; +} + uint32 Quest::MoneyValue(Player const* player) const { if (QuestMoneyRewardEntry const* money = sQuestMoneyRewardStore.LookupEntry(player->GetQuestLevel(this))) diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index 8bf1db4407e..87d562cdd67 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -508,6 +508,9 @@ class TC_GAME_API Quest // Possibly deprecated flag bool IsUnavailable() const { return HasFlag(QUEST_FLAGS_UNAVAILABLE); } + // whether the quest is globally enabled (spawned by pool, game event active etc.) + static bool IsTakingQuestEnabled(uint32 questId); + // table data accessors: uint32 GetQuestId() const { return _id; } uint32 GetQuestType() const { return _type; } diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b002ec150dd..ede13f04dae 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -77,6 +77,7 @@ #include "Player.h" #include "PlayerDump.h" #include "PoolMgr.h" +#include "QuestPools.h" #include "Realm.h" #include "ScenarioMgr.h" #include "ScriptMgr.h" @@ -1090,7 +1091,20 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_CAST_UNSTUCK] = sConfigMgr->GetBoolDefault("CastUnstuck", true); m_int_configs[CONFIG_INSTANCE_RESET_TIME_HOUR] = sConfigMgr->GetIntDefault("Instance.ResetTimeHour", 4); m_int_configs[CONFIG_INSTANCE_UNLOAD_DELAY] = sConfigMgr->GetIntDefault("Instance.UnloadDelay", 30 * MINUTE * IN_MILLISECONDS); + m_int_configs[CONFIG_DAILY_QUEST_RESET_TIME_HOUR] = sConfigMgr->GetIntDefault("Quests.DailyResetTime", 3); + if (m_int_configs[CONFIG_DAILY_QUEST_RESET_TIME_HOUR] < 0 || m_int_configs[CONFIG_DAILY_QUEST_RESET_TIME_HOUR] > 23) + { + TC_LOG_ERROR("server.loading", "Quests.DailyResetTime (%i) must be in range 0..23. Set to 3.", m_int_configs[CONFIG_DAILY_QUEST_RESET_TIME_HOUR]); + m_int_configs[CONFIG_DAILY_QUEST_RESET_TIME_HOUR] = 3; + } + + m_int_configs[CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY] = sConfigMgr->GetIntDefault("Quests.WeeklyResetWDay", 3); + if (m_int_configs[CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY] < 0 || m_int_configs[CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY] > 6) + { + TC_LOG_ERROR("server.loading", "Quests.WeeklyResetDay (%i) must be in range 0..6. Set to 3 (Wednesday).", m_int_configs[CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY]); + m_int_configs[CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY] = 3; + } m_int_configs[CONFIG_MAX_PRIMARY_TRADE_SKILL] = sConfigMgr->GetIntDefault("MaxPrimaryTradeSkill", 2); m_int_configs[CONFIG_MIN_PETITION_SIGNS] = sConfigMgr->GetIntDefault("MinPetitionSigns", 4); @@ -1988,6 +2002,8 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Objects Pooling Data..."); sPoolMgr->LoadFromDB(); + TC_LOG_INFO("server.loading", "Loading Quest Pooling Data..."); + sQuestPoolMgr->LoadFromDB(); // must be after quest templates TC_LOG_INFO("server.loading", "Loading Game Event Data..."); // must be after loading pools fully sGameEventMgr->LoadFromDB(); @@ -2377,14 +2393,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Deleting expired bans..."); LoginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate<>bandate"); // One-time query - TC_LOG_INFO("server.loading", "Calculate next daily quest reset time..."); - InitDailyQuestResetTime(); - - TC_LOG_INFO("server.loading", "Calculate next weekly quest reset time..."); - InitWeeklyQuestResetTime(); - - TC_LOG_INFO("server.loading", "Calculate next monthly quest reset time..."); - InitMonthlyQuestResetTime(); + TC_LOG_INFO("server.loading", "Initializing quest reset times..."); + InitQuestResetTimes(); + CheckScheduledResetTimes(); TC_LOG_INFO("server.loading", "Calculate random battleground reset time..."); InitRandomBGResetTime(); @@ -2507,20 +2518,7 @@ void World::Update(uint32 diff) } } - /// Handle daily quests reset time - if (currentGameTime > m_NextDailyQuestReset) - { - DailyReset(); - InitDailyQuestResetTime(false); - } - - /// Handle weekly quests reset time - if (currentGameTime > m_NextWeeklyQuestReset) - ResetWeeklyQuests(); - - /// Handle monthly quests reset time - if (currentGameTime > m_NextMonthlyQuestReset) - ResetMonthlyQuests(); + CheckScheduledResetTimes(); if (currentGameTime > m_NextRandomBGReset) ResetRandomBG(); @@ -3364,55 +3362,128 @@ void World::_UpdateRealmCharCount(PreparedQueryResult resultCharCount) } } -void World::InitWeeklyQuestResetTime() +void World::InitQuestResetTimes() { - time_t wstime = uint64(sWorld->getWorldState(WS_WEEKLY_QUEST_RESET_TIME)); - time_t curtime = GameTime::GetGameTime(); - m_NextWeeklyQuestReset = wstime < curtime ? curtime : time_t(wstime); + m_NextDailyQuestReset = sWorld->getWorldState(WS_DAILY_QUEST_RESET_TIME); + m_NextWeeklyQuestReset = sWorld->getWorldState(WS_WEEKLY_QUEST_RESET_TIME); + m_NextMonthlyQuestReset = sWorld->getWorldState(WS_MONTHLY_QUEST_RESET_TIME); } -void World::InitDailyQuestResetTime(bool loading) +static time_t GetNextDailyResetTime(time_t t) { - time_t mostRecentQuestTime = 0; + return GetLocalHourTimestamp(t, sWorld->getIntConfig(CONFIG_DAILY_QUEST_RESET_TIME_HOUR), true); +} - if (loading) - { - QueryResult result = CharacterDatabase.Query("SELECT MAX(time) FROM character_queststatus_daily"); - if (result) - { - Field* fields = result->Fetch(); - mostRecentQuestTime = time_t(fields[0].GetUInt32()); - } - } +void World::DailyReset() +{ + // reset all saved quest status + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_DAILY); + CharacterDatabase.Execute(stmt); - // FIX ME: client not show day start time - time_t curTime = GameTime::GetGameTime(); - tm localTm; - localtime_r(&curTime, &localTm); - localTm.tm_hour = getIntConfig(CONFIG_DAILY_QUEST_RESET_TIME_HOUR); - localTm.tm_min = 0; - localTm.tm_sec = 0; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS); + stmt->setUInt32(0, 1); + CharacterDatabase.Execute(stmt); - // current day reset time - time_t curDayResetTime = mktime(&localTm); + // reset all quest status in memory + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + if (Player* player = itr->second->GetPlayer()) + player->DailyReset(); + + // reselect pools + sQuestPoolMgr->ChangeDailyQuests(); - // last reset time before current moment - time_t resetTime = (curTime < curDayResetTime) ? curDayResetTime - DAY : curDayResetTime; + // store next reset time + time_t now = GameTime::GetGameTime(); + time_t next = GetNextDailyResetTime(now); + ASSERT(now < next); - // need reset (if we have quest time before last reset time (not processed by some reason) - if (mostRecentQuestTime && mostRecentQuestTime <= resetTime) - m_NextDailyQuestReset = mostRecentQuestTime; - else // plan next reset time - m_NextDailyQuestReset = (curTime >= curDayResetTime) ? curDayResetTime + DAY : curDayResetTime; + m_NextDailyQuestReset = next; + sWorld->setWorldState(WS_DAILY_QUEST_RESET_TIME, uint64(next)); - sWorld->setWorldState(WS_DAILY_QUEST_RESET_TIME, uint64(m_NextDailyQuestReset)); + TC_LOG_INFO("misc", "Daily quests for all characters have been reset."); } -void World::InitMonthlyQuestResetTime() +static time_t GetNextWeeklyResetTime(time_t t) +{ + t = GetNextDailyResetTime(t); + tm time = TimeBreakdown(t); + int wday = time.tm_wday; + int target = sWorld->getIntConfig(CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY); + if (target < wday) + wday -= 7; + t += (DAY * (target - wday)); + return t; +} + +void World::ResetWeeklyQuests() { - time_t wstime = uint64(sWorld->getWorldState(WS_MONTHLY_QUEST_RESET_TIME)); - time_t curtime = GameTime::GetGameTime(); - m_NextMonthlyQuestReset = wstime < curtime ? curtime : time_t(wstime); + // reset all saved quest status + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY); + CharacterDatabase.Execute(stmt); + // reset all quest status in memory + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + if (Player* player = itr->second->GetPlayer()) + player->ResetWeeklyQuestStatus(); + + // reselect pools + sQuestPoolMgr->ChangeWeeklyQuests(); + + // store next reset time + time_t now = GameTime::GetGameTime(); + time_t next = GetNextWeeklyResetTime(now); + ASSERT(now < next); + + m_NextWeeklyQuestReset = next; + sWorld->setWorldState(WS_WEEKLY_QUEST_RESET_TIME, uint64(next)); + + TC_LOG_INFO("misc", "Weekly quests for all characters have been reset."); +} + +static time_t GetNextMonthlyResetTime(time_t t) +{ + t = GetNextDailyResetTime(t); + tm time = TimeBreakdown(t); + if (time.tm_mday == 1) + return t; + + time.tm_mday = 1; + time.tm_mon += 1; + return mktime(&time); +} + +void World::ResetMonthlyQuests() +{ + // reset all saved quest status + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY); + CharacterDatabase.Execute(stmt); + // reset all quest status in memory + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + if (Player* player = itr->second->GetPlayer()) + player->ResetMonthlyQuestStatus(); + + // reselect pools + sQuestPoolMgr->ChangeMonthlyQuests(); + + // store next reset time + time_t now = GameTime::GetGameTime(); + time_t next = GetNextMonthlyResetTime(now); + ASSERT(now < next); + + m_NextMonthlyQuestReset = next; + sWorld->setWorldState(WS_MONTHLY_QUEST_RESET_TIME, uint64(next)); + + TC_LOG_INFO("misc", "Monthly quests for all characters have been reset."); +} + +void World::CheckScheduledResetTimes() +{ + time_t const now = GameTime::GetGameTime(); + if (m_NextDailyQuestReset <= now) + DailyReset(); + if (m_NextWeeklyQuestReset <= now) + ResetWeeklyQuests(); + if (m_NextMonthlyQuestReset <= now) + ResetMonthlyQuests(); } void World::InitRandomBGResetTime() @@ -3501,25 +3572,6 @@ void World::InitCurrencyResetTime() sWorld->setWorldState(WS_CURRENCY_RESET_TIME, uint32(m_NextCurrencyReset)); } -void World::DailyReset() -{ - TC_LOG_INFO("misc", "Daily quests reset for all characters."); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_DAILY); - CharacterDatabase.Execute(stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS); - stmt->setUInt32(0, 1); - CharacterDatabase.Execute(stmt); - - for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - if (itr->second->GetPlayer()) - itr->second->GetPlayer()->DailyReset(); - - // change available dailies - sPoolMgr->ChangeDailyQuests(); -} - void World::ResetCurrencyWeekCap() { CharacterDatabase.Execute("UPDATE `character_currency` SET `WeeklyQuantity` = 0"); @@ -3532,68 +3584,6 @@ void World::ResetCurrencyWeekCap() sWorld->setWorldState(WS_CURRENCY_RESET_TIME, uint32(m_NextCurrencyReset)); } -void World::ResetWeeklyQuests() -{ - TC_LOG_INFO("misc", "Weekly quests reset for all characters."); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY); - CharacterDatabase.Execute(stmt); - - for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - if (itr->second->GetPlayer()) - itr->second->GetPlayer()->ResetWeeklyQuestStatus(); - - m_NextWeeklyQuestReset = time_t(m_NextWeeklyQuestReset + WEEK); - sWorld->setWorldState(WS_WEEKLY_QUEST_RESET_TIME, uint32(m_NextWeeklyQuestReset)); - - // change available weeklies - sPoolMgr->ChangeWeeklyQuests(); -} - -void World::ResetMonthlyQuests() -{ - TC_LOG_INFO("misc", "Monthly quests reset for all characters."); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY); - CharacterDatabase.Execute(stmt); - - for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - if (itr->second->GetPlayer()) - itr->second->GetPlayer()->ResetMonthlyQuestStatus(); - - // generate time - time_t curTime = GameTime::GetGameTime(); - tm localTm; - localtime_r(&curTime, &localTm); - - int month = localTm.tm_mon; - int year = localTm.tm_year; - - ++month; - - // month 11 is december, next is january (0) - if (month > 11) - { - month = 0; - year += 1; - } - - // reset time for next month - localTm.tm_year = year; - localTm.tm_mon = month; - localTm.tm_mday = 1; // don't know if we really need config option for day / hour - localTm.tm_hour = 0; - localTm.tm_min = 0; - localTm.tm_sec = 0; - - time_t nextMonthResetTime = mktime(&localTm); - - // plan next reset time - m_NextMonthlyQuestReset = (curTime >= nextMonthResetTime) ? nextMonthResetTime + MONTH : nextMonthResetTime; - - sWorld->setWorldState(WS_MONTHLY_QUEST_RESET_TIME, uint32(m_NextMonthlyQuestReset)); -} - void World::ResetEventSeasonalQuests(uint16 event_id) { TC_LOG_INFO("misc", "Seasonal quests reset for all characters."); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 36939ab6970..0e09a7eaf97 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -266,6 +266,7 @@ enum WorldIntConfigs CONFIG_INSTANCE_RESET_TIME_HOUR, CONFIG_INSTANCE_UNLOAD_DELAY, CONFIG_DAILY_QUEST_RESET_TIME_HOUR, + CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY, CONFIG_MAX_PRIMARY_TRADE_SKILL, CONFIG_MIN_PETITION_SIGNS, CONFIG_MIN_QUEST_SCALED_XP_RATIO, @@ -811,15 +812,15 @@ class TC_GAME_API World // callback for UpdateRealmCharacters void _UpdateRealmCharCount(PreparedQueryResult resultCharCount); - void InitDailyQuestResetTime(bool loading = true); - void InitWeeklyQuestResetTime(); - void InitMonthlyQuestResetTime(); - void InitRandomBGResetTime(); - void InitGuildResetTime(); + void InitQuestResetTimes(); + void CheckScheduledResetTimes(); void InitCurrencyResetTime(); void DailyReset(); void ResetWeeklyQuests(); void ResetMonthlyQuests(); + + void InitRandomBGResetTime(); + void InitGuildResetTime(); void ResetRandomBG(); void ResetGuildCap(); void ResetCurrencyWeekCap(); diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 956400e1814..952f5a316c9 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -44,6 +44,8 @@ EndScriptData */ #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "PhasingHandler.h" +#include "PoolMgr.h" +#include "QuestPools.h" #include "RBAC.h" #include "SpellMgr.h" #include "SpellPackets.h" @@ -1482,21 +1484,20 @@ public: else return false; - time_t const now = GameTime::GetGameTime(); if (daily) { - sWorld->m_NextDailyQuestReset = now; - handler->SendSysMessage("Daily quest reset scheduled for next tick."); + sWorld->DailyReset(); + handler->PSendSysMessage("Daily quests have been reset. Next scheduled reset: %s", TimeToHumanReadable(sWorld->getWorldState(WS_DAILY_QUEST_RESET_TIME)).c_str()); } if (weekly) { - sWorld->m_NextWeeklyQuestReset = now; - handler->SendSysMessage("Weekly quest reset scheduled for next tick."); + sWorld->ResetWeeklyQuests(); + handler->PSendSysMessage("Weekly quests have been reset. Next scheduled reset: %s", TimeToHumanReadable(sWorld->getWorldState(WS_WEEKLY_QUEST_RESET_TIME)).c_str()); } if (monthly) { - sWorld->m_NextMonthlyQuestReset = now; - handler->SendSysMessage("Monthly quest reset scheduled for next tick."); + sWorld->ResetMonthlyQuests(); + handler->PSendSysMessage("Monthly quests have been reset. Next scheduled reset: %s", TimeToHumanReadable(sWorld->getWorldState(WS_MONTHLY_QUEST_RESET_TIME)).c_str()); } return true; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp index 124054ffd47..3cb361ac63a 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp @@ -21,7 +21,7 @@ #include "MotionMaster.h" #include "ObjectAccessor.h" #include "Player.h" -#include "PoolMgr.h" +#include "QuestPools.h" #include "ScriptedCreature.h" #include "SpellInfo.h" #include "SpellScript.h" @@ -511,7 +511,7 @@ class boss_lady_deathwhisper : public CreatureScript uint8 addIndexOther = uint8(addIndex ^ 1); // Summon first add, replace it with Darnavan if weekly quest is active - if (_waveCounter || !sPoolMgr->IsSpawnedObject<Quest>(QUEST_DEPROGRAMMING)) + if (_waveCounter || !sQuestPoolMgr->IsQuestActive(QUEST_DEPROGRAMMING)) Summon(SummonEntries[addIndex], SummonPositions[addIndex * 3]); else Summon(NPC_DARNAVAN, SummonPositions[addIndex * 3]); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp index 35f40a8f0db..1623d85fc5e 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp @@ -23,7 +23,7 @@ #include "Map.h" #include "ObjectMgr.h" #include "Player.h" -#include "PoolMgr.h" +#include "QuestPools.h" #include "TemporarySummon.h" #include "Transport.h" #include "TransportMgr.h" @@ -346,7 +346,7 @@ class instance_icecrown_citadel : public InstanceMapScript if (WeeklyQuestData[questIndex].creatureEntry == entry) { uint8 diffIndex = instance->Is25ManRaid() ? 1 : 0; - if (!sPoolMgr->IsSpawnedObject<Quest>(WeeklyQuestData[questIndex].questId[diffIndex])) + if (!sQuestPoolMgr->IsQuestActive(WeeklyQuestData[questIndex].questId[diffIndex])) return 0; break; } @@ -964,7 +964,7 @@ class instance_icecrown_citadel : public InstanceMapScript case DATA_VALITHRIA_DREAMWALKER: if (state == DONE) { - if (sPoolMgr->IsSpawnedObject<Quest>(WeeklyQuestData[8].questId[instance->Is25ManRaid() ? 1 : 0])) + if (sQuestPoolMgr->IsQuestActive(WeeklyQuestData[8].questId[instance->Is25ManRaid() ? 1 : 0])) instance->SummonCreature(NPC_VALITHRIA_DREAMWALKER_QUEST, ValithriaSpawnPos); if (GameObject* teleporter = instance->GetGameObject(TeleporterSindragosaGUID)) SetTeleporterState(teleporter, true); @@ -1051,7 +1051,7 @@ class instance_icecrown_citadel : public InstanceMapScript break; // 5 is the index of Blood Quickening - if (!sPoolMgr->IsSpawnedObject<Quest>(WeeklyQuestData[5].questId[instance->Is25ManRaid() ? 1 : 0])) + if (!sQuestPoolMgr->IsQuestActive(WeeklyQuestData[5].questId[instance->Is25ManRaid() ? 1 : 0])) break; switch (data) diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index f12a58da1d2..138816786ab 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -1075,6 +1075,15 @@ Quests.IgnoreAutoComplete = 0 Quests.DailyResetTime = 3 # +# Quests.WeeklyResetWDay +# Description: Day of the week when weekly quest reset occurs. +# Range: 0 (Sunday) to 6 (Saturday) +# Default: 3 - (Wednesday) +# + +Quests.WeeklyResetWDay = 3 + +# # Guild.EventLogRecordsCount # Description: Number of log entries for guild events that are stored per guild. Old entries # will be overwritten if the number of log entries exceed the configured value. |