aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/Achievements/AchievementMgr.cpp53
-rw-r--r--src/server/game/Achievements/AchievementMgr.h30
-rw-r--r--src/server/game/Conditions/ConditionMgr.cpp2
-rw-r--r--src/server/game/Maps/MapManager.cpp2
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);
}