diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs index b8934ba92..99871f2d4 100644 --- a/Source/Framework/Database/Databases/CharacterDatabase.cs +++ b/Source/Framework/Database/Databases/CharacterDatabase.cs @@ -101,7 +101,7 @@ namespace Framework.Database PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_DAILY, "SELECT quest, time FROM character_queststatus_daily WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_WEEKLY, "SELECT quest FROM character_queststatus_weekly WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_MONTHLY, "SELECT quest FROM character_queststatus_monthly WHERE guid = ?"); - PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_SEASONAL, "SELECT quest, event FROM character_queststatus_seasonal WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_SEASONAL, "SELECT quest, event, completedTime FROM character_queststatus_seasonal WHERE guid = ?"); PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_DAILY, "DELETE FROM character_queststatus_daily WHERE guid = ?"); PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_WEEKLY, "DELETE FROM character_queststatus_weekly WHERE guid = ?"); PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_MONTHLY, "DELETE FROM character_queststatus_monthly WHERE guid = ?"); @@ -109,11 +109,11 @@ namespace Framework.Database PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_DAILY, "INSERT INTO character_queststatus_daily (guid, quest, time) VALUES (?, ?, ?)"); PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_WEEKLY, "INSERT INTO character_queststatus_weekly (guid, quest) VALUES (?, ?)"); PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_MONTHLY, "INSERT INTO character_queststatus_monthly (guid, quest) VALUES (?, ?)"); - PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_SEASONAL, "INSERT INTO character_queststatus_seasonal (guid, quest, event) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_SEASONAL, "INSERT INTO character_queststatus_seasonal (guid, quest, event, completedTime) VALUES (?, ?, ?, ?)"); PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_DAILY, "DELETE FROM character_queststatus_daily"); PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY, "DELETE FROM character_queststatus_weekly"); PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY, "DELETE FROM character_queststatus_monthly"); - PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT, "DELETE FROM character_queststatus_seasonal WHERE event = ?"); + PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT, "DELETE FROM character_queststatus_seasonal WHERE event = ? AND completedTime < ?"); PrepareStatement(CharStatements.SEL_CHARACTER_REPUTATION, "SELECT faction, standing, flags FROM character_reputation WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_INVENTORY, "SELECT " + SelectItemInstanceContent + ", bag, slot FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE ci.guid = ? ORDER BY (ii.flags & 0x80000) ASC, bag ASC, slot ASC"); diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index 9efcac8e8..10bab40ed 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -931,11 +931,16 @@ namespace Game.Entities { uint quest_id = result.Read(0); uint event_id = result.Read(1); + long completedTime = result.Read(2); Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); if (quest == null) continue; - m_seasonalquests.Add(event_id, quest_id); + if (!m_seasonalquests.ContainsKey(event_id)) + m_seasonalquests[event_id] = new(); + + m_seasonalquests[event_id][quest_id] = completedTime; + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); if (questBit != 0) SetQuestCompletedBit(questBit, true); @@ -2128,13 +2133,17 @@ namespace Game.Entities if (m_seasonalquests.Empty()) return; - foreach (var iter in m_seasonalquests) + foreach (var (eventId, dictionary) in m_seasonalquests) { - stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_SEASONAL); - stmt.AddValue(0, GetGUID().GetCounter()); - stmt.AddValue(1, iter.Value); - stmt.AddValue(2, iter.Key); - trans.Append(stmt); + foreach (var (questId, completedTime) in dictionary) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_SEASONAL); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, questId); + stmt.AddValue(2, eventId); + stmt.AddValue(3, completedTime); + trans.Append(stmt); + } } } void _SaveMonthlyQuestStatus(SQLTransaction trans) diff --git a/Source/Game/Entities/Player/Player.Fields.cs b/Source/Game/Entities/Player/Player.Fields.cs index 43613c208..2ca2eb94d 100644 --- a/Source/Game/Entities/Player/Player.Fields.cs +++ b/Source/Game/Entities/Player/Player.Fields.cs @@ -164,7 +164,7 @@ namespace Game.Entities List m_timedquests = new(); List m_weeklyquests = new(); List m_monthlyquests = new(); - MultiMap m_seasonalquests = new(); + Dictionary> m_seasonalquests = new(); Dictionary m_QuestStatus = new(); MultiMap<(QuestObjectiveType Type, int ObjectID), QuestObjectiveStatusData> m_questObjectiveStatus = new(); Dictionary m_QuestStatusSave = new(); diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 23c6d8b17..946138fab 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -28,6 +28,7 @@ using Game.Networking.Packets; using Game.Spells; using System; using System.Collections.Generic; +using System.Linq; namespace Game.Entities { @@ -200,22 +201,29 @@ namespace Game.Entities } - public void ResetSeasonalQuestStatus(ushort event_id) + public void ResetSeasonalQuestStatus(ushort event_id, long eventStartTime) { + // DB data deleted in caller + m_SeasonalQuestChanged = false; + var eventList = m_seasonalquests.LookupByKey(event_id); if (eventList.Empty()) return; - foreach (uint questId in eventList) + foreach (var (questId, completedTime) in eventList.ToList()) { - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, false); + if (completedTime < eventStartTime) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); + if (questBit != 0) + SetQuestCompletedBit(questBit, false); + + eventList.Remove(questId); + } } - m_seasonalquests.Remove(event_id); - // DB data deleted in caller - m_SeasonalQuestChanged = false; + if (eventList.Empty()) + m_seasonalquests.Remove(event_id); } public void ResetMonthlyQuestStatus() @@ -1673,7 +1681,7 @@ namespace Game.Entities return true; // if not found in cooldown list - return !list.Contains(qInfo.Id); + return !list.ContainsKey(qInfo.Id); } public bool SatisfyQuestExpansion(Quest qInfo, bool msg) @@ -1864,7 +1872,7 @@ namespace Game.Entities ushort eventId = qInfo.GetEventIdForQuest(); if (m_seasonalquests.ContainsKey(eventId)) { - m_seasonalquests.Remove(eventId, questId); + m_seasonalquests[eventId].Remove(questId); m_SeasonalQuestChanged = true; } } @@ -3041,7 +3049,10 @@ namespace Game.Entities if (quest == null) return; - m_seasonalquests.Add(quest.GetEventIdForQuest(), quest_id); + if (!m_seasonalquests.ContainsKey(quest.GetEventIdForQuest())) + m_seasonalquests[quest.GetEventIdForQuest()] = new(); + + m_seasonalquests[quest.GetEventIdForQuest()][quest_id] = GameTime.GetGameTime(); m_SeasonalQuestChanged = true; } diff --git a/Source/Game/Events/GameEventManager.cs b/Source/Game/Events/GameEventManager.cs index f0f81d6e2..01cdb4e46 100644 --- a/Source/Game/Events/GameEventManager.cs +++ b/Source/Game/Events/GameEventManager.cs @@ -1100,7 +1100,7 @@ namespace Game RunSmartAIScripts(event_id, true); // check for seasonal quest reset. - Global.WorldMgr.ResetEventSeasonalQuests(event_id); + Global.WorldMgr.ResetEventSeasonalQuests(event_id, GetLastStartTime(event_id)); } void UpdateEventNPCFlags(ushort event_id) @@ -1676,6 +1676,21 @@ namespace Game } } + long GetLastStartTime(ushort event_id) + { + if (event_id >= mGameEvent.Length) + return 0; + + if (mGameEvent[event_id].state != GameEventState.Normal) + return 0; + + DateTime now = GameTime.GetSystemTime(); + DateTime eventInitialStart = Time.UnixTimeToDateTime(mGameEvent[event_id].start); + TimeSpan occurence = TimeSpan.FromMinutes(mGameEvent[event_id].occurence); + TimeSpan durationSinceLastStart = TimeSpan.FromTicks((now - eventInitialStart).Ticks % occurence.Ticks); + return Time.DateTimeToUnixTime(now - durationSinceLastStart); + } + public bool IsHolidayActive(HolidayIds id) { if (id == HolidayIds.None) diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index f4f588777..0e2fe47c1 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -2215,15 +2215,15 @@ namespace Game SetWorldState(WorldStates.CurrencyResetTime, (ulong)m_NextCurrencyReset); } - public void ResetEventSeasonalQuests(ushort event_id) + public void ResetEventSeasonalQuests(ushort event_id, long eventStartTime) { PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT); stmt.AddValue(0, event_id); + stmt.AddValue(1, eventStartTime); DB.Characters.Execute(stmt); foreach (var session in m_sessions.Values) - if (session.GetPlayer()) - session.GetPlayer().ResetSeasonalQuestStatus(event_id); + session.GetPlayer()?.ResetSeasonalQuestStatus(event_id, eventStartTime); } void ResetRandomBG() diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 8333e27a4..109c9f9e2 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -1426,6 +1426,7 @@ CREATE TABLE `character_queststatus_seasonal` ( `guid` bigint 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'; @@ -3670,7 +3671,8 @@ INSERT INTO `updates` VALUES ('2022_02_28_00_characters_2020_09_27_00_characters.sql','2292A1ED0E7F46DEC41384F75FA6D9461464EEB8','ARCHIVED','2022-02-28 12:43:58',0), ('2022_03_06_00_characters.sql','474AAF9D03E6A56017899C968DC9875368301934','ARCHIVED','2022-03-06 15:12:24',0), ('2022_03_11_00_characters_2021_07_18_00_characters.sql','0BA579ED21F4E75AC2B4797421B5029568B3F6E2','RELEASED','2022-03-11 18:56:07',0), -('2022_06_01_00_characters.sql','582AC6E256F8365F83AB70BA165CCC8B218E19FF','RELEASED','2022-06-01 21:16:56',0); +('2022_06_01_00_characters.sql','582AC6E256F8365F83AB70BA165CCC8B218E19FF','RELEASED','2022-06-01 21:16:56',0), +('2022_07_03_00_characters.sql','D3F04078C0846BCF7C8330AC20C39B8C3AEE7002','RELEASED','2022-07-03 23:37:24',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/master/2022_07_03_00_characters.sql b/sql/updates/characters/master/2022_07_03_00_characters.sql new file mode 100644 index 000000000..11ed0f7e4 --- /dev/null +++ b/sql/updates/characters/master/2022_07_03_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;