From 8aa53f2df780cdb92b1ee2b278bee9d9c95c6a37 Mon Sep 17 00:00:00 2001 From: ariel- Date: Thu, 2 Mar 2017 01:07:05 -0300 Subject: [PATCH] Core/Achievements: port realm first achievement handling from master veeeery partial cherry-pick of c75fcbe20b07fd7518be58bf02ba30d903133449 (cherry picked from commit 4596c17dae1dcb199b9301b7dcfd556bb1c0d7d5) --- .../game/Achievements/AchievementMgr.cpp | 55 ++++++++++++++----- src/server/game/Achievements/AchievementMgr.h | 31 ++--------- src/server/game/Conditions/ConditionMgr.cpp | 2 +- src/server/game/Maps/MapManager.cpp | 2 - 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp index 2683bdeab7..22cf99698d 100644 --- a/src/server/game/Achievements/AchievementMgr.cpp +++ b/src/server/game/Achievements/AchievementMgr.cpp @@ -1549,7 +1549,7 @@ bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry const* achi if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL | ACHIEVEMENT_FLAG_REALM_FIRST_GUILD)) { // someone on this realm has already completed that achievement - if (sAchievementMgr->IsRealmCompleted(achievement, GetInstanceId(GetOwner()))) + if (sAchievementMgr->IsRealmCompleted(achievement)) return false; } @@ -1930,7 +1930,8 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achiev ca.date = GameTime::GetGameTime(); ca.changed = true; - sAchievementMgr->SetRealmCompleted(achievement, GetOwner()->GetInstanceId()); + if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) + sAchievementMgr->SetRealmCompleted(achievement); _achievementPoints += achievement->Points; @@ -2023,7 +2024,7 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achieve ca.guids.insert(groupMember->GetGUID()); } - sAchievementMgr->SetRealmCompleted(achievement, referencePlayer->GetInstanceId()); + sAchievementMgr->SetRealmCompleted(achievement); _achievementPoints += achievement->Points; @@ -3322,6 +3323,32 @@ AchievementCriteriaEntryList const& AchievementGlobalMgr::GetAchievementCriteria } } +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() { @@ -3506,6 +3533,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) @@ -3532,11 +3567,10 @@ void AchievementGlobalMgr::LoadCompletedAchievements() continue; } else if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL | ACHIEVEMENT_FLAG_REALM_FIRST_GUILD)) - 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() @@ -3706,10 +3740,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 8708fe9170..d99d7e2cf3 100644 --- a/src/server/game/Achievements/AchievementMgr.h +++ b/src/server/game/Achievements/AchievementMgr.h @@ -360,26 +360,6 @@ class TC_GAME_API AchievementGlobalMgr return iter != m_criteriaDataMap.end() ? &iter->second : nullptr; } - 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; - } - bool IsGroupCriteriaType(AchievementCriteriaTypes type) const { switch (type) @@ -397,9 +377,8 @@ class TC_GAME_API AchievementGlobalMgr return false; } - - // 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(); @@ -430,8 +409,10 @@ class TC_GAME_API AchievementGlobalMgr // store achievements by referenced achievement id to speed up lookup AchievementListByReferencedId m_AchievementListByReferencedId; - typedef std::map 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 _allCompletedAchievements; AchievementRewards m_achievementRewards; AchievementRewardLocales m_achievementRewardLocales; diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index efbfbebd0b..18e21f91e5 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -449,7 +449,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::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 6dd058a69e..0f2f806e3a 100644 --- a/src/server/game/Maps/MapManager.cpp +++ b/src/server/game/Maps/MapManager.cpp @@ -34,7 +34,6 @@ #include "Player.h" #include "WorldSession.h" #include "Opcodes.h" -#include "AchievementMgr.h" MapManager::MapManager() : _nextInstanceId(0), _scheduledScripts(0) @@ -395,5 +394,4 @@ void MapManager::FreeInstanceId(uint32 instanceId) // If freed instance id is lower than the next id available for new instances, use the freed one instead _nextInstanceId = std::min(instanceId, _nextInstanceId); _freeInstanceIds[instanceId] = true; - sAchievementMgr->OnInstanceDestroyed(instanceId); }