TrinityCore
Loading...
Searching...
No Matches
AchievementMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "AchievementMgr.h"
19#include "AchievementPackets.h"
20#include "CellImpl.h"
21#include "ChatTextBuilder.h"
22#include "DB2HotfixGenerator.h"
23#include "DB2Stores.h"
24#include "DatabaseEnv.h"
25#include "GameTime.h"
26#include "GridNotifiersImpl.h"
27#include "Group.h"
28#include "Guild.h"
29#include "GuildMgr.h"
30#include "Item.h"
31#include "Language.h"
32#include "Log.h"
33#include "Mail.h"
34#include "MapUtils.h"
35#include "ObjectMgr.h"
36#include "RBAC.h"
37#include "ScriptMgr.h"
38#include "StringConvert.h"
39#include "World.h"
40#include "WorldSession.h"
41
43{
44 AchievementEntry const* operator()(std::pair<uint32 const, CompletedAchievementData> const& val)
45 {
46 AchievementEntry const* achievement = sAchievementStore.LookupEntry(val.first);
47 if (achievement && !(achievement->Flags & ACHIEVEMENT_FLAG_HIDDEN))
48 return achievement;
49 return nullptr;
50 }
51};
52
53AchievementMgr::AchievementMgr() : _achievementPoints(0) { }
54
56
61{
62 // suppress sending packets
64 UpdateCriteria(criteriaType, 0, 0, 0, nullptr, referencePlayer);
65}
66
67bool AchievementMgr::HasAchieved(uint32 achievementId) const
68{
69 return _completedAchievements.find(achievementId) != _completedAchievements.end();
70}
71
76
78{
79 std::vector<uint32> achievementIds(_completedAchievements.size());
80 std::ranges::transform(_completedAchievements, achievementIds.begin(), Trinity::Containers::MapKey);
81 return achievementIds;
82}
83
84bool AchievementMgr::CanUpdateCriteriaTree(Criteria const* criteria, CriteriaTree const* tree, Player* referencePlayer) const
85{
86 AchievementEntry const* achievement = tree->Achievement;
87 if (!achievement)
88 return false;
89
90 if (HasAchieved(achievement->ID))
91 {
92 TC_LOG_TRACE("criteria.achievement", "AchievementMgr::CanUpdateCriteriaTree: (Id: {} Type {} Achievement {}) Achievement already earned",
93 criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), achievement->ID);
94 return false;
95 }
96
97 if ((achievement->Faction == ACHIEVEMENT_FACTION_HORDE && referencePlayer->GetTeam() != HORDE) ||
98 (achievement->Faction == ACHIEVEMENT_FACTION_ALLIANCE && referencePlayer->GetTeam() != ALLIANCE))
99 {
100 TC_LOG_TRACE("criteria.achievement", "AchievementMgr::CanUpdateCriteriaTree: (Id: {} Type {} Achievement {}) Wrong faction",
101 criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), achievement->ID);
102 return false;
103 }
104
105 // Don't update realm first achievements if the player's account isn't allowed to do so
108 return false;
109
110 if (achievement->CovenantID && referencePlayer->m_playerData->CovenantID != achievement->CovenantID)
111 {
112 TC_LOG_TRACE("criteria.achievement", "AchievementMgr::CanUpdateCriteriaTree: (Id: {} Type {} Achievement {}) Wrong covenant",
113 criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), achievement->ID);
114 return false;
115 }
116
117 return CriteriaHandler::CanUpdateCriteriaTree(criteria, tree, referencePlayer);
118}
119
121{
122 AchievementEntry const* achievement = tree->Achievement;
123 if (!achievement)
124 return false;
125
126 // counter can never complete
127 if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER)
128 return false;
129
131 {
132 // someone on this realm has already completed that achievement
133 if (sAchievementMgr->IsRealmCompleted(achievement))
134 return false;
135 }
136
137 return true;
138}
139
141{
142 AchievementEntry const* achievement = tree->Achievement;
143 if (!achievement)
144 return;
145
146 // counter can never complete
147 if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER)
148 return;
149
150 // already completed and stored
151 if (HasAchieved(achievement->ID))
152 return;
153
154 if (IsCompletedAchievement(achievement))
155 CompletedAchievement(achievement, referencePlayer);
156}
157
159{
160 AchievementEntry const* achievement = tree->Achievement;
161 if (!achievement)
162 return;
163
164 // check again the completeness for SUMM and REQ COUNT achievements,
165 // as they don't depend on the completed criteria but on the sum of the progress of each individual criteria
166 if (achievement->Flags & ACHIEVEMENT_FLAG_SUMM)
167 if (IsCompletedAchievement(achievement))
168 CompletedAchievement(achievement, referencePlayer);
169
170 if (std::vector<AchievementEntry const*> const* achRefList = sAchievementMgr->GetAchievementByReferencedId(achievement->ID))
171 for (AchievementEntry const* refAchievement : *achRefList)
172 if (IsCompletedAchievement(refAchievement))
173 CompletedAchievement(refAchievement, referencePlayer);
174}
175
177{
178 // counter can never complete
179 if (entry->Flags & ACHIEVEMENT_FLAG_COUNTER)
180 return false;
181
182 CriteriaTree const* tree = sCriteriaMgr->GetCriteriaTree(entry->CriteriaTree);
183 if (!tree)
184 return false;
185
186 // For SUMM achievements, we have to count the progress of each criteria of the achievement.
187 // Oddly, the target count is NOT contained in the achievement, but in each individual criteria
188 if (entry->Flags & ACHIEVEMENT_FLAG_SUMM)
189 {
190 int64 progress = 0;
191 CriteriaMgr::WalkCriteriaTree(tree, [this, &progress](CriteriaTree const* criteriaTree)
192 {
193 if (criteriaTree->Criteria)
194 if (CriteriaProgress const* criteriaProgress = this->GetCriteriaProgress(criteriaTree->Criteria))
195 progress += criteriaProgress->Counter;
196 });
197 return progress >= tree->Entry->Amount;
198 }
199
200 return IsCompletedCriteriaTree(tree);
201}
202
204{
205 return HasAchieved(achievementId);
206}
207
209{
210}
211
213{
215
216 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
217 {
219 achievementDeleted.AchievementID = completedAchievement.first;
220 SendPacket(achievementDeleted.Write());
221 }
222
226
227 // re-fill data
229}
230
232{
233 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
234
236 stmt->setUInt64(0, guid.GetCounter());
237 trans->Append(stmt);
238
239 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS);
240 stmt->setUInt64(0, guid.GetCounter());
241 trans->Append(stmt);
242
243 CharacterDatabase.CommitTransaction(trans);
244}
245
247{
248 if (achievementResult)
249 {
250 do
251 {
252 Field* fields = achievementResult->Fetch();
253 uint32 achievementid = fields[0].GetUInt32();
254
255 // must not happen: cleanup at server startup in sAchievementMgr->LoadCompletedAchievements()
256 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementid);
257 if (!achievement)
258 continue;
259
261 ca.Date = fields[1].GetInt64();
262 ca.Changed = false;
263
264 _achievementPoints += achievement->Points;
265
266 // title achievement rewards are retroactive
267 if (AchievementReward const* reward = sAchievementMgr->GetAchievementReward(achievement))
268 if (uint32 titleId = reward->TitleId[Player::TeamForRace(_owner->GetRace()) == ALLIANCE ? 0 : 1])
269 if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId))
270 _owner->SetTitle(titleEntry);
271
272 } while (achievementResult->NextRow());
273 }
274
275 if (criteriaResult)
276 {
277 time_t now = GameTime::GetGameTime();
278 do
279 {
280 Field* fields = criteriaResult->Fetch();
281 uint32 id = fields[0].GetUInt32();
282 uint64 counter = fields[1].GetUInt64();
283 time_t date = fields[2].GetInt64();
284
285 Criteria const* criteria = sCriteriaMgr->GetCriteria(id);
286 if (!criteria)
287 {
288 // Removing non-existing criteria data for all characters
289 TC_LOG_ERROR("criteria.achievement", "Non-existing achievement criteria {} data has been removed from the table `character_achievement_progress`.", id);
290
292 stmt->setUInt32(0, id);
293 CharacterDatabase.Execute(stmt);
294
295 continue;
296 }
297
298 if (criteria->Entry->StartTimer && time_t(date + criteria->Entry->StartTimer) < now)
299 continue;
300
301 CriteriaProgress& progress = _criteriaProgress[id];
302 progress.Counter = counter;
303 progress.Date = date;
304 progress.PlayerGUID = _owner->GetGUID();
305 progress.Changed = false;
306 } while (criteriaResult->NextRow());
307 }
308}
309
311{
312 if (!_completedAchievements.empty())
313 {
314 for (std::pair<uint32 const, CompletedAchievementData>& completedAchievement : _completedAchievements)
315 {
316 if (!completedAchievement.second.Changed)
317 continue;
318
320 stmt->setUInt32(0, completedAchievement.first);
321 stmt->setUInt64(1, _owner->GetGUID().GetCounter());
322 trans->Append(stmt);
323
324 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACHIEVEMENT);
325 stmt->setUInt64(0, _owner->GetGUID().GetCounter());
326 stmt->setUInt32(1, completedAchievement.first);
327 stmt->setInt64(2, completedAchievement.second.Date);
328 trans->Append(stmt);
329
330 completedAchievement.second.Changed = false;
331 }
332 }
333
334 if (!_criteriaProgress.empty())
335 {
336 for (std::pair<uint32 const, CriteriaProgress>& criteriaProgres : _criteriaProgress)
337 {
338 if (!criteriaProgres.second.Changed)
339 continue;
340
342 stmt->setUInt64(0, _owner->GetGUID().GetCounter());
343 stmt->setUInt32(1, criteriaProgres.first);
344 trans->Append(stmt);
345
346 if (criteriaProgres.second.Counter)
347 {
348 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACHIEVEMENT_PROGRESS);
349 stmt->setUInt64(0, _owner->GetGUID().GetCounter());
350 stmt->setUInt32(1, criteriaProgres.first);
351 stmt->setUInt64(2, criteriaProgres.second.Counter);
352 stmt->setInt64(3, criteriaProgres.second.Date);
353 trans->Append(stmt);
354 }
355
356 criteriaProgres.second.Changed = false;
357 }
358 }
359}
360
361void PlayerAchievementMgr::SendAllData(Player const* /*receiver*/) const
362{
363 VisibleAchievementCheck filterInvisible;
365
367 achievementData.Data.Earned.reserve(_completedAchievements.size());
368 achievementData.Data.Progress.reserve(_criteriaProgress.size());
369
370 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
371 {
372 AchievementEntry const* achievement = filterInvisible(completedAchievement);
373 if (!achievement)
374 continue;
375
376 WorldPackets::Achievement::EarnedAchievement& earned = achievementData.Data.Earned.emplace_back();
377 earned.Id = completedAchievement.first;
378 earned.Date.SetUtcTimeFromUnixTime(completedAchievement.second.Date);
379 earned.Date += _owner->GetSession()->GetTimezoneOffset();
380 if (!(achievement->Flags & ACHIEVEMENT_FLAG_ACCOUNT))
381 {
382 earned.Owner = _owner->GetGUID();
383 earned.VirtualRealmAddress = earned.NativeRealmAddress = _owner->m_playerData->VirtualPlayerRealm;
384 }
385 }
386
387 for (std::pair<uint32 const, CriteriaProgress> const& criteriaProgres : _criteriaProgress)
388 {
389 Criteria const* criteria = sCriteriaMgr->GetCriteria(criteriaProgres.first);
390
391 WorldPackets::Achievement::CriteriaProgress& progress = achievementData.Data.Progress.emplace_back();
392 progress.Id = criteriaProgres.first;
393 progress.Quantity = criteriaProgres.second.Counter;
394 progress.Player = criteriaProgres.second.PlayerGUID;
395 progress.Flags = 0;
396 progress.Date.SetUtcTimeFromUnixTime(criteriaProgres.second.Date);
397 progress.Date += _owner->GetSession()->GetTimezoneOffset();
398 progress.TimeFromStart = Seconds::zero();
399 progress.TimeFromCreate = Seconds::zero();
400
401 if (criteria->FlagsCu & CRITERIA_FLAG_CU_ACCOUNT)
402 {
403 WorldPackets::Achievement::CriteriaProgress& accountProgress = allAccountCriteria.Progress.emplace_back();
404 accountProgress.Id = criteriaProgres.first;
405 accountProgress.Quantity = criteriaProgres.second.Counter;
406 accountProgress.Player = _owner->GetSession()->GetBattlenetAccountGUID();
407 accountProgress.Flags = 0;
408 accountProgress.Date.SetUtcTimeFromUnixTime(criteriaProgres.second.Date);
409 accountProgress.Date += _owner->GetSession()->GetTimezoneOffset();
410 accountProgress.TimeFromStart = Seconds::zero();
411 accountProgress.TimeFromCreate = Seconds::zero();
412 }
413 }
414
415 if (!allAccountCriteria.Progress.empty())
416 SendPacket(allAccountCriteria.Write());
417
418 SendPacket(achievementData.Write());
419}
420
421void PlayerAchievementMgr::SendAchievementInfo(Player* receiver, uint32 /*achievementId = 0 */) const
422{
423 VisibleAchievementCheck filterInvisible;
425 inspectedAchievements.Player = _owner->GetGUID();
426 inspectedAchievements.Data.Earned.reserve(_completedAchievements.size());
427 inspectedAchievements.Data.Progress.reserve(_criteriaProgress.size());
428
429 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
430 {
431 AchievementEntry const* achievement = filterInvisible(completedAchievement);
432 if (!achievement)
433 continue;
434
435 WorldPackets::Achievement::EarnedAchievement& earned = inspectedAchievements.Data.Earned.emplace_back();
436 earned.Id = completedAchievement.first;
437 earned.Date.SetUtcTimeFromUnixTime(completedAchievement.second.Date);
438 earned.Date += receiver->GetSession()->GetTimezoneOffset();
439 if (!(achievement->Flags & ACHIEVEMENT_FLAG_ACCOUNT))
440 {
441 earned.Owner = _owner->GetGUID();
442 earned.VirtualRealmAddress = earned.NativeRealmAddress = _owner->m_playerData->VirtualPlayerRealm;
443 }
444 }
445
446 for (std::pair<uint32 const, CriteriaProgress> const& criteriaProgres : _criteriaProgress)
447 {
448 WorldPackets::Achievement::CriteriaProgress& progress = inspectedAchievements.Data.Progress.emplace_back();
449 progress.Id = criteriaProgres.first;
450 progress.Quantity = criteriaProgres.second.Counter;
451 progress.Player = criteriaProgres.second.PlayerGUID;
452 progress.Flags = 0;
453 progress.Date.SetUtcTimeFromUnixTime(criteriaProgres.second.Date);
454 progress.Date += receiver->GetSession()->GetTimezoneOffset();
455 progress.TimeFromStart = Seconds::zero();
456 progress.TimeFromCreate = Seconds::zero();
457 }
458
459 receiver->SendDirectMessage(inspectedAchievements.Write());
460}
461
463{
464 // Disable for GameMasters with GM-mode enabled or for players that don't have the related RBAC permission
466 return;
467
468 if ((achievement->Faction == ACHIEVEMENT_FACTION_HORDE && referencePlayer->GetTeam() != HORDE) ||
469 (achievement->Faction == ACHIEVEMENT_FACTION_ALLIANCE && referencePlayer->GetTeam() != ALLIANCE))
470 return;
471
472 if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER || HasAchieved(achievement->ID))
473 return;
474
476 if (Guild* guild = referencePlayer->GetGuild())
477 guild->AddGuildNews(GUILD_NEWS_PLAYER_ACHIEVEMENT, referencePlayer->GetGUID(), achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_HEADER, achievement->ID);
478
480 SendAchievementEarned(achievement);
481
482 TC_LOG_INFO("criteria.achievement", "PlayerAchievementMgr::CompletedAchievement({}). {}", achievement->ID, GetOwnerInfo());
483
486 ca.Changed = true;
487
489 sAchievementMgr->SetRealmCompleted(achievement);
490
491 if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
492 _achievementPoints += achievement->Points;
493
494 referencePlayer->UpdateCriteria(CriteriaType::EarnAchievement, achievement->ID, 0, 0, nullptr);
495 referencePlayer->UpdateCriteria(CriteriaType::EarnAchievementPoints, achievement->Points, 0, 0, nullptr);
496
497 sScriptMgr->OnAchievementCompleted(referencePlayer, achievement);
498
499 // reward items and titles if any
500 AchievementReward const* reward = sAchievementMgr->GetAchievementReward(achievement);
501
502 // no rewards
503 if (!reward)
504 return;
505
506 // titles
511 int32 titleId = [&]
512 {
513 if (achievement->ID == 1793)
514 return reward->TitleId[_owner->GetNativeGender()];
515 switch (_owner->GetTeam())
516 {
517 case ALLIANCE: return reward->TitleId[0];
518 case HORDE: return reward->TitleId[1];
519 default: break;
520 }
521 return 0u;
522 }();
523 if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId))
524 _owner->SetTitle(titleEntry);
525
526 // mail
527 if (reward->SenderCreatureId)
528 {
529 MailDraft draft(uint16(reward->MailTemplateId));
530
531 if (!reward->MailTemplateId)
532 {
533 // subject and text
534 std::string subject = reward->Subject;
535 std::string text = reward->Body;
536
538 if (localeConstant != LOCALE_enUS)
539 {
540 if (AchievementRewardLocale const* loc = sAchievementMgr->GetAchievementRewardLocale(achievement))
541 {
542 ObjectMgr::GetLocaleString(loc->Subject, localeConstant, subject);
543 ObjectMgr::GetLocaleString(loc->Body, localeConstant, text);
544 }
545 }
546
547 draft = MailDraft(subject, text);
548 }
549
550 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
551
552 Item* item = reward->ItemId ? Item::CreateItem(reward->ItemId, 1, ItemContext::NONE, _owner) : nullptr;
553 if (item)
554 {
555 // save new item before send
556 item->SaveToDB(trans); // save for prevent lost at next mail load, if send fail then item will deleted
557
558 // item
559 draft.AddItem(item);
560 }
561
563 CharacterDatabase.CommitTransaction(trans);
564 }
565}
566
568{
569 if (ModifierTreeNode const* modifierTree = sCriteriaMgr->GetModifierTree(modifierTreeId))
570 return ModifierTreeSatisfied(modifierTree, 0, 0, nullptr, _owner);
571
572 return false;
573}
574
575void PlayerAchievementMgr::SendCriteriaUpdate(Criteria const* criteria, CriteriaProgress const* progress, Seconds timeElapsed, bool timedCompleted) const
576{
577 if (criteria->FlagsCu & CRITERIA_FLAG_CU_ACCOUNT)
578 {
580
581 criteriaUpdate.Progress.Id = criteria->ID;
582 criteriaUpdate.Progress.Quantity = progress->Counter;
584 criteriaUpdate.Progress.Flags = 0;
585 if (criteria->Entry->StartTimer)
586 criteriaUpdate.Progress.Flags = timedCompleted ? 1 : 0; // 1 is for keeping the counter at 0 in client
587
588 criteriaUpdate.Progress.Date.SetUtcTimeFromUnixTime(progress->Date);
589 criteriaUpdate.Progress.Date += _owner->GetSession()->GetTimezoneOffset();
590 criteriaUpdate.Progress.TimeFromStart = timeElapsed;
591 criteriaUpdate.Progress.TimeFromCreate = Seconds::zero();
592
593 SendPacket(criteriaUpdate.Write());
594 }
595 if (criteria->FlagsCu & CRITERIA_FLAG_CU_PLAYER)
596 {
598
599 criteriaUpdate.CriteriaID = criteria->ID;
600 criteriaUpdate.Quantity = progress->Counter;
601 criteriaUpdate.PlayerGUID = _owner->GetGUID();
602 criteriaUpdate.Flags = 0;
603 if (criteria->Entry->StartTimer)
604 criteriaUpdate.Flags = timedCompleted ? 1 : 0; // 1 is for keeping the counter at 0 in client
605
606 criteriaUpdate.CurrentTime.SetUtcTimeFromUnixTime(progress->Date);
607 criteriaUpdate.CurrentTime += _owner->GetSession()->GetTimezoneOffset();
608 criteriaUpdate.ElapsedTime = timeElapsed;
609 criteriaUpdate.CreationTime = 0;
610
611 SendPacket(criteriaUpdate.Write());
612 }
613}
614
616{
618 criteriaDeleted.CriteriaID = criteriaId;
619 SendPacket(criteriaDeleted.Write());
620}
621
623{
624 // Don't send for achievements with ACHIEVEMENT_FLAG_HIDDEN
625 if (achievement->Flags & ACHIEVEMENT_FLAG_HIDDEN)
626 return;
627
628 TC_LOG_DEBUG("criteria.achievement", "PlayerAchievementMgr::SendAchievementEarned({})", achievement->ID);
629
630 if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
631 {
632 if (Guild* guild = sGuildMgr->GetGuildById(_owner->GetGuildId()))
633 {
636 guild->BroadcastWorker(_localizer, _owner);
637 }
638
640 {
641 // broadcast realm first reached
643 serverFirstAchievement.Name = _owner->GetName();
644 serverFirstAchievement.PlayerGUID = _owner->GetGUID();
645 serverFirstAchievement.AchievementID = achievement->ID;
646 sWorld->SendGlobalMessage(serverFirstAchievement.Write());
647 }
648 // if player is in world he can tell his friends about new achievement
649 else if (_owner->IsInWorld())
650 {
655 }
656 }
657
658 auto achievementEarnedBuilder = [&](Player const* receiver)
659 {
661 achievementEarned.Sender = _owner->GetGUID();
662 achievementEarned.Earner = _owner->GetGUID();
663 achievementEarned.EarnerNativeRealm = achievementEarned.EarnerVirtualRealm = _owner->m_playerData->VirtualPlayerRealm;
664 achievementEarned.AchievementID = achievement->ID;
665 achievementEarned.Time = *GameTime::GetUtcWowTime();
666 achievementEarned.Time += receiver->GetSession()->GetTimezoneOffset();
667 achievementEarned.Initial = receiver->HasAtLoginFlag(AT_LOGIN_FIRST);
668 receiver->SendDirectMessage(achievementEarned.Write());
669 };
670
671 achievementEarnedBuilder(_owner);
672
673 if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
674 {
675 float dist = sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY);
676 Trinity::MessageDistDeliverer notifier(_owner, achievementEarnedBuilder, dist);
677 Cell::VisitWorldObjects(_owner, notifier, dist);
678 }
679}
680
682{
684}
685
687{
688 return sCriteriaMgr->GetPlayerCriteriaByType(type, asset);
689}
690
692{
693}
694
696{
698
699 ObjectGuid guid = _owner->GetGUID();
700 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
701 {
702 _owner->BroadcastWorker([&](Player const* receiver)
703 {
705 guildAchievementDeleted.AchievementID = completedAchievement.first;
706 guildAchievementDeleted.GuildGUID = guid;
707 guildAchievementDeleted.TimeDeleted = *GameTime::GetUtcWowTime();
708 guildAchievementDeleted.TimeDeleted += receiver->GetSession()->GetTimezoneOffset();
709 receiver->SendDirectMessage(guildAchievementDeleted.Write());
710 });
711 }
712
715 DeleteFromDB(guid);
716}
717
719{
720 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
721
723 stmt->setUInt64(0, guid.GetCounter());
724 trans->Append(stmt);
725
727 stmt->setUInt64(0, guid.GetCounter());
728 trans->Append(stmt);
729
730 CharacterDatabase.CommitTransaction(trans);
731}
732
734{
735 if (achievementResult)
736 {
737 do
738 {
739 Field* fields = achievementResult->Fetch();
740 uint32 achievementid = fields[0].GetUInt32();
741
742 // must not happen: cleanup at server startup in sAchievementMgr->LoadCompletedAchievements()
743 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementid);
744 if (!achievement)
745 continue;
746
748 ca.Date = fields[1].GetInt64();
749 for (std::string_view guid : Trinity::Tokenize(fields[2].GetStringView(), ',', false))
750 if (Optional<ObjectGuid::LowType> parsedGuid = Trinity::StringTo<ObjectGuid::LowType>(guid))
751 ca.CompletingPlayers.insert(ObjectGuid::Create<HighGuid::Player>(*parsedGuid));
752
753 ca.Changed = false;
754
755 _achievementPoints += achievement->Points;
756 } while (achievementResult->NextRow());
757 }
758
759 if (criteriaResult)
760 {
761 time_t now = GameTime::GetGameTime();
762 do
763 {
764 Field* fields = criteriaResult->Fetch();
765 uint32 id = fields[0].GetUInt32();
766 uint64 counter = fields[1].GetUInt64();
767 time_t date = fields[2].GetInt64();
768 ObjectGuid::LowType guid = fields[3].GetUInt64();
769
770 Criteria const* criteria = sCriteriaMgr->GetCriteria(id);
771 if (!criteria)
772 {
773 // Remove non-existing achievements from all guilds
774 TC_LOG_ERROR("criteria.achievement", "Non-existing achievement criteria {} data removed from table `guild_achievement_progress`.", id);
775
777 stmt->setUInt32(0, id);
778 CharacterDatabase.Execute(stmt);
779 continue;
780 }
781
782 if (criteria->Entry->StartTimer && time_t(date + criteria->Entry->StartTimer) < now)
783 continue;
784
785 CriteriaProgress& progress = _criteriaProgress[id];
786 progress.Counter = counter;
787 progress.Date = date;
788 progress.PlayerGUID = ObjectGuid::Create<HighGuid::Player>(guid);
789 progress.Changed = false;
790 } while (criteriaResult->NextRow());
791 }
792}
793
795{
797 for (std::pair<uint32 const, CompletedAchievementData>& completedAchievement : _completedAchievements)
798 {
799 if (!completedAchievement.second.Changed)
800 continue;
801
802 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_ACHIEVEMENT);
803 stmt->setUInt64(0, _owner->GetId());
804 stmt->setUInt32(1, completedAchievement.first);
805 trans->Append(stmt);
806
807 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_ACHIEVEMENT);
808 stmt->setUInt64(0, _owner->GetId());
809 stmt->setUInt32(1, completedAchievement.first);
810 stmt->setInt64(2, completedAchievement.second.Date);
811 std::string guidstr;
812 auto completersItr = completedAchievement.second.CompletingPlayers.begin();
813 auto completersEnd = completedAchievement.second.CompletingPlayers.end();
814 if (completersItr != completersEnd)
815 {
816 Trinity::StringFormatTo(std::back_inserter(guidstr), "{}", completersItr->GetCounter());
817 while (++completersItr != completersEnd)
818 Trinity::StringFormatTo(std::back_inserter(guidstr), ",{}", completersItr->GetCounter());
819 }
820
821 stmt->setString(3, std::move(guidstr));
822 trans->Append(stmt);
823
824 completedAchievement.second.Changed = false;
825 }
826
827 for (std::pair<uint32 const, CriteriaProgress>& criteriaProgres : _criteriaProgress)
828 {
829 if (!criteriaProgres.second.Changed)
830 continue;
831
832 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_ACHIEVEMENT_CRITERIA);
833 stmt->setUInt64(0, _owner->GetId());
834 stmt->setUInt32(1, criteriaProgres.first);
835 trans->Append(stmt);
836
837 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_ACHIEVEMENT_CRITERIA);
838 stmt->setUInt64(0, _owner->GetId());
839 stmt->setUInt32(1, criteriaProgres.first);
840 stmt->setUInt64(2, criteriaProgres.second.Counter);
841 stmt->setInt64(3, criteriaProgres.second.Date);
842 stmt->setUInt64(4, criteriaProgres.second.PlayerGUID.GetCounter());
843 trans->Append(stmt);
844
845 criteriaProgres.second.Changed = false;
846 }
847}
848
849void GuildAchievementMgr::SendAllData(Player const* receiver) const
850{
851 VisibleAchievementCheck filterInvisible;
853 allGuildAchievements.Earned.reserve(_completedAchievements.size());
854
855 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
856 {
857 AchievementEntry const* achievement = filterInvisible(completedAchievement);
858 if (!achievement)
859 continue;
860
861 WorldPackets::Achievement::EarnedAchievement& earned = allGuildAchievements.Earned.emplace_back();
862 earned.Id = completedAchievement.first;
863 earned.Date.SetUtcTimeFromUnixTime(completedAchievement.second.Date);
864 earned.Date += receiver->GetSession()->GetTimezoneOffset();
865 }
866
867 receiver->SendDirectMessage(allGuildAchievements.Write());
868}
869
870void GuildAchievementMgr::SendAchievementInfo(Player* receiver, uint32 achievementId /*= 0*/) const
871{
873 if (AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId))
874 {
875 if (CriteriaTree const* tree = sCriteriaMgr->GetCriteriaTree(achievement->CriteriaTree))
876 {
877 CriteriaMgr::WalkCriteriaTree(tree, [this, &guildCriteriaUpdate, receiver](CriteriaTree const* node)
878 {
879 if (node->Criteria)
880 {
881 auto progress = this->_criteriaProgress.find(node->Criteria->ID);
882 if (progress != this->_criteriaProgress.end())
883 {
884 WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress.emplace_back();
885 guildCriteriaProgress.CriteriaID = node->Criteria->ID;
886 guildCriteriaProgress.DateCreated = 0;
887 guildCriteriaProgress.DateStarted = 0;
888 guildCriteriaProgress.DateUpdated.SetUtcTimeFromUnixTime(progress->second.Date);
889 guildCriteriaProgress.DateUpdated += receiver->GetSession()->GetTimezoneOffset();
890 guildCriteriaProgress.Quantity = progress->second.Counter;
891 guildCriteriaProgress.PlayerGUID = progress->second.PlayerGUID;
892 guildCriteriaProgress.Flags = 0;
893 }
894 }
895 });
896 }
897 }
898
899 receiver->SendDirectMessage(guildCriteriaUpdate.Write());
900}
901
902void GuildAchievementMgr::SendAllTrackedCriterias(Player* receiver, std::set<uint32> const& trackedCriterias) const
903{
905 guildCriteriaUpdate.Progress.reserve(trackedCriterias.size());
906
907 for (uint32 criteriaId : trackedCriterias)
908 {
909 auto progress = _criteriaProgress.find(criteriaId);
910 if (progress == _criteriaProgress.end())
911 continue;
912
913 WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress.emplace_back();
914 guildCriteriaProgress.CriteriaID = criteriaId;
915 guildCriteriaProgress.DateCreated = 0;
916 guildCriteriaProgress.DateStarted = 0;
917 guildCriteriaProgress.DateUpdated.SetUtcTimeFromUnixTime(progress->second.Date);
918 guildCriteriaProgress.DateUpdated += receiver->GetSession()->GetTimezoneOffset();
919 guildCriteriaProgress.Quantity = progress->second.Counter;
920 guildCriteriaProgress.PlayerGUID = progress->second.PlayerGUID;
921 guildCriteriaProgress.Flags = 0;
922 }
923
924 receiver->SendDirectMessage(guildCriteriaUpdate.Write());
925}
926
928{
929 auto itr = _completedAchievements.find(achievementId);
930 if (itr != _completedAchievements.end())
931 {
933 guildAchievementMembers.GuildGUID = _owner->GetGUID();
934 guildAchievementMembers.AchievementID = achievementId;
935 guildAchievementMembers.Member.reserve(itr->second.CompletingPlayers.size());
936 for (ObjectGuid const& member : itr->second.CompletingPlayers)
937 guildAchievementMembers.Member.emplace_back(member);
938
939 receiver->SendDirectMessage(guildAchievementMembers.Write());
940 }
941}
942
943void GuildAchievementMgr::CompletedAchievement(AchievementEntry const* achievement, Player* referencePlayer)
944{
945 TC_LOG_DEBUG("criteria.achievement", "GuildAchievementMgr::CompletedAchievement({})", achievement->ID);
946
947 if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER || HasAchieved(achievement->ID))
948 return;
949
951 if (Guild* guild = referencePlayer->GetGuild())
952 guild->AddGuildNews(GUILD_NEWS_GUILD_ACHIEVEMENT, ObjectGuid::Empty, achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_HEADER, achievement->ID);
953
954 SendAchievementEarned(achievement);
957 ca.Changed = true;
958
960 {
961 if (referencePlayer->GetGuildId() == _owner->GetId())
962 ca.CompletingPlayers.insert(referencePlayer->GetGUID());
963
964 if (Group const* group = referencePlayer->GetGroup())
965 for (GroupReference const& ref : group->GetMembers())
966 if (ref.GetSource()->GetGuildId() == _owner->GetId())
967 ca.CompletingPlayers.insert(ref.GetSource()->GetGUID());
968 }
969
971 sAchievementMgr->SetRealmCompleted(achievement);
972
973 if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
974 _achievementPoints += achievement->Points;
975
976 UpdateCriteria(CriteriaType::EarnAchievement, achievement->ID, 0, 0, nullptr, referencePlayer);
977 UpdateCriteria(CriteriaType::EarnAchievementPoints, achievement->Points, 0, 0, nullptr, referencePlayer);
978
979 sScriptMgr->OnAchievementCompleted(referencePlayer, achievement);
980}
981
982void GuildAchievementMgr::SendCriteriaUpdate(Criteria const* entry, CriteriaProgress const* progress, Seconds /*timeElapsed*/, bool /*timedCompleted*/) const
983{
984 for (Player const* member : _owner->GetMembersTrackingCriteria(entry->ID))
985 {
987 WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress.emplace_back();
988 guildCriteriaProgress.CriteriaID = entry->ID;
989 guildCriteriaProgress.DateCreated = 0;
990 guildCriteriaProgress.DateStarted = 0;
991 guildCriteriaProgress.DateUpdated.SetUtcTimeFromUnixTime(progress->Date);
992 guildCriteriaProgress.DateUpdated += member->GetSession()->GetTimezoneOffset();
993 guildCriteriaProgress.Quantity = progress->Counter;
994 guildCriteriaProgress.PlayerGUID = progress->PlayerGUID;
995 guildCriteriaProgress.Flags = 0;
996
997 member->SendDirectMessage(guildCriteriaUpdate.Write());
998 }
999}
1000
1002{
1004 guildCriteriaDeleted.GuildGUID = _owner->GetGUID();
1005 guildCriteriaDeleted.CriteriaID = criteriaId;
1006 SendPacket(guildCriteriaDeleted.Write());
1007}
1008
1010{
1012 {
1013 // broadcast realm first reached
1015 serverFirstAchievement.Name = _owner->GetName();
1016 serverFirstAchievement.PlayerGUID = _owner->GetGUID();
1017 serverFirstAchievement.AchievementID = achievement->ID;
1018 serverFirstAchievement.GuildAchievement = true;
1019 sWorld->SendGlobalMessage(serverFirstAchievement.Write());
1020 }
1021
1022 _owner->BroadcastWorker([&](Player const* receiver)
1023 {
1025 guildAchievementEarned.AchievementID = achievement->ID;
1026 guildAchievementEarned.GuildGUID = _owner->GetGUID();
1027 guildAchievementEarned.TimeEarned = *GameTime::GetUtcWowTime();
1028 guildAchievementEarned.TimeEarned += receiver->GetSession()->GetTimezoneOffset();
1029 receiver->SendDirectMessage(guildAchievementEarned.Write());
1030 });
1031}
1032
1034{
1035 _owner->BroadcastPacket(data);
1036}
1037
1039{
1040 return sCriteriaMgr->GetGuildCriteriaByType(type);
1041}
1042
1045
1047{
1048 return Trinity::StringFormat("{} {}", _owner->GetGUID().ToString(), _owner->GetName());
1049}
1050
1052{
1053 return Trinity::StringFormat("Guild ID {} {}", _owner->GetId(), _owner->GetName());
1054}
1055
1057{
1058 static AchievementGlobalMgr instance;
1059 return &instance;
1060}
1061
1062std::vector<AchievementEntry const*> const* AchievementGlobalMgr::GetAchievementByReferencedId(uint32 id) const
1063{
1064 auto itr = _achievementListByReferencedId.find(id);
1065 return itr != _achievementListByReferencedId.end() ? &itr->second : nullptr;
1066}
1067
1069{
1070 auto iter = _achievementRewards.find(achievement->ID);
1071 return iter != _achievementRewards.end() ? &iter->second : nullptr;
1072}
1073
1075{
1076 auto iter = _achievementRewardLocales.find(achievement->ID);
1077 return iter != _achievementRewardLocales.end() ? &iter->second : nullptr;
1078}
1079
1081{
1082 auto itr = _allCompletedAchievements.find(achievement->ID);
1083 if (itr == _allCompletedAchievements.end())
1084 return false;
1085
1086 if (itr->second == SystemTimePoint ::min())
1087 return false;
1088
1089 if (itr->second == SystemTimePoint::max())
1090 return true;
1091
1092 // Allow completing the realm first kill for entire minute after first person did it
1093 // it may allow more than one group to achieve it (highly unlikely)
1094 // but apparently this is how blizz handles it as well
1095 if (achievement->Flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL)
1096 return (GameTime::GetSystemTime() - itr->second) > Minutes(1);
1097
1098 return true;
1099}
1100
1102{
1103 if (IsRealmCompleted(achievement))
1104 return;
1105
1107}
1108
1109//==========================================================
1111{
1112 uint32 oldMSTime = getMSTime();
1113
1114 if (sAchievementStore.GetNumRows() == 0)
1115 {
1116 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement references.");
1117 return;
1118 }
1119
1120 uint32 count = 0;
1121
1122 for (uint32 entryId = 0; entryId < sAchievementStore.GetNumRows(); ++entryId)
1123 {
1124 AchievementEntry const* achievement = sAchievementStore.LookupEntry(entryId);
1125 if (!achievement || !achievement->SharesCriteria)
1126 continue;
1127
1128 _achievementListByReferencedId[achievement->SharesCriteria].push_back(achievement);
1129 ++count;
1130 }
1131
1133
1134 // Once Bitten, Twice Shy (10 player) - Icecrown Citadel
1135 // Correct map requirement (currently has Ulduar); 6.0.3 note - it STILL has ulduar requirement
1136 hotfixes.ApplyHotfix(4539, [](AchievementEntry* achievement)
1137 {
1138 achievement->InstanceID = 631;
1139 });
1140
1141 TC_LOG_INFO("server.loading", ">> Loaded {} achievement references in {} ms.", count, GetMSTimeDiffToNow(oldMSTime));
1142}
1143
1145{
1146 uint32 oldMSTime = getMSTime();
1147
1148 _achievementScripts.clear(); // need for reload case
1149
1150 QueryResult result = WorldDatabase.Query("SELECT AchievementId, ScriptName FROM achievement_scripts");
1151 if (!result)
1152 {
1153 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement scripts. DB table `achievement_scripts` is empty.");
1154 return;
1155 }
1156
1157 do
1158 {
1159 Field* fields = result->Fetch();
1160
1161 uint32 achievementId = fields[0].GetUInt32();
1162 std::string_view scriptName = fields[1].GetStringView();
1163
1164 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId);
1165 if (!achievement)
1166 {
1167 TC_LOG_ERROR("sql.sql", "Table `achievement_scripts` contains non-existing Achievement (ID: {}), skipped.", achievementId);
1168 continue;
1169 }
1170 _achievementScripts[achievementId] = sObjectMgr->GetScriptId(scriptName);
1171 }
1172 while (result->NextRow());
1173
1174 TC_LOG_INFO("server.loading", ">> Loaded {} achievement scripts in {} ms", _achievementScripts.size(), GetMSTimeDiffToNow(oldMSTime));
1175}
1176
1178{
1179 uint32 oldMSTime = getMSTime();
1180
1181 // Populate _allCompletedAchievements with all realm first achievement ids to make multithreaded access safer
1182 // while it will not prevent races, it will prevent crashes that happen because std::unordered_map key was added
1183 // instead the only potential race will happen on value associated with the key
1184 for (AchievementEntry const* achievement : sAchievementStore)
1186 _allCompletedAchievements[achievement->ID] = SystemTimePoint::min();
1187
1188 QueryResult result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement");
1189
1190 if (!result)
1191 {
1192 TC_LOG_INFO("server.loading", ">> Loaded 0 realm first completed achievements. DB table `character_achievement` is empty.");
1193 return;
1194 }
1195
1196 do
1197 {
1198 Field* fields = result->Fetch();
1199
1200 uint32 achievementId = fields[0].GetUInt32();
1201 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId);
1202 if (!achievement)
1203 {
1204 // Remove non-existing achievements from all characters
1205 TC_LOG_ERROR("criteria.achievement", "Non-existing achievement {} data has been removed from the table `character_achievement`.", achievementId);
1206
1208 stmt->setUInt32(0, achievementId);
1209 CharacterDatabase.Execute(stmt);
1210
1211 continue;
1212 }
1214 _allCompletedAchievements[achievementId] = SystemTimePoint::max();
1215 }
1216 while (result->NextRow());
1217
1218 TC_LOG_INFO("server.loading", ">> Loaded {} realm first completed achievements in {} ms.", (unsigned long)_allCompletedAchievements.size(), GetMSTimeDiffToNow(oldMSTime));
1219}
1220
1222{
1223 uint32 oldMSTime = getMSTime();
1224
1225 _achievementRewards.clear(); // need for reload case
1226
1227 // 0 1 2 3 4 5 6 7
1228 QueryResult result = WorldDatabase.Query("SELECT ID, TitleA, TitleH, ItemID, Sender, Subject, Body, MailTemplateID FROM achievement_reward");
1229
1230 if (!result)
1231 {
1232 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
1233 return;
1234 }
1235
1236 do
1237 {
1238 Field* fields = result->Fetch();
1239 uint32 id = fields[0].GetUInt32();
1240 AchievementEntry const* achievement = sAchievementStore.LookupEntry(id);
1241 if (!achievement)
1242 {
1243 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` contains a wrong achievement ID ({}), ignored.", id);
1244 continue;
1245 }
1246
1247 AchievementReward reward;
1248 reward.TitleId[0] = fields[1].GetUInt32();
1249 reward.TitleId[1] = fields[2].GetUInt32();
1250 reward.ItemId = fields[3].GetUInt32();
1251 reward.SenderCreatureId = fields[4].GetUInt32();
1252 reward.Subject = fields[5].GetString();
1253 reward.Body = fields[6].GetString();
1254 reward.MailTemplateId = fields[7].GetUInt32();
1255
1256 // must be title or mail at least
1257 if (!reward.TitleId[0] && !reward.TitleId[1] && !reward.SenderCreatureId)
1258 {
1259 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not contain title or item reward data. Ignored.", id);
1260 continue;
1261 }
1262
1263 if (achievement->Faction == ACHIEVEMENT_FACTION_ANY && (!reward.TitleId[0] ^ !reward.TitleId[1]))
1264 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) contains the title (A: {} H: {}) for only one team.", id, reward.TitleId[0], reward.TitleId[1]);
1265
1266 if (reward.TitleId[0])
1267 {
1268 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.TitleId[0]);
1269 if (!titleEntry)
1270 {
1271 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (Entry: {}) contains an invalid title id ({}) in `title_A`, set to 0", id, reward.TitleId[0]);
1272 reward.TitleId[0] = 0;
1273 }
1274 }
1275
1276 if (reward.TitleId[1])
1277 {
1278 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.TitleId[1]);
1279 if (!titleEntry)
1280 {
1281 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (Entry: {}) contains an invalid title id ({}) in `title_H`, set to 0", id, reward.TitleId[1]);
1282 reward.TitleId[1] = 0;
1283 }
1284 }
1285
1286 //check mail data before item for report including wrong item case
1287 if (reward.SenderCreatureId)
1288 {
1289 if (!sObjectMgr->GetCreatureTemplate(reward.SenderCreatureId))
1290 {
1291 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) contains an invalid creature ID {} as sender, mail reward skipped.", id, reward.SenderCreatureId);
1292 reward.SenderCreatureId = 0;
1293 }
1294 }
1295 else
1296 {
1297 if (reward.ItemId)
1298 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but contains an item reward. Item will not be rewarded.", id);
1299
1300 if (!reward.Subject.empty())
1301 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but contains a mail subject.", id);
1302
1303 if (!reward.Body.empty())
1304 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but contains mail text.", id);
1305
1306 if (reward.MailTemplateId)
1307 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but has a MailTemplate.", id);
1308 }
1309
1310 if (reward.MailTemplateId)
1311 {
1312 if (!sMailTemplateStore.LookupEntry(reward.MailTemplateId))
1313 {
1314 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) is using an invalid MailTemplate ({}).", id, reward.MailTemplateId);
1315 reward.MailTemplateId = 0;
1316 }
1317 else if (!reward.Subject.empty() || !reward.Body.empty())
1318 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) is using MailTemplate ({}) and mail subject/text.", id, reward.MailTemplateId);
1319 }
1320
1321 if (reward.ItemId)
1322 {
1323 if (!sObjectMgr->GetItemTemplate(reward.ItemId))
1324 {
1325 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) contains an invalid item id {}, reward mail will not contain the rewarded item.", id, reward.ItemId);
1326 reward.ItemId = 0;
1327 }
1328 }
1329
1330 _achievementRewards[id] = reward;
1331 } while (result->NextRow());
1332
1333 TC_LOG_INFO("server.loading", ">> Loaded {} achievement rewards in {} ms.", uint32(_achievementRewards.size()), GetMSTimeDiffToNow(oldMSTime));
1334}
1335
1337{
1338 uint32 oldMSTime = getMSTime();
1339
1340 _achievementRewardLocales.clear(); // need for reload case
1341
1342 // 0 1 2 3
1343 QueryResult result = WorldDatabase.Query("SELECT ID, Locale, Subject, Body FROM achievement_reward_locale");
1344
1345 if (!result)
1346 {
1347 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement reward locale strings. DB table `achievement_reward_locale` is empty.");
1348 return;
1349 }
1350
1351 do
1352 {
1353 Field* fields = result->Fetch();
1354
1355 uint32 id = fields[0].GetUInt32();
1356 std::string_view localeName = fields[1].GetStringView();
1357
1358 if (_achievementRewards.find(id) == _achievementRewards.end())
1359 {
1360 TC_LOG_ERROR("sql.sql", "Table `achievement_reward_locale` (ID: {}) contains locale strings for a non-existing achievement reward.", id);
1361 continue;
1362 }
1363
1364 LocaleConstant locale = GetLocaleByName(localeName);
1365 if (!IsValidLocale(locale) || locale == LOCALE_enUS)
1366 continue;
1367
1369 ObjectMgr::AddLocaleString(fields[2].GetStringView(), locale, data.Subject);
1370 ObjectMgr::AddLocaleString(fields[3].GetStringView(), locale, data.Body);
1371 } while (result->NextRow());
1372
1373 TC_LOG_INFO("server.loading", ">> Loaded {} achievement reward locale strings in {} ms.", uint32(_achievementRewardLocales.size()), GetMSTimeDiffToNow(oldMSTime));
1374}
1375
1377{
1378 if (uint32 const* scriptId = Trinity::Containers::MapGetValuePtr(_achievementScripts, achievementId))
1379 return *scriptId;
1380 return 0;
1381}
#define sAchievementMgr
@ CHAR_INS_GUILD_ACHIEVEMENT_CRITERIA
@ CHAR_DEL_ALL_GUILD_ACHIEVEMENTS
@ CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA
@ CHAR_INS_CHAR_ACHIEVEMENT_PROGRESS
@ CHAR_INS_CHAR_ACHIEVEMENT
@ CHAR_DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT
@ CHAR_DEL_INVALID_ACHIEVMENT
@ CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS
@ CHAR_INS_GUILD_ACHIEVEMENT
@ CHAR_DEL_GUILD_ACHIEVEMENT
@ CHAR_DEL_GUILD_ACHIEVEMENT_CRITERIA
@ CHAR_DEL_ALL_GUILD_ACHIEVEMENT_CRITERIA
@ CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA
@ CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA_GUILD
@ CHAR_DEL_CHAR_ACHIEVEMENT
LocaleConstant GetLocaleByName(std::string_view name)
Definition Common.cpp:36
LocaleConstant
Definition Common.h:51
@ LOCALE_enUS
Definition Common.h:52
constexpr bool IsValidLocale(LocaleConstant locale)
Definition Common.h:98
@ CRITERIA_FLAG_CU_ACCOUNT
@ CRITERIA_FLAG_CU_PLAYER
#define sCriteriaMgr
std::vector< Criteria const * > CriteriaList
DB2Storage< AchievementEntry > sAchievementStore("Achievement.db2", &AchievementLoadInfo::Instance)
DB2Storage< CharTitlesEntry > sCharTitlesStore("CharTitles.db2", &CharTitlesLoadInfo::Instance)
DB2Storage< MailTemplateEntry > sMailTemplateStore("MailTemplate.db2", &MailTemplateLoadInfo::Instance)
@ ACHIEVEMENT_FACTION_HORDE
Definition DBCEnums.h:71
@ ACHIEVEMENT_FACTION_ALLIANCE
Definition DBCEnums.h:72
@ ACHIEVEMENT_FACTION_ANY
Definition DBCEnums.h:73
@ ACHIEVEMENT_FLAG_TRACKING_FLAG
Definition DBCEnums.h:98
@ ACHIEVEMENT_FLAG_COUNTER
Definition DBCEnums.h:78
@ ACHIEVEMENT_FLAG_ACCOUNT
Definition DBCEnums.h:95
@ ACHIEVEMENT_FLAG_SHOW_GUILD_MEMBERS
Definition DBCEnums.h:93
@ ACHIEVEMENT_FLAG_HIDDEN
Definition DBCEnums.h:79
@ ACHIEVEMENT_FLAG_REALM_FIRST_REACH
Definition DBCEnums.h:86
@ ACHIEVEMENT_FLAG_REALM_FIRST_KILL
Definition DBCEnums.h:87
@ ACHIEVEMENT_FLAG_SHOW_IN_GUILD_HEADER
Definition DBCEnums.h:91
@ ACHIEVEMENT_FLAG_SHOW_IN_GUILD_NEWS
Definition DBCEnums.h:90
@ ACHIEVEMENT_FLAG_SUMM
Definition DBCEnums.h:81
CriteriaType
Definition DBCEnums.h:548
SQLTransaction< CharacterDatabaseConnection > CharacterDatabaseTransaction
std::shared_ptr< ResultSet > QueryResult
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< CharacterDatabaseConnection > CharacterDatabase
Accessor to the character database.
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
int64_t int64
Definition Define.h:149
int32_t int32
Definition Define.h:150
uint64_t uint64
Definition Define.h:153
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition Duration.h:28
std::chrono::minutes Minutes
Minutes shorthand typedef.
Definition Duration.h:32
#define sGuildMgr
Definition GuildMgr.h:76
@ GUILD_NEWS_GUILD_ACHIEVEMENT
Definition Guild.h:240
@ GUILD_NEWS_PLAYER_ACHIEVEMENT
Definition Guild.h:241
@ BROADCAST_TEXT_ACHIEVEMENT_EARNED
Definition Language.h:23
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:173
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:182
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:176
#define TC_LOG_TRACE(filterType__, message__,...)
Definition Log.h:170
@ MAIL_CREATURE
Definition Mail.h:42
#define sObjectMgr
Definition ObjectMgr.h:1873
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
@ AT_LOGIN_FIRST
Definition Player.h:626
Role Based Access Control related classes definition.
#define sScriptMgr
Definition ScriptMgr.h:1449
@ ALLIANCE
@ HORDE
@ CHAT_MSG_GUILD_ACHIEVEMENT
@ CHAT_MSG_ACHIEVEMENT
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition Timer.h:57
uint32 getMSTime()
Definition Timer.h:33
AchievementRewardLocale const * GetAchievementRewardLocale(AchievementEntry const *achievement) const
std::unordered_map< uint32, AchievementRewardLocale > _achievementRewardLocales
std::unordered_map< uint32, uint32 > _achievementScripts
bool IsRealmCompleted(AchievementEntry const *achievement) const
std::unordered_map< uint32, std::vector< AchievementEntry const * > > _achievementListByReferencedId
void SetRealmCompleted(AchievementEntry const *achievement)
std::unordered_map< uint32, AchievementReward > _achievementRewards
static AchievementGlobalMgr * Instance()
AchievementReward const * GetAchievementReward(AchievementEntry const *achievement) const
uint32 GetAchievementScriptId(uint32 achievementId) const
std::vector< AchievementEntry const * > const * GetAchievementByReferencedId(uint32 id) const
std::unordered_map< uint32, SystemTimePoint > _allCompletedAchievements
bool HasAchieved(uint32 achievementId) const
uint32 _achievementPoints
bool RequiredAchievementSatisfied(uint32 achievementId) const override
virtual void CompletedAchievement(AchievementEntry const *entry, Player *referencePlayer)=0
void CompletedCriteriaTree(CriteriaTree const *tree, Player *referencePlayer) override
void CheckAllAchievementCriteria(Player *referencePlayer)
bool IsCompletedAchievement(AchievementEntry const *entry)
std::unordered_map< uint32, CompletedAchievementData > _completedAchievements
uint32 GetAchievementPoints() const
void AfterCriteriaTreeUpdate(CriteriaTree const *tree, Player *referencePlayer) override
bool CanCompleteCriteriaTree(CriteriaTree const *tree) override
bool CanUpdateCriteriaTree(Criteria const *criteria, CriteriaTree const *tree, Player *referencePlayer) const override
std::vector< uint32 > GetCompletedAchievementIds() const
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
bool IsInWorld() const
Definition BaseEntity.h:158
virtual void Reset()
CriteriaProgress * GetCriteriaProgress(Criteria const *entry)
virtual bool CanUpdateCriteriaTree(Criteria const *criteria, CriteriaTree const *tree, Player *referencePlayer) const
CriteriaProgressMap _criteriaProgress
bool IsCompletedCriteriaTree(CriteriaTree const *tree)
void UpdateCriteria(CriteriaType type, uint64 miscValue1=0, uint64 miscValue2=0, uint64 miscValue3=0, WorldObject const *ref=nullptr, Player *referencePlayer=nullptr)
static char const * GetCriteriaTypeString(CriteriaType type)
static void WalkCriteriaTree(CriteriaTree const *tree, Func const &func)
static std::span< CriteriaType const > GetRetroactivelyUpdateableCriteriaTypes()
void ApplyHotfix(uint32 id, void(*fixer)(T *), bool notifyClient=false)
Class used to access individual fields of database query result.
Definition Field.h:94
uint64 GetUInt64() const noexcept
Definition Field.cpp:71
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
std::string_view GetStringView() const noexcept
Definition Field.cpp:118
std::string GetString() const noexcept
Definition Field.cpp:113
int64 GetInt64() const noexcept
Definition Field.cpp:78
Definition Group.h:209
void Reset() override
std::string GetOwnerInfo() const override
void SendCriteriaUpdate(Criteria const *entry, CriteriaProgress const *progress, Seconds timeElapsed, bool timedCompleted) const override
void SendAchievementEarned(AchievementEntry const *achievement) const
void SendAchievementInfo(Player *receiver, uint32 achievementId=0) const
void LoadFromDB(PreparedQueryResult achievementResult, PreparedQueryResult criteriaResult)
GuildAchievementMgr(Guild *owner)
static void DeleteFromDB(ObjectGuid const &guid)
void CompletedAchievement(AchievementEntry const *entry, Player *referencePlayer) override
void SendCriteriaProgressRemoved(uint32 criteriaId) override
CriteriaList const & GetCriteriaByType(CriteriaType type, uint32 asset) const override
void SendAchievementMembers(Player *receiver, uint32 achievementId) const
void SendAllData(Player const *receiver) const override
void SendPacket(WorldPacket const *data) const override
void SendAllTrackedCriterias(Player *receiver, std::set< uint32 > const &trackedCriterias) const
void SaveToDB(CharacterDatabaseTransaction trans)
Definition Guild.h:329
void BroadcastPacket(WorldPacket const *packet) const
Definition Guild.cpp:2766
std::vector< Player * > GetMembersTrackingCriteria(uint32 criteriaId) const
Definition Guild.cpp:2773
ObjectGuid::LowType GetId() const
Definition Guild.h:754
std::string const & GetName() const
Definition Guild.h:757
void BroadcastWorker(Do &&_do, Player const *except=nullptr) const
Definition Guild.h:838
ObjectGuid GetGUID() const
Definition Guild.h:755
Definition Item.h:185
virtual void SaveToDB(CharacterDatabaseTransaction trans)
Definition Item.cpp:639
static Item * CreateItem(uint32 itemEntry, uint32 count, ItemContext context, Player const *player=nullptr, bool addDefaultBonuses=true)
Definition Item.cpp:1721
void SendMailTo(CharacterDatabaseTransaction trans, MailReceiver const &receiver, MailSender const &sender, MailCheckMask checked=MAIL_CHECK_MASK_NONE, uint32 deliver_delay=0)
Definition Mail.cpp:195
MailDraft & AddItem(Item *item)
Definition Mail.cpp:99
LowType GetCounter() const
Definition ObjectGuid.h:336
static ObjectGuid const Empty
Definition ObjectGuid.h:314
std::string ToString() const
uint64 LowType
Definition ObjectGuid.h:321
static void AddLocaleString(std::string_view value, LocaleConstant localeConstant, std::vector< std::string > &data)
static std::string_view GetLocaleString(std::vector< std::string > const &data, LocaleConstant locale)
Definition ObjectMgr.h:1608
bool ModifierTreeSatisfied(uint32 modifierTreeId) const
void SendAllData(Player const *receiver) const override
void SendAchievementEarned(AchievementEntry const *achievement) const
void CompletedAchievement(AchievementEntry const *entry, Player *referencePlayer) override
static void DeleteFromDB(ObjectGuid const &guid)
void SendCriteriaUpdate(Criteria const *entry, CriteriaProgress const *progress, Seconds timeElapsed, bool timedCompleted) const override
void SendAchievementInfo(Player *receiver, uint32 achievementId=0) const
void SaveToDB(CharacterDatabaseTransaction trans)
void LoadFromDB(PreparedQueryResult achievementResult, PreparedQueryResult criteriaResult)
void SendCriteriaProgressRemoved(uint32 criteriaId) override
PlayerAchievementMgr(Player *owner)
void SendPacket(WorldPacket const *data) const override
CriteriaList const & GetCriteriaByType(CriteriaType type, uint32 asset) const override
std::string GetOwnerInfo() const override
UF::UpdateField< UF::PlayerData, int32(WowCS::EntityFragment::CGObject), TYPEID_PLAYER > m_playerData
Definition Player.h:3047
static Team TeamForRace(uint8 race)
Definition Player.cpp:6371
Gender GetNativeGender() const override
Definition Player.h:1334
void SendDirectMessage(WorldPacket const *data) const
Definition Player.cpp:6193
Guild * GetGuild()
Definition Player.cpp:30428
WorldSession * GetSession() const
Definition Player.h:2256
void UpdateCriteria(CriteriaType type, uint64 miscValue1=0, uint64 miscValue2=0, uint64 miscValue3=0, WorldObject *ref=nullptr)
Definition Player.cpp:27532
ObjectGuid::LowType GetGuildId() const
Definition Player.h:2147
bool IsGameMaster() const
Definition Player.h:1293
void SetTitle(CharTitlesEntry const *title, bool lost=false)
Definition Player.cpp:27021
Group * GetGroup(Optional< uint8 > partyIndex)
Definition Player.h:2781
Team GetTeam() const
Definition Player.h:2406
void setString(uint8 index, std::string &&value)
void setUInt32(uint8 index, uint32 value)
void setInt64(uint8 index, int64 value)
void setUInt64(uint8 index, uint64 value)
uint8 GetRace() const
Definition Unit.h:773
std::string const & GetName() const
Definition Object.h:342
std::vector< GuildAchievementMember > Member
std::vector< GuildCriteriaProgress > Progress
ObjectGuid GetBattlenetAccountGUID() const
LocaleConstant GetSessionDbLocaleIndex() const
Minutes GetTimezoneOffset() const
bool PlayerLoading() const
bool HasPermission(uint32 permissionId)
void SetUtcTimeFromUnixTime(std::time_t unixTime)
Definition WowTime.cpp:86
#define sWorld
Definition World.h:919
@ CONFIG_LISTEN_RANGE_SAY
Definition World.h:210
WowTime const * GetUtcWowTime()
Definition GameTime.cpp:101
SystemTimePoint GetSystemTime()
Current chrono system_clock time point.
Definition GameTime.cpp:62
time_t GetGameTime()
Definition GameTime.cpp:52
constexpr auto MapKey
Definition MapUtils.h:72
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition Util.cpp:57
OutputIt StringFormatTo(OutputIt out, FormatString< Args... > fmt, Args &&... args) noexcept
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
@ RBAC_PERM_CANNOT_EARN_REALM_FIRST_ACHIEVEMENTS
Definition RBAC.h:62
@ RBAC_PERM_CANNOT_EARN_ACHIEVEMENTS
Definition RBAC.h:61
std::vector< std::string > Body
std::vector< std::string > Subject
std::string Subject
static void VisitWorldObjects(WorldObject const *obj, T &visitor, float radius)
Definition CellImpl.h:150
ObjectGuid PlayerGUID
::Criteria const * Criteria
CriteriaTreeEntry const * Entry
AchievementEntry const * Achievement
CriteriaEntry const * Entry
uint32 FlagsCu
AchievementEntry const * operator()(std::pair< uint32 const, CompletedAchievementData > const &val)
std::vector< CriteriaProgress > Progress
std::vector< EarnedAchievement > Earned