diff --git a/src/game/Achievements/AchievementMgr.cpp b/src/game/Achievements/AchievementMgr.cpp index 881bc09ce..c30a53ac3 100644 --- a/src/game/Achievements/AchievementMgr.cpp +++ b/src/game/Achievements/AchievementMgr.cpp @@ -2163,9 +2163,7 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) } } - // don't insert for ACHIEVEMENT_FLAG_REALM_FIRST_KILL since otherwise only the first group member would reach that achievement - // TODO: where do set this instead? - if (!(achievement->flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) + if (achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) sAchievementMgr->SetRealmCompleted(achievement); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT, achievement->ID); @@ -2307,6 +2305,35 @@ bool AchievementMgr::CanUpdateCriteria(AchievementCriteriaEntry const* criteria, return true; } +bool AchievementGlobalMgr::IsRealmCompleted(AchievementEntry const* achievement) const +{ + auto itr = m_allCompletedAchievements.find(achievement->ID); + if (itr == m_allCompletedAchievements.end()) + return false; + + if (itr->second == std::chrono::system_clock::time_point::min()) + return false; + + if (itr->second == std::chrono::system_clock::time_point::max()) + return true; + + // Allow completing the realm first kill for entire minute after first person did it + // it may allow more than one group to achieve it (highly unlikely) + // but apparently this is how blizz handles it as well + if (achievement->flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL) + return (std::chrono::system_clock::now() - itr->second) > std::chrono::minutes(1); + + return true; +} + +void AchievementGlobalMgr::SetRealmCompleted(AchievementEntry const* achievement) +{ + if (IsRealmCompleted(achievement)) + return; + + m_allCompletedAchievements[achievement->ID] = std::chrono::system_clock::now(); +} + //========================================================== void AchievementGlobalMgr::LoadAchievementCriteriaList() { @@ -2628,6 +2655,14 @@ void AchievementGlobalMgr::LoadCompletedAchievements() QueryResult result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement"); + // Populate _allCompletedAchievements with all realm first achievement ids to make multithreaded access safer + // while it will not prevent races, it will prevent crashes that happen because std::unordered_map key was added + // instead the only potential race will happen on value associated with the key + for (uint32 i = 0; i < sAchievementStore.GetNumRows(); ++i) + if (AchievementEntry const* achievement = sAchievementStore.LookupEntry(i)) + if (achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) + m_allCompletedAchievements[achievement->ID] = std::chrono::system_clock::time_point::min(); + if (!result) { sLog->outString(">> Loaded 0 completed achievements. DB table `character_achievement` is empty."); @@ -2655,7 +2690,7 @@ void AchievementGlobalMgr::LoadCompletedAchievements() continue; } else if (achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) - m_allCompletedAchievements.insert(achievementId); + m_allCompletedAchievements[achievementId] = std::chrono::system_clock::time_point::max(); } while (result->NextRow()); sLog->outString(">> Loaded %lu completed achievements in %u ms", (unsigned long)m_allCompletedAchievements.size(), GetMSTimeDiffToNow(oldMSTime)); diff --git a/src/game/Achievements/AchievementMgr.h b/src/game/Achievements/AchievementMgr.h index 390a0cb58..ff5563943 100644 --- a/src/game/Achievements/AchievementMgr.h +++ b/src/game/Achievements/AchievementMgr.h @@ -8,6 +8,7 @@ #include #include +#include #include "Common.h" #include @@ -348,15 +349,8 @@ class AchievementGlobalMgr return iter != m_criteriaDataMap.end() ? &iter->second : NULL; } - bool IsRealmCompleted(AchievementEntry const* achievement) const - { - return m_allCompletedAchievements.find(achievement->ID) != m_allCompletedAchievements.end(); - } - - void SetRealmCompleted(AchievementEntry const* achievement) - { - m_allCompletedAchievements.insert(achievement->ID); - } + bool IsRealmCompleted(AchievementEntry const* achievement) const; + void SetRealmCompleted(AchievementEntry const* achievement); void LoadAchievementCriteriaList(); void LoadAchievementCriteriaData(); @@ -375,7 +369,7 @@ class AchievementGlobalMgr // store achievements by referenced achievement id to speed up lookup AchievementListByReferencedId m_AchievementListByReferencedId; - typedef std::set AllCompletedAchievements; + typedef UNORDERED_MAP AllCompletedAchievements; AllCompletedAchievements m_allCompletedAchievements; AchievementRewards m_achievementRewards;