/*
* Copyright (C) 2008-2019 TrinityCore
* Copyright (C) 2005-2009 MaNGOS
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "AchievementMgr.h"
#include "AchievementPackets.h"
#include "DB2HotfixGenerator.h"
#include "DB2Stores.h"
#include "CellImpl.h"
#include "ChatTextBuilder.h"
#include "DatabaseEnv.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "Item.h"
#include "Language.h"
#include "Log.h"
#include "Mail.h"
#include "ObjectMgr.h"
#include "World.h"
#include "WorldSession.h"
struct VisibleAchievementCheck
{
AchievementEntry const* operator()(std::pair const& val)
{
AchievementEntry const* achievement = sAchievementStore.LookupEntry(val.first);
if (achievement && !(achievement->Flags & ACHIEVEMENT_FLAG_HIDDEN))
return achievement;
return nullptr;
}
};
AchievementMgr::AchievementMgr() : _achievementPoints(0) { }
AchievementMgr::~AchievementMgr() { }
/**
* called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet
*/
void AchievementMgr::CheckAllAchievementCriteria(Player* referencePlayer)
{
// suppress sending packets
for (uint32 i = 0; i < CRITERIA_TYPE_TOTAL; ++i)
UpdateCriteria(CriteriaTypes(i), 0, 0, 0, NULL, referencePlayer);
}
bool AchievementMgr::HasAchieved(uint32 achievementId) const
{
return _completedAchievements.find(achievementId) != _completedAchievements.end();
}
uint32 AchievementMgr::GetAchievementPoints() const
{
return _achievementPoints;
}
bool AchievementMgr::CanUpdateCriteriaTree(Criteria const* criteria, CriteriaTree const* tree, Player* referencePlayer) const
{
AchievementEntry const* achievement = tree->Achievement;
if (!achievement)
return false;
if (HasAchieved(achievement->ID))
{
TC_LOG_TRACE("criteria.achievement", "AchievementMgr::CanUpdateCriteriaTree: (Id: %u Type %s Achievement %u) Achievement already earned",
criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), achievement->ID);
return false;
}
if (achievement->InstanceID != -1 && referencePlayer->GetMapId() != uint32(achievement->InstanceID))
{
TC_LOG_TRACE("criteria.achievement", "AchievementMgr::CanUpdateCriteriaTree: (Id: %u Type %s Achievement %u) Wrong map",
criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), achievement->ID);
return false;
}
if ((achievement->Faction == ACHIEVEMENT_FACTION_HORDE && referencePlayer->GetTeam() != HORDE) ||
(achievement->Faction == ACHIEVEMENT_FACTION_ALLIANCE && referencePlayer->GetTeam() != ALLIANCE))
{
TC_LOG_TRACE("criteria.achievement", "AchievementMgr::CanUpdateCriteriaTree: (Id: %u Type %s Achievement %u) Wrong faction",
criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), achievement->ID);
return false;
}
return CriteriaHandler::CanUpdateCriteriaTree(criteria, tree, referencePlayer);
}
bool AchievementMgr::CanCompleteCriteriaTree(CriteriaTree const* tree)
{
AchievementEntry const* achievement = tree->Achievement;
if (!achievement)
return false;
// counter can never complete
if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER)
return false;
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))
return false;
}
return true;
}
void AchievementMgr::CompletedCriteriaTree(CriteriaTree const* tree, Player* referencePlayer)
{
AchievementEntry const* achievement = tree->Achievement;
if (!achievement)
return;
// counter can never complete
if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER)
return;
// already completed and stored
if (HasAchieved(achievement->ID))
return;
if (IsCompletedAchievement(achievement))
CompletedAchievement(achievement, referencePlayer);
}
void AchievementMgr::AfterCriteriaTreeUpdate(CriteriaTree const* tree, Player* referencePlayer)
{
AchievementEntry const* achievement = tree->Achievement;
if (!achievement)
return;
// check again the completeness for SUMM and REQ COUNT achievements,
// as they don't depend on the completed criteria but on the sum of the progress of each individual criteria
if (achievement->Flags & ACHIEVEMENT_FLAG_SUMM)
if (IsCompletedAchievement(achievement))
CompletedAchievement(achievement, referencePlayer);
if (std::vector const* achRefList = sAchievementMgr->GetAchievementByReferencedId(achievement->ID))
for (AchievementEntry const* refAchievement : *achRefList)
if (IsCompletedAchievement(refAchievement))
CompletedAchievement(refAchievement, referencePlayer);
}
bool AchievementMgr::IsCompletedAchievement(AchievementEntry const* entry)
{
// counter can never complete
if (entry->Flags & ACHIEVEMENT_FLAG_COUNTER)
return false;
CriteriaTree const* tree = sCriteriaMgr->GetCriteriaTree(entry->CriteriaTree);
if (!tree)
return false;
// For SUMM achievements, we have to count the progress of each criteria of the achievement.
// Oddly, the target count is NOT contained in the achievement, but in each individual criteria
if (entry->Flags & ACHIEVEMENT_FLAG_SUMM)
{
int64 progress = 0;
CriteriaMgr::WalkCriteriaTree(tree, [this, &progress](CriteriaTree const* criteriaTree)
{
if (criteriaTree->Criteria)
if (CriteriaProgress const* criteriaProgress = this->GetCriteriaProgress(criteriaTree->Criteria))
progress += criteriaProgress->Counter;
});
return progress >= tree->Entry->Amount;
}
return IsCompletedCriteriaTree(tree);
}
bool AchievementMgr::RequiredAchievementSatisfied(uint32 achievementId) const
{
return HasAchieved(achievementId);
}
PlayerAchievementMgr::PlayerAchievementMgr(Player* owner) : _owner(owner)
{
}
void PlayerAchievementMgr::Reset()
{
AchievementMgr::Reset();
for (auto iter = _completedAchievements.begin(); iter != _completedAchievements.end(); ++iter)
{
WorldPackets::Achievement::AchievementDeleted achievementDeleted;
achievementDeleted.AchievementID = iter->first;
SendPacket(achievementDeleted.Write());
}
_completedAchievements.clear();
_achievementPoints = 0;
DeleteFromDB(_owner->GetGUID());
// re-fill data
CheckAllAchievementCriteria(_owner);
}
void PlayerAchievementMgr::DeleteFromDB(ObjectGuid const& guid)
{
SQLTransaction trans = CharacterDatabase.BeginTransaction();
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT);
stmt->setUInt64(0, guid.GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS);
stmt->setUInt64(0, guid.GetCounter());
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
}
void PlayerAchievementMgr::LoadFromDB(PreparedQueryResult achievementResult, PreparedQueryResult criteriaResult)
{
if (achievementResult)
{
do
{
Field* fields = achievementResult->Fetch();
uint32 achievementid = fields[0].GetUInt32();
// must not happen: cleanup at server startup in sAchievementMgr->LoadCompletedAchievements()
AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementid);
if (!achievement)
continue;
CompletedAchievementData& ca = _completedAchievements[achievementid];
ca.Date = time_t(fields[1].GetUInt32());
ca.Changed = false;
_achievementPoints += achievement->Points;
// title achievement rewards are retroactive
if (AchievementReward const* reward = sAchievementMgr->GetAchievementReward(achievement))
if (uint32 titleId = reward->TitleId[Player::TeamForRace(_owner->getRace()) == ALLIANCE ? 0 : 1])
if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId))
_owner->SetTitle(titleEntry);
} while (achievementResult->NextRow());
}
if (criteriaResult)
{
time_t now = time(NULL);
do
{
Field* fields = criteriaResult->Fetch();
uint32 id = fields[0].GetUInt32();
uint64 counter = fields[1].GetUInt64();
time_t date = time_t(fields[2].GetUInt32());
Criteria const* criteria = sCriteriaMgr->GetCriteria(id);
if (!criteria)
{
// Removing non-existing criteria data for all characters
TC_LOG_ERROR("criteria.achievement", "Non-existing achievement criteria %u data has been removed from the table `character_achievement_progress`.", id);
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA);
stmt->setUInt32(0, id);
CharacterDatabase.Execute(stmt);
continue;
}
if (criteria->Entry->StartTimer && time_t(date + criteria->Entry->StartTimer) < now)
continue;
CriteriaProgress& progress = _criteriaProgress[id];
progress.Counter = counter;
progress.Date = date;
progress.Changed = false;
} while (criteriaResult->NextRow());
}
}
void PlayerAchievementMgr::SaveToDB(SQLTransaction& trans)
{
if (!_completedAchievements.empty())
{
for (auto iter = _completedAchievements.begin(); iter != _completedAchievements.end(); ++iter)
{
if (!iter->second.Changed)
continue;
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT);
stmt->setUInt32(0, iter->first);
stmt->setUInt64(1, _owner->GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACHIEVEMENT);
stmt->setUInt64(0, _owner->GetGUID().GetCounter());
stmt->setUInt32(1, iter->first);
stmt->setUInt32(2, uint32(iter->second.Date));
trans->Append(stmt);
iter->second.Changed = false;
}
}
if (!_criteriaProgress.empty())
{
for (auto iter = _criteriaProgress.begin(); iter != _criteriaProgress.end(); ++iter)
{
if (!iter->second.Changed)
continue;
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA);
stmt->setUInt64(0, _owner->GetGUID().GetCounter());
stmt->setUInt32(1, iter->first);
trans->Append(stmt);
if (iter->second.Counter)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACHIEVEMENT_PROGRESS);
stmt->setUInt64(0, _owner->GetGUID().GetCounter());
stmt->setUInt32(1, iter->first);
stmt->setUInt64(2, iter->second.Counter);
stmt->setUInt32(3, uint32(iter->second.Date));
trans->Append(stmt);
}
iter->second.Changed = false;
}
}
}
void PlayerAchievementMgr::ResetCriteria(CriteriaTypes type, uint64 miscValue1, uint64 miscValue2, bool evenIfCriteriaComplete)
{
TC_LOG_DEBUG("criteria.achievement", "PlayerAchievementMgr::ResetCriteria(%u, " UI64FMTD ", " UI64FMTD ")", type, miscValue1, miscValue2);
// disable for gamemasters with GM-mode enabled
if (_owner->IsGameMaster())
return;
CriteriaList const& achievementCriteriaList = GetCriteriaByType(type);
for (Criteria const* achievementCriteria : achievementCriteriaList)
{
if (achievementCriteria->Entry->FailEvent != miscValue1 || (achievementCriteria->Entry->FailAsset && achievementCriteria->Entry->FailAsset != int64(miscValue2)))
continue;
std::vector const* trees = sCriteriaMgr->GetCriteriaTreesByCriteria(achievementCriteria->ID);
bool allComplete = true;
for (CriteriaTree const* tree : *trees)
{
// don't update already completed criteria if not forced or achievement already complete
if (!(IsCompletedCriteriaTree(tree) && !evenIfCriteriaComplete) || !HasAchieved(tree->Achievement->ID))
{
allComplete = false;
break;
}
}
if (allComplete)
continue;
RemoveCriteriaProgress(achievementCriteria);
}
}
void PlayerAchievementMgr::SendAllData(Player const* /*receiver*/) const
{
VisibleAchievementCheck filterInvisible;
WorldPackets::Achievement::AllAchievementData achievementData;
achievementData.Data.Earned.reserve(_completedAchievements.size());
achievementData.Data.Progress.reserve(_criteriaProgress.size());
for (auto itr = _completedAchievements.begin(); itr != _completedAchievements.end(); ++itr)
{
AchievementEntry const* achievement = filterInvisible(*itr);
if (!achievement)
continue;
WorldPackets::Achievement::EarnedAchievement earned;
earned.Id = itr->first;
earned.Date = itr->second.Date;
if (!(achievement->Flags & ACHIEVEMENT_FLAG_ACCOUNT))
{
earned.Owner = _owner->GetGUID();
earned.VirtualRealmAddress = earned.NativeRealmAddress = GetVirtualRealmAddress();
}
achievementData.Data.Earned.push_back(earned);
}
for (auto itr = _criteriaProgress.begin(); itr != _criteriaProgress.end(); ++itr)
{
WorldPackets::Achievement::CriteriaProgress progress;
progress.Id = itr->first;
progress.Quantity = itr->second.Counter;
progress.Player = itr->second.PlayerGUID;
progress.Flags = 0;
progress.Date = itr->second.Date;
progress.TimeFromStart = 0;
progress.TimeFromCreate = 0;
achievementData.Data.Progress.push_back(progress);
}
SendPacket(achievementData.Write());
}
void PlayerAchievementMgr::SendAchievementInfo(Player* receiver, uint32 /*achievementId = 0 */) const
{
VisibleAchievementCheck filterInvisible;
WorldPackets::Achievement::RespondInspectAchievements inspectedAchievements;
inspectedAchievements.Player = _owner->GetGUID();
inspectedAchievements.Data.Earned.reserve(_completedAchievements.size());
inspectedAchievements.Data.Progress.reserve(_criteriaProgress.size());
for (auto itr = _completedAchievements.begin(); itr != _completedAchievements.end(); ++itr)
{
AchievementEntry const* achievement = filterInvisible(*itr);
if (!achievement)
continue;
WorldPackets::Achievement::EarnedAchievement earned;
earned.Id = itr->first;
earned.Date = itr->second.Date;
if (!(achievement->Flags & ACHIEVEMENT_FLAG_ACCOUNT))
{
earned.Owner = _owner->GetGUID();
earned.VirtualRealmAddress = earned.NativeRealmAddress = GetVirtualRealmAddress();
}
inspectedAchievements.Data.Earned.push_back(earned);
}
for (auto itr = _criteriaProgress.begin(); itr != _criteriaProgress.end(); ++itr)
{
WorldPackets::Achievement::CriteriaProgress progress;
progress.Id = itr->first;
progress.Quantity = itr->second.Counter;
progress.Player = itr->second.PlayerGUID;
progress.Flags = 0;
progress.Date = itr->second.Date;
progress.TimeFromStart = 0;
progress.TimeFromCreate = 0;
inspectedAchievements.Data.Progress.push_back(progress);
}
receiver->SendDirectMessage(inspectedAchievements.Write());
}
void PlayerAchievementMgr::CompletedAchievement(AchievementEntry const* achievement, Player* referencePlayer)
{
// disable for gamemasters with GM-mode enabled
if (_owner->IsGameMaster())
return;
if ((achievement->Faction == ACHIEVEMENT_FACTION_HORDE && referencePlayer->GetTeam() != HORDE) ||
(achievement->Faction == ACHIEVEMENT_FACTION_ALLIANCE && referencePlayer->GetTeam() != ALLIANCE))
return;
if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER || HasAchieved(achievement->ID))
return;
if (achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_NEWS)
if (Guild* guild = referencePlayer->GetGuild())
guild->AddGuildNews(GUILD_NEWS_PLAYER_ACHIEVEMENT, referencePlayer->GetGUID(), achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_HEADER, achievement->ID);
if (!_owner->GetSession()->PlayerLoading())
SendAchievementEarned(achievement);
TC_LOG_INFO("criteria.achievement", "PlayerAchievementMgr::CompletedAchievement(%u). %s", achievement->ID, GetOwnerInfo().c_str());
CompletedAchievementData& ca = _completedAchievements[achievement->ID];
ca.Date = time(NULL);
ca.Changed = true;
if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
sAchievementMgr->SetRealmCompleted(achievement);
if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
_achievementPoints += achievement->Points;
UpdateCriteria(CRITERIA_TYPE_COMPLETE_ACHIEVEMENT, 0, 0, 0, NULL, referencePlayer);
UpdateCriteria(CRITERIA_TYPE_EARN_ACHIEVEMENT_POINTS, achievement->Points, 0, 0, NULL, referencePlayer);
// reward items and titles if any
AchievementReward const* reward = sAchievementMgr->GetAchievementReward(achievement);
// no rewards
if (!reward)
return;
// titles
//! Currently there's only one achievement that deals with gender-specific titles.
//! Since no common attributes were found, (not even in titleRewardFlags field)
//! we explicitly check by ID. Maybe in the future we could move the achievement_reward
//! condition fields to the condition system.
if (uint32 titleId = reward->TitleId[achievement->ID == 1793 ? _owner->m_playerData->NativeSex : (_owner->GetTeam() == ALLIANCE ? 0 : 1)])
if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId))
_owner->SetTitle(titleEntry);
// mail
if (reward->SenderCreatureId)
{
MailDraft draft(uint16(reward->MailTemplateId));
if (!reward->MailTemplateId)
{
// subject and text
std::string subject = reward->Subject;
std::string text = reward->Body;
LocaleConstant localeConstant = _owner->GetSession()->GetSessionDbLocaleIndex();
if (localeConstant != LOCALE_enUS)
{
if (AchievementRewardLocale const* loc = sAchievementMgr->GetAchievementRewardLocale(achievement))
{
ObjectMgr::GetLocaleString(loc->Subject, localeConstant, subject);
ObjectMgr::GetLocaleString(loc->Body, localeConstant, text);
}
}
draft = MailDraft(subject, text);
}
SQLTransaction trans = CharacterDatabase.BeginTransaction();
Item* item = reward->ItemId ? Item::CreateItem(reward->ItemId, 1, _owner) : NULL;
if (item)
{
// save new item before send
item->SaveToDB(trans); // save for prevent lost at next mail load, if send fail then item will deleted
// item
draft.AddItem(item);
}
draft.SendMailTo(trans, _owner, MailSender(MAIL_CREATURE, uint64(reward->SenderCreatureId)));
CharacterDatabase.CommitTransaction(trans);
}
}
bool PlayerAchievementMgr::ModifierTreeSatisfied(uint32 modifierTreeId) const
{
return AdditionalRequirementsSatisfied(sCriteriaMgr->GetModifierTree(modifierTreeId), 0, 0, nullptr, _owner);
}
void PlayerAchievementMgr::SendCriteriaUpdate(Criteria const* criteria, CriteriaProgress const* progress, uint32 timeElapsed, bool timedCompleted) const
{
WorldPackets::Achievement::CriteriaUpdate criteriaUpdate;
criteriaUpdate.CriteriaID = criteria->ID;
criteriaUpdate.Quantity = progress->Counter;
criteriaUpdate.PlayerGUID = _owner->GetGUID();
criteriaUpdate.Flags = 0;
if (criteria->Entry->StartTimer)
criteriaUpdate.Flags = timedCompleted ? 1 : 0; // 1 is for keeping the counter at 0 in client
criteriaUpdate.CurrentTime = progress->Date;
criteriaUpdate.ElapsedTime = timeElapsed;
criteriaUpdate.CreationTime = 0;
SendPacket(criteriaUpdate.Write());
}
void PlayerAchievementMgr::SendCriteriaProgressRemoved(uint32 criteriaId)
{
WorldPackets::Achievement::CriteriaDeleted criteriaDeleted;
criteriaDeleted.CriteriaID = criteriaId;
SendPacket(criteriaDeleted.Write());
}
void PlayerAchievementMgr::SendAchievementEarned(AchievementEntry const* achievement) const
{
// Don't send for achievements with ACHIEVEMENT_FLAG_HIDDEN
if (achievement->Flags & ACHIEVEMENT_FLAG_HIDDEN)
return;
TC_LOG_DEBUG("criteria.achievement", "PlayerAchievementMgr::SendAchievementEarned(%u)", achievement->ID);
if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
{
if (Guild* guild = sGuildMgr->GetGuildById(_owner->GetGuildId()))
{
Trinity::BroadcastTextBuilder _builder(_owner, CHAT_MSG_GUILD_ACHIEVEMENT, BROADCAST_TEXT_ACHIEVEMENT_EARNED, _owner->getGender(), _owner, achievement->ID);
Trinity::LocalizedPacketDo _localizer(_builder);
guild->BroadcastWorker(_localizer, _owner);
}
if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_KILL | ACHIEVEMENT_FLAG_REALM_FIRST_REACH))
{
// broadcast realm first reached
WorldPackets::Achievement::BroadcastAchievement serverFirstAchievement;
serverFirstAchievement.Name = _owner->GetName();
serverFirstAchievement.PlayerGUID = _owner->GetGUID();
serverFirstAchievement.AchievementID = achievement->ID;
sWorld->SendGlobalMessage(serverFirstAchievement.Write());
}
// if player is in world he can tell his friends about new achievement
else if (_owner->IsInWorld())
{
Trinity::BroadcastTextBuilder _builder(_owner, CHAT_MSG_ACHIEVEMENT, BROADCAST_TEXT_ACHIEVEMENT_EARNED, _owner->getGender(), _owner, achievement->ID);
Trinity::LocalizedPacketDo _localizer(_builder);
Trinity::PlayerDistWorker> _worker(_owner, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), _localizer);
Cell::VisitWorldObjects(_owner, _worker, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY));
}
}
WorldPackets::Achievement::AchievementEarned achievementEarned;
achievementEarned.Sender = _owner->GetGUID();
achievementEarned.Earner = _owner->GetGUID();
achievementEarned.EarnerNativeRealm = achievementEarned.EarnerVirtualRealm = GetVirtualRealmAddress();
achievementEarned.AchievementID = achievement->ID;
achievementEarned.Time = time(NULL);
if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
_owner->SendMessageToSetInRange(achievementEarned.Write(), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), true);
else
_owner->SendDirectMessage(achievementEarned.Write());
}
void PlayerAchievementMgr::SendPacket(WorldPacket const* data) const
{
_owner->SendDirectMessage(data);
}
CriteriaList const& PlayerAchievementMgr::GetCriteriaByType(CriteriaTypes type) const
{
return sCriteriaMgr->GetPlayerCriteriaByType(type);
}
GuildAchievementMgr::GuildAchievementMgr(Guild* owner) : _owner(owner)
{
}
void GuildAchievementMgr::Reset()
{
AchievementMgr::Reset();
ObjectGuid guid = _owner->GetGUID();
for (auto iter = _completedAchievements.begin(); iter != _completedAchievements.end(); ++iter)
{
WorldPackets::Achievement::GuildAchievementDeleted guildAchievementDeleted;
guildAchievementDeleted.AchievementID = iter->first;
guildAchievementDeleted.GuildGUID = guid;
guildAchievementDeleted.TimeDeleted = time(NULL);
SendPacket(guildAchievementDeleted.Write());
}
_achievementPoints = 0;
_completedAchievements.clear();
DeleteFromDB(guid);
}
void GuildAchievementMgr::DeleteFromDB(ObjectGuid const& guid)
{
SQLTransaction trans = CharacterDatabase.BeginTransaction();
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_GUILD_ACHIEVEMENTS);
stmt->setUInt64(0, guid.GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_GUILD_ACHIEVEMENT_CRITERIA);
stmt->setUInt64(0, guid.GetCounter());
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
}
void GuildAchievementMgr::LoadFromDB(PreparedQueryResult achievementResult, PreparedQueryResult criteriaResult)
{
if (achievementResult)
{
do
{
Field* fields = achievementResult->Fetch();
uint32 achievementid = fields[0].GetUInt32();
// must not happen: cleanup at server startup in sAchievementMgr->LoadCompletedAchievements()
AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementid);
if (!achievement)
continue;
CompletedAchievementData& ca = _completedAchievements[achievementid];
ca.Date = time_t(fields[1].GetUInt32());
Tokenizer guids(fields[2].GetString(), ' ');
for (uint32 i = 0; i < guids.size(); ++i)
ca.CompletingPlayers.insert(ObjectGuid::Create(uint64(strtoull(guids[i], nullptr, 10))));
ca.Changed = false;
_achievementPoints += achievement->Points;
} while (achievementResult->NextRow());
}
if (criteriaResult)
{
time_t now = time(NULL);
do
{
Field* fields = criteriaResult->Fetch();
uint32 id = fields[0].GetUInt32();
uint64 counter = fields[1].GetUInt64();
time_t date = time_t(fields[2].GetUInt32());
ObjectGuid::LowType guid = fields[3].GetUInt64();
Criteria const* criteria = sCriteriaMgr->GetCriteria(id);
if (!criteria)
{
// we will remove not existed criteria for all guilds
TC_LOG_ERROR("criteria.achievement", "Non-existing achievement criteria %u data removed from table `guild_achievement_progress`.", id);
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA_GUILD);
stmt->setUInt32(0, id);
CharacterDatabase.Execute(stmt);
continue;
}
if (criteria->Entry->StartTimer && time_t(date + criteria->Entry->StartTimer) < now)
continue;
CriteriaProgress& progress = _criteriaProgress[id];
progress.Counter = counter;
progress.Date = date;
progress.PlayerGUID = ObjectGuid::Create(guid);
progress.Changed = false;
} while (criteriaResult->NextRow());
}
}
void GuildAchievementMgr::SaveToDB(SQLTransaction& trans)
{
PreparedStatement* stmt;
std::ostringstream guidstr;
for (auto itr = _completedAchievements.begin(); itr != _completedAchievements.end(); ++itr)
{
if (!itr->second.Changed)
continue;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_ACHIEVEMENT);
stmt->setUInt64(0, _owner->GetId());
stmt->setUInt32(1, itr->first);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_ACHIEVEMENT);
stmt->setUInt64(0, _owner->GetId());
stmt->setUInt32(1, itr->first);
stmt->setUInt32(2, uint32(itr->second.Date));
for (GuidSet::const_iterator gItr = itr->second.CompletingPlayers.begin(); gItr != itr->second.CompletingPlayers.end(); ++gItr)
guidstr << gItr->GetCounter() << ',';
stmt->setString(3, guidstr.str());
trans->Append(stmt);
guidstr.str("");
}
for (auto itr = _criteriaProgress.begin(); itr != _criteriaProgress.end(); ++itr)
{
if (!itr->second.Changed)
continue;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_ACHIEVEMENT_CRITERIA);
stmt->setUInt64(0, _owner->GetId());
stmt->setUInt32(1, itr->first);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_ACHIEVEMENT_CRITERIA);
stmt->setUInt64(0, _owner->GetId());
stmt->setUInt32(1, itr->first);
stmt->setUInt64(2, itr->second.Counter);
stmt->setUInt32(3, uint32(itr->second.Date));
stmt->setUInt64(4, itr->second.PlayerGUID.GetCounter());
trans->Append(stmt);
}
}
void GuildAchievementMgr::SendAllData(Player const* receiver) const
{
VisibleAchievementCheck filterInvisible;
WorldPackets::Achievement::AllGuildAchievements allGuildAchievements;
allGuildAchievements.Earned.reserve(_completedAchievements.size());
for (auto itr = _completedAchievements.begin(); itr != _completedAchievements.end(); ++itr)
{
AchievementEntry const* achievement = filterInvisible(*itr);
if (!achievement)
continue;
WorldPackets::Achievement::EarnedAchievement earned;
earned.Id = itr->first;
earned.Date = itr->second.Date;
allGuildAchievements.Earned.push_back(earned);
}
receiver->SendDirectMessage(allGuildAchievements.Write());
}
void GuildAchievementMgr::SendAchievementInfo(Player* receiver, uint32 achievementId /*= 0*/) const
{
WorldPackets::Achievement::GuildCriteriaUpdate guildCriteriaUpdate;
if (AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId))
{
if (CriteriaTree const* tree = sCriteriaMgr->GetCriteriaTree(achievement->CriteriaTree))
{
CriteriaMgr::WalkCriteriaTree(tree, [this, &guildCriteriaUpdate](CriteriaTree const* node)
{
if (node->Criteria)
{
auto progress = this->_criteriaProgress.find(node->Criteria->ID);
if (progress != this->_criteriaProgress.end())
{
WorldPackets::Achievement::GuildCriteriaProgress guildCriteriaProgress;
guildCriteriaProgress.CriteriaID = node->Criteria->ID;
guildCriteriaProgress.DateCreated = 0;
guildCriteriaProgress.DateStarted = 0;
guildCriteriaProgress.DateUpdated = progress->second.Date;
guildCriteriaProgress.Quantity = progress->second.Counter;
guildCriteriaProgress.PlayerGUID = progress->second.PlayerGUID;
guildCriteriaProgress.Flags = 0;
guildCriteriaUpdate.Progress.push_back(guildCriteriaProgress);
}
}
});
}
}
receiver->SendDirectMessage(guildCriteriaUpdate.Write());
}
void GuildAchievementMgr::SendAllTrackedCriterias(Player* receiver, std::set const& trackedCriterias) const
{
WorldPackets::Achievement::GuildCriteriaUpdate guildCriteriaUpdate;
guildCriteriaUpdate.Progress.reserve(trackedCriterias.size());
for (uint32 criteriaId : trackedCriterias)
{
auto progress = _criteriaProgress.find(criteriaId);
if (progress == _criteriaProgress.end())
continue;
WorldPackets::Achievement::GuildCriteriaProgress guildCriteriaProgress;
guildCriteriaProgress.CriteriaID = criteriaId;
guildCriteriaProgress.DateCreated = 0;
guildCriteriaProgress.DateStarted = 0;
guildCriteriaProgress.DateUpdated = progress->second.Date;
guildCriteriaProgress.Quantity = progress->second.Counter;
guildCriteriaProgress.PlayerGUID = progress->second.PlayerGUID;
guildCriteriaProgress.Flags = 0;
guildCriteriaUpdate.Progress.push_back(guildCriteriaProgress);
}
receiver->SendDirectMessage(guildCriteriaUpdate.Write());
}
void GuildAchievementMgr::SendAchievementMembers(Player* receiver, uint32 achievementId) const
{
auto itr = _completedAchievements.find(achievementId);
if (itr != _completedAchievements.end())
{
WorldPackets::Achievement::GuildAchievementMembers guildAchievementMembers;
guildAchievementMembers.GuildGUID = _owner->GetGUID();
guildAchievementMembers.AchievementID = achievementId;
guildAchievementMembers.Member.reserve(itr->second.CompletingPlayers.size());
for (ObjectGuid const& member : itr->second.CompletingPlayers)
guildAchievementMembers.Member.emplace_back(member);
receiver->SendDirectMessage(guildAchievementMembers.Write());
}
}
void GuildAchievementMgr::CompletedAchievement(AchievementEntry const* achievement, Player* referencePlayer)
{
TC_LOG_DEBUG("criteria.achievement", "GuildAchievementMgr::CompletedAchievement(%u)", achievement->ID);
if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER || HasAchieved(achievement->ID))
return;
if (achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_NEWS)
if (Guild* guild = referencePlayer->GetGuild())
guild->AddGuildNews(GUILD_NEWS_GUILD_ACHIEVEMENT, ObjectGuid::Empty, achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_HEADER, achievement->ID);
SendAchievementEarned(achievement);
CompletedAchievementData& ca = _completedAchievements[achievement->ID];
ca.Date = time(NULL);
ca.Changed = true;
if (achievement->Flags & ACHIEVEMENT_FLAG_SHOW_GUILD_MEMBERS)
{
if (referencePlayer->GetGuildId() == _owner->GetId())
ca.CompletingPlayers.insert(referencePlayer->GetGUID());
if (Group const* group = referencePlayer->GetGroup())
for (GroupReference const* ref = group->GetFirstMember(); ref != NULL; ref = ref->next())
if (Player const* groupMember = ref->GetSource())
if (groupMember->GetGuildId() == _owner->GetId())
ca.CompletingPlayers.insert(groupMember->GetGUID());
}
if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
sAchievementMgr->SetRealmCompleted(achievement);
if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
_achievementPoints += achievement->Points;
UpdateCriteria(CRITERIA_TYPE_COMPLETE_ACHIEVEMENT, 0, 0, 0, NULL, referencePlayer);
UpdateCriteria(CRITERIA_TYPE_EARN_ACHIEVEMENT_POINTS, achievement->Points, 0, 0, NULL, referencePlayer);
}
void GuildAchievementMgr::SendCriteriaUpdate(Criteria const* entry, CriteriaProgress const* progress, uint32 /*timeElapsed*/, bool /*timedCompleted*/) const
{
WorldPackets::Achievement::GuildCriteriaUpdate guildCriteriaUpdate;
guildCriteriaUpdate.Progress.resize(1);
WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress[0];
guildCriteriaProgress.CriteriaID = entry->ID;
guildCriteriaProgress.DateCreated = 0;
guildCriteriaProgress.DateStarted = 0;
guildCriteriaProgress.DateUpdated = progress->Date;
guildCriteriaProgress.Quantity = progress->Counter;
guildCriteriaProgress.PlayerGUID = progress->PlayerGUID;
guildCriteriaProgress.Flags = 0;
_owner->BroadcastPacketIfTrackingAchievement(guildCriteriaUpdate.Write(), entry->ID);
}
void GuildAchievementMgr::SendCriteriaProgressRemoved(uint32 criteriaId)
{
WorldPackets::Achievement::GuildCriteriaDeleted guildCriteriaDeleted;
guildCriteriaDeleted.GuildGUID = _owner->GetGUID();
guildCriteriaDeleted.CriteriaID = criteriaId;
SendPacket(guildCriteriaDeleted.Write());
}
void GuildAchievementMgr::SendAchievementEarned(AchievementEntry const* achievement) const
{
if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_KILL | ACHIEVEMENT_FLAG_REALM_FIRST_REACH))
{
// broadcast realm first reached
WorldPackets::Achievement::BroadcastAchievement serverFirstAchievement;
serverFirstAchievement.Name = _owner->GetName();
serverFirstAchievement.PlayerGUID = _owner->GetGUID();
serverFirstAchievement.AchievementID = achievement->ID;
serverFirstAchievement.GuildAchievement = true;
sWorld->SendGlobalMessage(serverFirstAchievement.Write());
}
WorldPackets::Achievement::GuildAchievementEarned guildAchievementEarned;
guildAchievementEarned.AchievementID = achievement->ID;
guildAchievementEarned.GuildGUID = _owner->GetGUID();
guildAchievementEarned.TimeEarned = time(NULL);
SendPacket(guildAchievementEarned.Write());
}
void GuildAchievementMgr::SendPacket(WorldPacket const* data) const
{
_owner->BroadcastPacket(data);
}
CriteriaList const& GuildAchievementMgr::GetCriteriaByType(CriteriaTypes type) const
{
return sCriteriaMgr->GetGuildCriteriaByType(type);
}
std::string PlayerAchievementMgr::GetOwnerInfo() const
{
return Trinity::StringFormat("%s %s", _owner->GetGUID().ToString().c_str(), _owner->GetName().c_str());
}
std::string GuildAchievementMgr::GetOwnerInfo() const
{
return Trinity::StringFormat("Guild ID " UI64FMTD " %s", _owner->GetId(), _owner->GetName().c_str());
}
AchievementGlobalMgr* AchievementGlobalMgr::Instance()
{
static AchievementGlobalMgr instance;
return &instance;
}
std::vector const* AchievementGlobalMgr::GetAchievementByReferencedId(uint32 id) const
{
auto itr = _achievementListByReferencedId.find(id);
return itr != _achievementListByReferencedId.end() ? &itr->second : NULL;
}
AchievementReward const* AchievementGlobalMgr::GetAchievementReward(AchievementEntry const* achievement) const
{
auto iter = _achievementRewards.find(achievement->ID);
return iter != _achievementRewards.end() ? &iter->second : NULL;
}
AchievementRewardLocale const* AchievementGlobalMgr::GetAchievementRewardLocale(AchievementEntry const* achievement) const
{
auto iter = _achievementRewardLocales.find(achievement->ID);
return iter != _achievementRewardLocales.end() ? &iter->second : NULL;
}
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 (std::chrono::system_clock::now() - itr->second) > Minutes(1);
return true;
}
void AchievementGlobalMgr::SetRealmCompleted(AchievementEntry const* achievement)
{
if (IsRealmCompleted(achievement))
return;
_allCompletedAchievements[achievement->ID] = std::chrono::system_clock::now();
}
//==========================================================
void AchievementGlobalMgr::LoadAchievementReferenceList()
{
uint32 oldMSTime = getMSTime();
if (sAchievementStore.GetNumRows() == 0)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 achievement references.");
return;
}
uint32 count = 0;
for (uint32 entryId = 0; entryId < sAchievementStore.GetNumRows(); ++entryId)
{
AchievementEntry const* achievement = sAchievementStore.LookupEntry(entryId);
if (!achievement || !achievement->SharesCriteria)
continue;
_achievementListByReferencedId[achievement->SharesCriteria].push_back(achievement);
++count;
}
DB2HotfixGenerator hotfixes(sAchievementStore);
// Once Bitten, Twice Shy (10 player) - Icecrown Citadel
// Correct map requirement (currently has Ulduar); 6.0.3 note - it STILL has ulduar requirement
hotfixes.ApplyHotfix(4539, [](AchievementEntry* achievement)
{
achievement->InstanceID = 631;
});
TC_LOG_INFO("server.loading", ">> Loaded %u achievement references in %u ms.", count, GetMSTimeDiffToNow(oldMSTime));
}
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 (AchievementEntry const* achievement : sAchievementStore)
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)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 realm first completed achievements. DB table `character_achievement` is empty.");
return;
}
do
{
Field* fields = result->Fetch();
uint32 achievementId = fields[0].GetUInt32();
AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId);
if (!achievement)
{
// Remove non-existing achievements from all characters
TC_LOG_ERROR("criteria.achievement", "Non-existing achievement %u data has been removed from the table `character_achievement`.", achievementId);
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_ACHIEVMENT);
stmt->setUInt32(0, achievementId);
CharacterDatabase.Execute(stmt);
continue;
}
else if (achievement->Flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
_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)_allCompletedAchievements.size(), GetMSTimeDiffToNow(oldMSTime));
}
void AchievementGlobalMgr::LoadRewards()
{
uint32 oldMSTime = getMSTime();
_achievementRewards.clear(); // need for reload case
// 0 1 2 3 4 5 6 7
QueryResult result = WorldDatabase.Query("SELECT ID, TitleA, TitleH, ItemID, Sender, Subject, Body, MailTemplateID FROM achievement_reward");
if (!result)
{
TC_LOG_ERROR("server.loading", ">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
return;
}
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].GetUInt32();
AchievementEntry const* achievement = sAchievementStore.LookupEntry(id);
if (!achievement)
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` contains a wrong achievement ID (%u), ignored.", id);
continue;
}
AchievementReward reward;
reward.TitleId[0] = fields[1].GetUInt32();
reward.TitleId[1] = fields[2].GetUInt32();
reward.ItemId = fields[3].GetUInt32();
reward.SenderCreatureId = fields[4].GetUInt32();
reward.Subject = fields[5].GetString();
reward.Body = fields[6].GetString();
reward.MailTemplateId = fields[7].GetUInt32();
// must be title or mail at least
if (!reward.TitleId[0] && !reward.TitleId[1] && !reward.SenderCreatureId)
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) does not contain title or item reward data. Ignored.", id);
continue;
}
if (achievement->Faction == ACHIEVEMENT_FACTION_ANY && (!reward.TitleId[0] ^ !reward.TitleId[1]))
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) contains the title (A: %u H: %u) for only one team.", id, reward.TitleId[0], reward.TitleId[1]);
if (reward.TitleId[0])
{
CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.TitleId[0]);
if (!titleEntry)
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (Entry: %u) contains an invalid title id (%u) in `title_A`, set to 0", id, reward.TitleId[0]);
reward.TitleId[0] = 0;
}
}
if (reward.TitleId[1])
{
CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.TitleId[1]);
if (!titleEntry)
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (Entry: %u) contains an invalid title id (%u) in `title_H`, set to 0", id, reward.TitleId[1]);
reward.TitleId[1] = 0;
}
}
//check mail data before item for report including wrong item case
if (reward.SenderCreatureId)
{
if (!sObjectMgr->GetCreatureTemplate(reward.SenderCreatureId))
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) contains an invalid creature ID %u as sender, mail reward skipped.", id, reward.SenderCreatureId);
reward.SenderCreatureId = 0;
}
}
else
{
if (reward.ItemId)
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) does not have sender data, but contains an item reward. Item will not be rewarded.", id);
if (!reward.Subject.empty())
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) does not have sender data, but contains a mail subject.", id);
if (!reward.Body.empty())
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) does not have sender data, but contains mail text.", id);
if (reward.MailTemplateId)
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) does not have sender data, but has a MailTemplate.", id);
}
if (reward.MailTemplateId)
{
if (!sMailTemplateStore.LookupEntry(reward.MailTemplateId))
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) is using an invalid MailTemplate (%u).", id, reward.MailTemplateId);
reward.MailTemplateId = 0;
}
else if (!reward.Subject.empty() || !reward.Body.empty())
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) is using MailTemplate (%u) and mail subject/text.", id, reward.MailTemplateId);
}
if (reward.ItemId)
{
if (!sObjectMgr->GetItemTemplate(reward.ItemId))
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: %u) contains an invalid item id %u, reward mail will not contain the rewarded item.", id, reward.ItemId);
reward.ItemId = 0;
}
}
_achievementRewards[id] = reward;
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded %u achievement rewards in %u ms.", uint32(_achievementRewards.size()), GetMSTimeDiffToNow(oldMSTime));
}
void AchievementGlobalMgr::LoadRewardLocales()
{
uint32 oldMSTime = getMSTime();
_achievementRewardLocales.clear(); // need for reload case
// 0 1 2 3
QueryResult result = WorldDatabase.Query("SELECT ID, Locale, Subject, Body FROM achievement_reward_locale");
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 achievement reward locale strings. DB table `achievement_reward_locale` is empty.");
return;
}
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].GetUInt32();
std::string localeName = fields[1].GetString();
if (_achievementRewards.find(id) == _achievementRewards.end())
{
TC_LOG_ERROR("sql.sql", "Table `achievement_reward_locale` (ID: %u) contains locale strings for a non-existing achievement reward.", id);
continue;
}
AchievementRewardLocale& data = _achievementRewardLocales[id];
LocaleConstant locale = GetLocaleByName(localeName);
if (locale == LOCALE_enUS)
continue;
ObjectMgr::AddLocaleString(fields[2].GetString(), locale, data.Subject);
ObjectMgr::AddLocaleString(fields[3].GetString(), locale, data.Body);
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded %u achievement reward locale strings in %u ms.", uint32(_achievementRewardLocales.size()), GetMSTimeDiffToNow(oldMSTime));
}