diff options
| -rw-r--r-- | src/server/game/Achievements/AchievementMgr.cpp | 53 | ||||
| -rw-r--r-- | src/server/game/Achievements/AchievementMgr.h | 30 | ||||
| -rw-r--r-- | src/server/game/Conditions/ConditionMgr.cpp | 2 | ||||
| -rw-r--r-- | src/server/game/Maps/MapManager.cpp | 2 |
4 files changed, 47 insertions, 40 deletions
diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp index c2f09360562..a0c73a1b068 100644 --- a/src/server/game/Achievements/AchievementMgr.cpp +++ b/src/server/game/Achievements/AchievementMgr.cpp @@ -1130,7 +1130,7 @@ bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry const* achieve if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) { // someone on this realm has already completed that achievement - if (sAchievementMgr->IsRealmCompleted(achievement, GetPlayer()->GetInstanceId())) + if (sAchievementMgr->IsRealmCompleted(achievement)) return false; } @@ -1494,7 +1494,8 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) ca.date = time(NULL); ca.changed = true; - sAchievementMgr->SetRealmCompleted(achievement, GetPlayer()->GetInstanceId()); + if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) + sAchievementMgr->SetRealmCompleted(achievement); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT, achievement->ID); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_ACHIEVEMENT_POINTS, achievement->Points); @@ -2245,6 +2246,32 @@ AchievementCriteriaEntryList const& AchievementGlobalMgr::GetAchievementCriteria return m_AchievementCriteriasByType[type]; } +bool AchievementGlobalMgr::IsRealmCompleted(AchievementEntry const* achievement) const +{ + auto itr = _allCompletedAchievements.find(achievement->ID); + if (itr == _allCompletedAchievements.end()) + return false; + + if (itr->second == std::chrono::system_clock::time_point::min()) + return false; + + // 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 (GameTime::GetGameTimeSystemPoint() - itr->second) > Minutes(1); + + return true; +} + +void AchievementGlobalMgr::SetRealmCompleted(AchievementEntry const* achievement) +{ + if (IsRealmCompleted(achievement)) + return; + + _allCompletedAchievements[achievement->ID] = GameTime::GetGameTimeSystemPoint(); +} + //========================================================== void AchievementGlobalMgr::LoadAchievementCriteriaList() { @@ -2488,6 +2515,14 @@ void AchievementGlobalMgr::LoadCompletedAchievements() { uint32 oldMSTime = getMSTime(); + // 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)) + _allCompletedAchievements[achievement->ID] = std::chrono::system_clock::time_point::min(); + QueryResult result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement"); if (!result) @@ -2514,11 +2549,10 @@ void AchievementGlobalMgr::LoadCompletedAchievements() continue; } else if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) - m_allCompletedAchievements[achievementId] = uint32(0xFFFFFFFF); - } - while (result->NextRow()); + _allCompletedAchievements[achievementId] = std::chrono::system_clock::time_point::max();; + } while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %lu realm first completed achievements in %u ms.", (unsigned long)m_allCompletedAchievements.size(), GetMSTimeDiffToNow(oldMSTime)); + TC_LOG_INFO("server.loading", ">> Loaded %lu realm first completed achievements in %u ms.", (unsigned long)_allCompletedAchievements.size(), GetMSTimeDiffToNow(oldMSTime)); } void AchievementGlobalMgr::LoadRewards() @@ -2691,10 +2725,3 @@ AchievementCriteriaEntry const* AchievementGlobalMgr::GetAchievementCriteria(uin { return sAchievementCriteriaStore.LookupEntry(criteriaId); } - -void AchievementGlobalMgr::OnInstanceDestroyed(uint32 instanceId) -{ - for (auto& realmCompletion : m_allCompletedAchievements) - if (realmCompletion.second == instanceId) - realmCompletion.second = uint32(0xFFFFFFFF); -} diff --git a/src/server/game/Achievements/AchievementMgr.h b/src/server/game/Achievements/AchievementMgr.h index 64d852d9011..99ac4f90f87 100644 --- a/src/server/game/Achievements/AchievementMgr.h +++ b/src/server/game/Achievements/AchievementMgr.h @@ -369,28 +369,8 @@ class TC_GAME_API AchievementGlobalMgr return iter != m_criteriaDataMap.end() ? &iter->second : NULL; } - bool IsRealmCompleted(AchievementEntry const* achievement, uint32 instanceId) const - { - AllCompletedAchievements::const_iterator itr = m_allCompletedAchievements.find(achievement->ID); - if (itr == m_allCompletedAchievements.end()) - return false; - - if (achievement->Flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL) - return itr->second != instanceId; - - return true; - } - - void SetRealmCompleted(AchievementEntry const* achievement, uint32 instanceId) - { - if (IsRealmCompleted(achievement, instanceId)) - return; - - m_allCompletedAchievements[achievement->ID] = instanceId; - } - - // Removes instanceId as valid id to complete realm first kill achievements - void OnInstanceDestroyed(uint32 instanceId); + bool IsRealmCompleted(AchievementEntry const* achievement) const; + void SetRealmCompleted(AchievementEntry const* achievement); void LoadAchievementCriteriaList(); void LoadAchievementCriteriaData(); @@ -419,8 +399,10 @@ class TC_GAME_API AchievementGlobalMgr // store achievements by referenced achievement id to speed up lookup AchievementListByReferencedId m_AchievementListByReferencedId; - typedef std::map<uint32 /*achievementId*/, uint32 /*instanceId*/> AllCompletedAchievements; - AllCompletedAchievements m_allCompletedAchievements; + // store realm first achievements + // std::chrono::system_clock::time_point::min() is a placeholder value for realm firsts not yet completed + // std::chrono::system_clock::time_point::max() is a value assigned to realm firsts complete before worldserver started + std::unordered_map<uint32 /*achievementId*/, std::chrono::system_clock::time_point /*completionTime*/> _allCompletedAchievements; AchievementRewards m_achievementRewards; AchievementRewardLocales m_achievementRewardLocales; diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index e389f45ceb0..568b88aa6c9 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -431,7 +431,7 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const case CONDITION_REALM_ACHIEVEMENT: { AchievementEntry const* achievement = sAchievementMgr->GetAchievement(ConditionValue1); - if (achievement && sAchievementMgr->IsRealmCompleted(achievement, std::numeric_limits<uint32>::max())) + if (achievement && sAchievementMgr->IsRealmCompleted(achievement)) condMeets = true; break; } diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp index b2f01ecd0e2..5aeadbf4fc5 100644 --- a/src/server/game/Maps/MapManager.cpp +++ b/src/server/game/Maps/MapManager.cpp @@ -35,7 +35,6 @@ #include "Player.h" #include "WorldSession.h" #include "Opcodes.h" -#include "AchievementMgr.h" MapManager::MapManager() : _nextInstanceId(0), _scheduledScripts(0) @@ -370,5 +369,4 @@ void MapManager::FreeInstanceId(uint32 instanceId) SetNextInstanceId(instanceId); _instanceIds[instanceId] = false; - sAchievementMgr->OnInstanceDestroyed(instanceId); } |
