From 3ae02de71b9ee295cd82e7f74b7304e63fc83d1d Mon Sep 17 00:00:00 2001 From: Shauren Date: Sat, 24 Dec 2022 23:56:06 +0100 Subject: [PATCH] Core/Quests: Reset seasonal quests based on saved completion time and intended holiday start time --- sql/base/characters_database.sql | 2 + .../4.3.4/2022_12_24_00_characters.sql | 5 +++ .../Implementation/CharacterDatabase.cpp | 6 +-- src/server/game/Entities/Player/Player.cpp | 37 ++++++++++++++----- src/server/game/Entities/Player/Player.h | 8 ++-- src/server/game/Events/GameEventMgr.cpp | 17 ++++++++- src/server/game/Events/GameEventMgr.h | 1 + src/server/game/World/World.cpp | 5 ++- src/server/game/World/World.h | 4 +- 9 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 sql/updates/characters/4.3.4/2022_12_24_00_characters.sql diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index ccac149d0f..5b1e46fc17 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -1239,6 +1239,7 @@ CREATE TABLE `character_queststatus_seasonal` ( `guid` int unsigned NOT NULL DEFAULT '0' COMMENT 'Global Unique Identifier', `quest` int unsigned NOT NULL DEFAULT '0' COMMENT 'Quest Identifier', `event` int unsigned NOT NULL DEFAULT '0' COMMENT 'Event Identifier', + `completedTime` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`guid`,`quest`), KEY `idx_guid` (`guid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Player System'; @@ -3058,6 +3059,7 @@ INSERT INTO `updates` VALUES ('2022_01_09_00_characters.sql','1420602F3A6F5AAE1CB59045C47E33A3CE8809A4','ARCHIVED','2022-01-09 00:12:22',0), ('2022_09_01_00_characters.sql','E3C0DA9995BA71ED5A267294470CD03DC51862DD','RELEASED','2022-09-01 14:51:33',0), ('2022_09_03_00_characters.sql','50429D68E6EBD1149CDA14A9EA642BC06A1FAE3D','RELEASED','2022-09-03 11:03:06',0), +('2022_12_24_00_characters.sql','D3F04078C0846BCF7C8330AC20C39B8C3AEE7002','RELEASED','2022-12-24 23:52:52',0), ('custom_2017_12_21_00_characters.sql','2D5B3830D47E4BC717EC468E18E477A32B9A21BF','ARCHIVED','2019-11-06 00:17:44',0), ('custom_2018_01_07_00_characters.sql','7437B6243B3CB6FA08F6A37BB39E38930B0DCFD3','ARCHIVED','2019-11-06 00:17:44',0), ('custom_2018_03_18_00_characters.sql','8D1E81A7272F643687C67443C270D17255A9AA84','ARCHIVED','2019-11-06 00:17:44',0), diff --git a/sql/updates/characters/4.3.4/2022_12_24_00_characters.sql b/sql/updates/characters/4.3.4/2022_12_24_00_characters.sql new file mode 100644 index 0000000000..11ed0f7e42 --- /dev/null +++ b/sql/updates/characters/4.3.4/2022_12_24_00_characters.sql @@ -0,0 +1,5 @@ +ALTER TABLE `character_queststatus_seasonal` ADD `completedTime` bigint NOT NULL DEFAULT '0' AFTER `event`; + +UPDATE `character_queststatus_seasonal` SET `completedTime` = UNIX_TIMESTAMP(); + +DELETE FROM `worldstates` WHERE `entry` BETWEEN 1 AND 85; diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 8e71ec55ab..3e56ae5ee9 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -79,7 +79,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_DAILY, "SELECT quest, time FROM character_queststatus_daily WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_WEEKLY, "SELECT quest FROM character_queststatus_weekly WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_MONTHLY, "SELECT quest FROM character_queststatus_monthly WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_SEASONAL, "SELECT quest, event FROM character_queststatus_seasonal WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_SEASONAL, "SELECT quest, event, completedTime FROM character_queststatus_seasonal WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_DAILY, "DELETE FROM character_queststatus_daily WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_WEEKLY, "DELETE FROM character_queststatus_weekly WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_MONTHLY, "DELETE FROM character_queststatus_monthly WHERE guid = ?", CONNECTION_ASYNC); @@ -87,11 +87,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_CHARACTER_QUESTSTATUS_DAILY, "INSERT INTO character_queststatus_daily (guid, quest, time) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHARACTER_QUESTSTATUS_WEEKLY, "INSERT INTO character_queststatus_weekly (guid, quest) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHARACTER_QUESTSTATUS_MONTHLY, "INSERT INTO character_queststatus_monthly (guid, quest) VALUES (?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHARACTER_QUESTSTATUS_SEASONAL, "INSERT INTO character_queststatus_seasonal (guid, quest, event) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHARACTER_QUESTSTATUS_SEASONAL, "INSERT INTO character_queststatus_seasonal (guid, quest, event, completedTime) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_DAILY, "DELETE FROM character_queststatus_daily", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY, "DELETE FROM character_queststatus_weekly", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY, "DELETE FROM character_queststatus_monthly", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT, "DELETE FROM character_queststatus_seasonal WHERE event = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT, "DELETE FROM character_queststatus_seasonal WHERE event = ? AND completedTime < ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_REWARDSTATUS_LFG, "SELECT dungeonId, rewardCount, dailyReset FROM character_rewardstatus_lfg WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_REWARDSTATUS_LFG, "DELETE FROM character_rewardstatus_lfg WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index c717aea6a7..33a8ad495b 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -18178,11 +18178,12 @@ void Player::_LoadSeasonalQuestStatus(PreparedQueryResult result) Field* fields = result->Fetch(); uint32 quest_id = fields[0].GetUInt32(); uint32 event_id = fields[1].GetUInt32(); + uint32 completedTime = fields[2].GetInt64(); Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id); if (!quest) continue; - m_seasonalquests[event_id].insert(quest_id); + m_seasonalquests[event_id][quest_id] = completedTime; TC_LOG_DEBUG("entities.player.loading", "Player::_LoadSeasonalQuestStatus: Loaded seasonal quest cooldown (QuestID: %u) for player '%s' (%s)", quest_id, GetName().c_str(), GetGUID().ToString().c_str()); } @@ -19717,18 +19718,20 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction& trans) if (m_seasonalquests.empty()) return; - for (SeasonalEventQuestMap::const_iterator iter = m_seasonalquests.begin(); iter != m_seasonalquests.end(); ++iter) + for (SeasonalQuestMapByEvent::const_iterator iter = m_seasonalquests.begin(); iter != m_seasonalquests.end(); ++iter) { uint16 eventId = iter->first; - for (SeasonalQuestSet::const_iterator itr = iter->second.begin(); itr != iter->second.end(); ++itr) + for (SeasonalQuestMapByQuest::const_iterator itr = iter->second.begin(); itr != iter->second.end(); ++itr) { - uint32 questId = *itr; + uint32 questId = itr->first; + time_t completedTime = itr->second; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_SEASONAL); stmt->setUInt32(0, GetGUID().GetCounter()); stmt->setUInt32(1, questId); stmt->setUInt32(2, eventId); + stmt->setInt64(3, completedTime); trans->Append(stmt); } } @@ -23310,7 +23313,7 @@ void Player::SetSeasonalQuestStatus(uint32 quest_id) if (!quest) return; - m_seasonalquests[quest->GetEventIdForQuest()].insert(quest_id); + m_seasonalquests[quest->GetEventIdForQuest()][quest_id] = GameTime::GetGameTime(); m_SeasonalQuestChanged = true; } @@ -23353,14 +23356,28 @@ void Player::ResetWeeklyQuestStatus() } -void Player::ResetSeasonalQuestStatus(uint16 event_id) +void Player::ResetSeasonalQuestStatus(uint16 event_id, time_t eventStartTime) { - if (m_seasonalquests.empty() || m_seasonalquests[event_id].empty()) - return; - - m_seasonalquests.erase(event_id); // DB data deleted in caller m_SeasonalQuestChanged = false; + + auto eventItr = m_seasonalquests.find(event_id); + if (eventItr == m_seasonalquests.end()) + return; + + if (eventItr->second.empty()) + return; + + for (auto questItr = eventItr->second.begin(); questItr != eventItr->second.end();) + { + if (questItr->second < eventStartTime) + questItr = eventItr->second.erase(questItr); + else + ++questItr; + } + + if (eventItr->second.empty()) + m_seasonalquests.erase(eventItr); } void Player::ResetMonthlyQuestStatus() diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 24afc689bb..0533965284 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1376,7 +1376,7 @@ class TC_GAME_API Player : public Unit, public GridObject void ResetDailyQuestStatus(); void ResetWeeklyQuestStatus(); void ResetMonthlyQuestStatus(); - void ResetSeasonalQuestStatus(uint16 event_id); + void ResetSeasonalQuestStatus(uint16 event_id, time_t eventStartTime); void ResetWeeklyLFGRewardStatus(); void ResetDailyLFGRewardStatus(); @@ -2405,8 +2405,8 @@ class TC_GAME_API Player : public Unit, public GridObject //We allow only one timed quest active at the same time. Below can then be simple value instead of set. typedef std::set QuestSet; - typedef std::set SeasonalQuestSet; - typedef std::unordered_map SeasonalEventQuestMap; + typedef std::unordered_map SeasonalQuestMapByQuest; + typedef std::unordered_map SeasonalQuestMapByEvent; struct LFGRewardInfo { @@ -2422,7 +2422,7 @@ class TC_GAME_API Player : public Unit, public GridObject QuestSet m_timedquests; QuestSet m_weeklyquests; QuestSet m_monthlyquests; - SeasonalEventQuestMap m_seasonalquests; + SeasonalQuestMapByEvent m_seasonalquests; LFGRewardStatusMap m_lfgrewardstatus; ObjectGuid m_playerSharingQuest; diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp index da374442fa..6d5a1f4978 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -1175,7 +1175,7 @@ void GameEventMgr::ApplyNewEvent(uint16 event_id) // update bg holiday UpdateBattlegroundSettings(); // check for seasonal quest reset. - sWorld->ResetEventSeasonalQuests(event_id); + sWorld->ResetEventSeasonalQuests(event_id, GetLastStartTime(event_id)); } void GameEventMgr::UpdateEventNPCFlags(uint16 event_id) @@ -1815,6 +1815,21 @@ void GameEventMgr::SetHolidayEventTime(GameEventData& event) } } +time_t GameEventMgr::GetLastStartTime(uint16 event_id) const +{ + if (event_id >= mGameEvent.size()) + return 0; + + if (mGameEvent[event_id].state != GAMEEVENT_NORMAL) + return 0; + + std::chrono::system_clock::time_point now = GameTime::GetGameTimeSystemPoint(); + std::chrono::system_clock::time_point eventInitialStart = std::chrono::system_clock::from_time_t(mGameEvent[event_id].start); + Minutes occurence(mGameEvent[event_id].occurence); + std::chrono::system_clock::time_point::duration durationSinceLastStart = (now - eventInitialStart) % occurence; + return std::chrono::system_clock::to_time_t(now - durationSinceLastStart); +} + bool IsHolidayActive(HolidayIds id) { if (id == HOLIDAY_NONE) diff --git a/src/server/game/Events/GameEventMgr.h b/src/server/game/Events/GameEventMgr.h index 796e8bf9a9..2cb6fe2a91 100644 --- a/src/server/game/Events/GameEventMgr.h +++ b/src/server/game/Events/GameEventMgr.h @@ -140,6 +140,7 @@ class TC_GAME_API GameEventMgr bool hasCreatureActiveEventExcept(ObjectGuid::LowType creature_guid, uint16 event_id); bool hasGameObjectActiveEventExcept(ObjectGuid::LowType go_guid, uint16 event_id); void SetHolidayEventTime(GameEventData& event); + time_t GetLastStartTime(uint16 event_id) const; typedef std::list GuidList; typedef std::list IdList; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 69de3c3fcb..a90173bc05 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -3513,17 +3513,18 @@ void World::SetPlayerSecurityLimit(AccountTypes _sec) KickAllLess(m_allowedSecurityLevel); } -void World::ResetEventSeasonalQuests(uint16 event_id) +void World::ResetEventSeasonalQuests(uint16 event_id, time_t eventStartTime) { TC_LOG_INFO("misc", "Seasonal quests reset for all characters."); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT); stmt->setUInt16(0, event_id); + stmt->setInt64(1, eventStartTime); CharacterDatabase.Execute(stmt); for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) if (itr->second->GetPlayer()) - itr->second->GetPlayer()->ResetSeasonalQuestStatus(event_id); + itr->second->GetPlayer()->ResetSeasonalQuestStatus(event_id, eventStartTime); } void World::ResetRandomBG() diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 9e02dcd41b..0d43877776 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -787,8 +787,8 @@ class TC_GAME_API World void UpdateAreaDependentAuras(); uint32 GetCleaningFlags() const { return m_CleaningFlags; } - void SetCleaningFlags(uint32 flags) { m_CleaningFlags = flags; } - void ResetEventSeasonalQuests(uint16 event_id); + void SetCleaningFlags(uint32 flags) { m_CleaningFlags = flags; } + void ResetEventSeasonalQuests(uint16 event_id, time_t eventStartTime); void ReloadRBAC();