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 receiver->SendDirectMessage(achievementEarned.Write());
668 };
669
670 achievementEarnedBuilder(_owner);
671
672 if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
673 {
674 float dist = sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY);
675 Trinity::MessageDistDeliverer notifier(_owner, achievementEarnedBuilder, dist);
676 Cell::VisitWorldObjects(_owner, notifier, dist);
677 }
678}
679
681{
683}
684
686{
687 return sCriteriaMgr->GetPlayerCriteriaByType(type, asset);
688}
689
691{
692}
693
695{
697
698 ObjectGuid guid = _owner->GetGUID();
699 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
700 {
701 _owner->BroadcastWorker([&](Player const* receiver)
702 {
704 guildAchievementDeleted.AchievementID = completedAchievement.first;
705 guildAchievementDeleted.GuildGUID = guid;
706 guildAchievementDeleted.TimeDeleted = *GameTime::GetUtcWowTime();
707 guildAchievementDeleted.TimeDeleted += receiver->GetSession()->GetTimezoneOffset();
708 receiver->SendDirectMessage(guildAchievementDeleted.Write());
709 });
710 }
711
714 DeleteFromDB(guid);
715}
716
718{
719 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
720
722 stmt->setUInt64(0, guid.GetCounter());
723 trans->Append(stmt);
724
726 stmt->setUInt64(0, guid.GetCounter());
727 trans->Append(stmt);
728
729 CharacterDatabase.CommitTransaction(trans);
730}
731
733{
734 if (achievementResult)
735 {
736 do
737 {
738 Field* fields = achievementResult->Fetch();
739 uint32 achievementid = fields[0].GetUInt32();
740
741 // must not happen: cleanup at server startup in sAchievementMgr->LoadCompletedAchievements()
742 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementid);
743 if (!achievement)
744 continue;
745
747 ca.Date = fields[1].GetInt64();
748 for (std::string_view guid : Trinity::Tokenize(fields[2].GetStringView(), ',', false))
749 if (Optional<ObjectGuid::LowType> parsedGuid = Trinity::StringTo<ObjectGuid::LowType>(guid))
750 ca.CompletingPlayers.insert(ObjectGuid::Create<HighGuid::Player>(*parsedGuid));
751
752 ca.Changed = false;
753
754 _achievementPoints += achievement->Points;
755 } while (achievementResult->NextRow());
756 }
757
758 if (criteriaResult)
759 {
760 time_t now = GameTime::GetGameTime();
761 do
762 {
763 Field* fields = criteriaResult->Fetch();
764 uint32 id = fields[0].GetUInt32();
765 uint64 counter = fields[1].GetUInt64();
766 time_t date = fields[2].GetInt64();
767 ObjectGuid::LowType guid = fields[3].GetUInt64();
768
769 Criteria const* criteria = sCriteriaMgr->GetCriteria(id);
770 if (!criteria)
771 {
772 // we will remove not existed criteria for all guilds
773 TC_LOG_ERROR("criteria.achievement", "Non-existing achievement criteria {} data removed from table `guild_achievement_progress`.", id);
774
776 stmt->setUInt32(0, id);
777 CharacterDatabase.Execute(stmt);
778 continue;
779 }
780
781 if (criteria->Entry->StartTimer && time_t(date + criteria->Entry->StartTimer) < now)
782 continue;
783
784 CriteriaProgress& progress = _criteriaProgress[id];
785 progress.Counter = counter;
786 progress.Date = date;
787 progress.PlayerGUID = ObjectGuid::Create<HighGuid::Player>(guid);
788 progress.Changed = false;
789 } while (criteriaResult->NextRow());
790 }
791}
792
794{
796 for (std::pair<uint32 const, CompletedAchievementData>& completedAchievement : _completedAchievements)
797 {
798 if (!completedAchievement.second.Changed)
799 continue;
800
801 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_ACHIEVEMENT);
802 stmt->setUInt64(0, _owner->GetId());
803 stmt->setUInt32(1, completedAchievement.first);
804 trans->Append(stmt);
805
806 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_ACHIEVEMENT);
807 stmt->setUInt64(0, _owner->GetId());
808 stmt->setUInt32(1, completedAchievement.first);
809 stmt->setInt64(2, completedAchievement.second.Date);
810 std::string guidstr;
811 auto completersItr = completedAchievement.second.CompletingPlayers.begin();
812 auto completersEnd = completedAchievement.second.CompletingPlayers.end();
813 if (completersItr != completersEnd)
814 {
815 Trinity::StringFormatTo(std::back_inserter(guidstr), "{}", completersItr->GetCounter());
816 while (++completersItr != completersEnd)
817 Trinity::StringFormatTo(std::back_inserter(guidstr), ",{}", completersItr->GetCounter());
818 }
819
820 stmt->setString(3, std::move(guidstr));
821 trans->Append(stmt);
822
823 completedAchievement.second.Changed = false;
824 }
825
826 for (std::pair<uint32 const, CriteriaProgress>& criteriaProgres : _criteriaProgress)
827 {
828 if (!criteriaProgres.second.Changed)
829 continue;
830
831 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_ACHIEVEMENT_CRITERIA);
832 stmt->setUInt64(0, _owner->GetId());
833 stmt->setUInt32(1, criteriaProgres.first);
834 trans->Append(stmt);
835
836 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_ACHIEVEMENT_CRITERIA);
837 stmt->setUInt64(0, _owner->GetId());
838 stmt->setUInt32(1, criteriaProgres.first);
839 stmt->setUInt64(2, criteriaProgres.second.Counter);
840 stmt->setInt64(3, criteriaProgres.second.Date);
841 stmt->setUInt64(4, criteriaProgres.second.PlayerGUID.GetCounter());
842 trans->Append(stmt);
843
844 criteriaProgres.second.Changed = false;
845 }
846}
847
848void GuildAchievementMgr::SendAllData(Player const* receiver) const
849{
850 VisibleAchievementCheck filterInvisible;
852 allGuildAchievements.Earned.reserve(_completedAchievements.size());
853
854 for (std::pair<uint32 const, CompletedAchievementData> const& completedAchievement : _completedAchievements)
855 {
856 AchievementEntry const* achievement = filterInvisible(completedAchievement);
857 if (!achievement)
858 continue;
859
860 WorldPackets::Achievement::EarnedAchievement& earned = allGuildAchievements.Earned.emplace_back();
861 earned.Id = completedAchievement.first;
862 earned.Date.SetUtcTimeFromUnixTime(completedAchievement.second.Date);
863 earned.Date += receiver->GetSession()->GetTimezoneOffset();
864 }
865
866 receiver->SendDirectMessage(allGuildAchievements.Write());
867}
868
869void GuildAchievementMgr::SendAchievementInfo(Player* receiver, uint32 achievementId /*= 0*/) const
870{
872 if (AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId))
873 {
874 if (CriteriaTree const* tree = sCriteriaMgr->GetCriteriaTree(achievement->CriteriaTree))
875 {
876 CriteriaMgr::WalkCriteriaTree(tree, [this, &guildCriteriaUpdate, receiver](CriteriaTree const* node)
877 {
878 if (node->Criteria)
879 {
880 auto progress = this->_criteriaProgress.find(node->Criteria->ID);
881 if (progress != this->_criteriaProgress.end())
882 {
883 WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress.emplace_back();
884 guildCriteriaProgress.CriteriaID = node->Criteria->ID;
885 guildCriteriaProgress.DateCreated = 0;
886 guildCriteriaProgress.DateStarted = 0;
887 guildCriteriaProgress.DateUpdated.SetUtcTimeFromUnixTime(progress->second.Date);
888 guildCriteriaProgress.DateUpdated += receiver->GetSession()->GetTimezoneOffset();
889 guildCriteriaProgress.Quantity = progress->second.Counter;
890 guildCriteriaProgress.PlayerGUID = progress->second.PlayerGUID;
891 guildCriteriaProgress.Flags = 0;
892 }
893 }
894 });
895 }
896 }
897
898 receiver->SendDirectMessage(guildCriteriaUpdate.Write());
899}
900
901void GuildAchievementMgr::SendAllTrackedCriterias(Player* receiver, std::set<uint32> const& trackedCriterias) const
902{
904 guildCriteriaUpdate.Progress.reserve(trackedCriterias.size());
905
906 for (uint32 criteriaId : trackedCriterias)
907 {
908 auto progress = _criteriaProgress.find(criteriaId);
909 if (progress == _criteriaProgress.end())
910 continue;
911
912 WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress.emplace_back();
913 guildCriteriaProgress.CriteriaID = criteriaId;
914 guildCriteriaProgress.DateCreated = 0;
915 guildCriteriaProgress.DateStarted = 0;
916 guildCriteriaProgress.DateUpdated.SetUtcTimeFromUnixTime(progress->second.Date);
917 guildCriteriaProgress.DateUpdated += receiver->GetSession()->GetTimezoneOffset();
918 guildCriteriaProgress.Quantity = progress->second.Counter;
919 guildCriteriaProgress.PlayerGUID = progress->second.PlayerGUID;
920 guildCriteriaProgress.Flags = 0;
921 }
922
923 receiver->SendDirectMessage(guildCriteriaUpdate.Write());
924}
925
927{
928 auto itr = _completedAchievements.find(achievementId);
929 if (itr != _completedAchievements.end())
930 {
932 guildAchievementMembers.GuildGUID = _owner->GetGUID();
933 guildAchievementMembers.AchievementID = achievementId;
934 guildAchievementMembers.Member.reserve(itr->second.CompletingPlayers.size());
935 for (ObjectGuid const& member : itr->second.CompletingPlayers)
936 guildAchievementMembers.Member.emplace_back(member);
937
938 receiver->SendDirectMessage(guildAchievementMembers.Write());
939 }
940}
941
942void GuildAchievementMgr::CompletedAchievement(AchievementEntry const* achievement, Player* referencePlayer)
943{
944 TC_LOG_DEBUG("criteria.achievement", "GuildAchievementMgr::CompletedAchievement({})", achievement->ID);
945
946 if (achievement->Flags & ACHIEVEMENT_FLAG_COUNTER || HasAchieved(achievement->ID))
947 return;
948
950 if (Guild* guild = referencePlayer->GetGuild())
951 guild->AddGuildNews(GUILD_NEWS_GUILD_ACHIEVEMENT, ObjectGuid::Empty, achievement->Flags & ACHIEVEMENT_FLAG_SHOW_IN_GUILD_HEADER, achievement->ID);
952
953 SendAchievementEarned(achievement);
956 ca.Changed = true;
957
959 {
960 if (referencePlayer->GetGuildId() == _owner->GetId())
961 ca.CompletingPlayers.insert(referencePlayer->GetGUID());
962
963 if (Group const* group = referencePlayer->GetGroup())
964 for (GroupReference const& ref : group->GetMembers())
965 if (ref.GetSource()->GetGuildId() == _owner->GetId())
966 ca.CompletingPlayers.insert(ref.GetSource()->GetGUID());
967 }
968
970 sAchievementMgr->SetRealmCompleted(achievement);
971
972 if (!(achievement->Flags & ACHIEVEMENT_FLAG_TRACKING_FLAG))
973 _achievementPoints += achievement->Points;
974
975 UpdateCriteria(CriteriaType::EarnAchievement, achievement->ID, 0, 0, nullptr, referencePlayer);
976 UpdateCriteria(CriteriaType::EarnAchievementPoints, achievement->Points, 0, 0, nullptr, referencePlayer);
977
978 sScriptMgr->OnAchievementCompleted(referencePlayer, achievement);
979}
980
981void GuildAchievementMgr::SendCriteriaUpdate(Criteria const* entry, CriteriaProgress const* progress, Seconds /*timeElapsed*/, bool /*timedCompleted*/) const
982{
983 for (Player const* member : _owner->GetMembersTrackingCriteria(entry->ID))
984 {
986 WorldPackets::Achievement::GuildCriteriaProgress& guildCriteriaProgress = guildCriteriaUpdate.Progress.emplace_back();
987 guildCriteriaProgress.CriteriaID = entry->ID;
988 guildCriteriaProgress.DateCreated = 0;
989 guildCriteriaProgress.DateStarted = 0;
990 guildCriteriaProgress.DateUpdated.SetUtcTimeFromUnixTime(progress->Date);
991 guildCriteriaProgress.DateUpdated += member->GetSession()->GetTimezoneOffset();
992 guildCriteriaProgress.Quantity = progress->Counter;
993 guildCriteriaProgress.PlayerGUID = progress->PlayerGUID;
994 guildCriteriaProgress.Flags = 0;
995
996 member->SendDirectMessage(guildCriteriaUpdate.Write());
997 }
998}
999
1001{
1003 guildCriteriaDeleted.GuildGUID = _owner->GetGUID();
1004 guildCriteriaDeleted.CriteriaID = criteriaId;
1005 SendPacket(guildCriteriaDeleted.Write());
1006}
1007
1009{
1011 {
1012 // broadcast realm first reached
1014 serverFirstAchievement.Name = _owner->GetName();
1015 serverFirstAchievement.PlayerGUID = _owner->GetGUID();
1016 serverFirstAchievement.AchievementID = achievement->ID;
1017 serverFirstAchievement.GuildAchievement = true;
1018 sWorld->SendGlobalMessage(serverFirstAchievement.Write());
1019 }
1020
1021 _owner->BroadcastWorker([&](Player const* receiver)
1022 {
1024 guildAchievementEarned.AchievementID = achievement->ID;
1025 guildAchievementEarned.GuildGUID = _owner->GetGUID();
1026 guildAchievementEarned.TimeEarned = *GameTime::GetUtcWowTime();
1027 guildAchievementEarned.TimeEarned += receiver->GetSession()->GetTimezoneOffset();
1028 receiver->SendDirectMessage(guildAchievementEarned.Write());
1029 });
1030}
1031
1033{
1034 _owner->BroadcastPacket(data);
1035}
1036
1038{
1039 return sCriteriaMgr->GetGuildCriteriaByType(type);
1040}
1041
1044
1046{
1047 return Trinity::StringFormat("{} {}", _owner->GetGUID().ToString(), _owner->GetName());
1048}
1049
1051{
1052 return Trinity::StringFormat("Guild ID {} {}", _owner->GetId(), _owner->GetName());
1053}
1054
1056{
1057 static AchievementGlobalMgr instance;
1058 return &instance;
1059}
1060
1061std::vector<AchievementEntry const*> const* AchievementGlobalMgr::GetAchievementByReferencedId(uint32 id) const
1062{
1063 auto itr = _achievementListByReferencedId.find(id);
1064 return itr != _achievementListByReferencedId.end() ? &itr->second : nullptr;
1065}
1066
1068{
1069 auto iter = _achievementRewards.find(achievement->ID);
1070 return iter != _achievementRewards.end() ? &iter->second : nullptr;
1071}
1072
1074{
1075 auto iter = _achievementRewardLocales.find(achievement->ID);
1076 return iter != _achievementRewardLocales.end() ? &iter->second : nullptr;
1077}
1078
1080{
1081 auto itr = _allCompletedAchievements.find(achievement->ID);
1082 if (itr == _allCompletedAchievements.end())
1083 return false;
1084
1085 if (itr->second == SystemTimePoint ::min())
1086 return false;
1087
1088 if (itr->second == SystemTimePoint::max())
1089 return true;
1090
1091 // Allow completing the realm first kill for entire minute after first person did it
1092 // it may allow more than one group to achieve it (highly unlikely)
1093 // but apparently this is how blizz handles it as well
1094 if (achievement->Flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL)
1095 return (GameTime::GetSystemTime() - itr->second) > Minutes(1);
1096
1097 return true;
1098}
1099
1101{
1102 if (IsRealmCompleted(achievement))
1103 return;
1104
1106}
1107
1108//==========================================================
1110{
1111 uint32 oldMSTime = getMSTime();
1112
1113 if (sAchievementStore.GetNumRows() == 0)
1114 {
1115 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement references.");
1116 return;
1117 }
1118
1119 uint32 count = 0;
1120
1121 for (uint32 entryId = 0; entryId < sAchievementStore.GetNumRows(); ++entryId)
1122 {
1123 AchievementEntry const* achievement = sAchievementStore.LookupEntry(entryId);
1124 if (!achievement || !achievement->SharesCriteria)
1125 continue;
1126
1127 _achievementListByReferencedId[achievement->SharesCriteria].push_back(achievement);
1128 ++count;
1129 }
1130
1132
1133 // Once Bitten, Twice Shy (10 player) - Icecrown Citadel
1134 // Correct map requirement (currently has Ulduar); 6.0.3 note - it STILL has ulduar requirement
1135 hotfixes.ApplyHotfix(4539, [](AchievementEntry* achievement)
1136 {
1137 achievement->InstanceID = 631;
1138 });
1139
1140 TC_LOG_INFO("server.loading", ">> Loaded {} achievement references in {} ms.", count, GetMSTimeDiffToNow(oldMSTime));
1141}
1142
1144{
1145 uint32 oldMSTime = getMSTime();
1146
1147 _achievementScripts.clear(); // need for reload case
1148
1149 QueryResult result = WorldDatabase.Query("SELECT AchievementId, ScriptName FROM achievement_scripts");
1150 if (!result)
1151 {
1152 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement scripts. DB table `achievement_scripts` is empty.");
1153 return;
1154 }
1155
1156 do
1157 {
1158 Field* fields = result->Fetch();
1159
1160 uint32 achievementId = fields[0].GetUInt32();
1161 std::string_view scriptName = fields[1].GetStringView();
1162
1163 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId);
1164 if (!achievement)
1165 {
1166 TC_LOG_ERROR("sql.sql", "Table `achievement_scripts` contains non-existing Achievement (ID: {}), skipped.", achievementId);
1167 continue;
1168 }
1169 _achievementScripts[achievementId] = sObjectMgr->GetScriptId(scriptName);
1170 }
1171 while (result->NextRow());
1172
1173 TC_LOG_INFO("server.loading", ">> Loaded {} achievement scripts in {} ms", _achievementScripts.size(), GetMSTimeDiffToNow(oldMSTime));
1174}
1175
1177{
1178 uint32 oldMSTime = getMSTime();
1179
1180 // Populate _allCompletedAchievements with all realm first achievement ids to make multithreaded access safer
1181 // while it will not prevent races, it will prevent crashes that happen because std::unordered_map key was added
1182 // instead the only potential race will happen on value associated with the key
1183 for (AchievementEntry const* achievement : sAchievementStore)
1185 _allCompletedAchievements[achievement->ID] = SystemTimePoint::min();
1186
1187 QueryResult result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement");
1188
1189 if (!result)
1190 {
1191 TC_LOG_INFO("server.loading", ">> Loaded 0 realm first completed achievements. DB table `character_achievement` is empty.");
1192 return;
1193 }
1194
1195 do
1196 {
1197 Field* fields = result->Fetch();
1198
1199 uint32 achievementId = fields[0].GetUInt32();
1200 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementId);
1201 if (!achievement)
1202 {
1203 // Remove non-existing achievements from all characters
1204 TC_LOG_ERROR("criteria.achievement", "Non-existing achievement {} data has been removed from the table `character_achievement`.", achievementId);
1205
1207 stmt->setUInt32(0, achievementId);
1208 CharacterDatabase.Execute(stmt);
1209
1210 continue;
1211 }
1213 _allCompletedAchievements[achievementId] = SystemTimePoint::max();
1214 }
1215 while (result->NextRow());
1216
1217 TC_LOG_INFO("server.loading", ">> Loaded {} realm first completed achievements in {} ms.", (unsigned long)_allCompletedAchievements.size(), GetMSTimeDiffToNow(oldMSTime));
1218}
1219
1221{
1222 uint32 oldMSTime = getMSTime();
1223
1224 _achievementRewards.clear(); // need for reload case
1225
1226 // 0 1 2 3 4 5 6 7
1227 QueryResult result = WorldDatabase.Query("SELECT ID, TitleA, TitleH, ItemID, Sender, Subject, Body, MailTemplateID FROM achievement_reward");
1228
1229 if (!result)
1230 {
1231 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
1232 return;
1233 }
1234
1235 do
1236 {
1237 Field* fields = result->Fetch();
1238 uint32 id = fields[0].GetUInt32();
1239 AchievementEntry const* achievement = sAchievementStore.LookupEntry(id);
1240 if (!achievement)
1241 {
1242 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` contains a wrong achievement ID ({}), ignored.", id);
1243 continue;
1244 }
1245
1246 AchievementReward reward;
1247 reward.TitleId[0] = fields[1].GetUInt32();
1248 reward.TitleId[1] = fields[2].GetUInt32();
1249 reward.ItemId = fields[3].GetUInt32();
1250 reward.SenderCreatureId = fields[4].GetUInt32();
1251 reward.Subject = fields[5].GetString();
1252 reward.Body = fields[6].GetString();
1253 reward.MailTemplateId = fields[7].GetUInt32();
1254
1255 // must be title or mail at least
1256 if (!reward.TitleId[0] && !reward.TitleId[1] && !reward.SenderCreatureId)
1257 {
1258 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not contain title or item reward data. Ignored.", id);
1259 continue;
1260 }
1261
1262 if (achievement->Faction == ACHIEVEMENT_FACTION_ANY && (!reward.TitleId[0] ^ !reward.TitleId[1]))
1263 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]);
1264
1265 if (reward.TitleId[0])
1266 {
1267 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.TitleId[0]);
1268 if (!titleEntry)
1269 {
1270 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (Entry: {}) contains an invalid title id ({}) in `title_A`, set to 0", id, reward.TitleId[0]);
1271 reward.TitleId[0] = 0;
1272 }
1273 }
1274
1275 if (reward.TitleId[1])
1276 {
1277 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.TitleId[1]);
1278 if (!titleEntry)
1279 {
1280 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (Entry: {}) contains an invalid title id ({}) in `title_H`, set to 0", id, reward.TitleId[1]);
1281 reward.TitleId[1] = 0;
1282 }
1283 }
1284
1285 //check mail data before item for report including wrong item case
1286 if (reward.SenderCreatureId)
1287 {
1288 if (!sObjectMgr->GetCreatureTemplate(reward.SenderCreatureId))
1289 {
1290 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) contains an invalid creature ID {} as sender, mail reward skipped.", id, reward.SenderCreatureId);
1291 reward.SenderCreatureId = 0;
1292 }
1293 }
1294 else
1295 {
1296 if (reward.ItemId)
1297 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);
1298
1299 if (!reward.Subject.empty())
1300 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but contains a mail subject.", id);
1301
1302 if (!reward.Body.empty())
1303 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but contains mail text.", id);
1304
1305 if (reward.MailTemplateId)
1306 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) does not have sender data, but has a MailTemplate.", id);
1307 }
1308
1309 if (reward.MailTemplateId)
1310 {
1311 if (!sMailTemplateStore.LookupEntry(reward.MailTemplateId))
1312 {
1313 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) is using an invalid MailTemplate ({}).", id, reward.MailTemplateId);
1314 reward.MailTemplateId = 0;
1315 }
1316 else if (!reward.Subject.empty() || !reward.Body.empty())
1317 TC_LOG_ERROR("sql.sql", "Table `achievement_reward` (ID: {}) is using MailTemplate ({}) and mail subject/text.", id, reward.MailTemplateId);
1318 }
1319
1320 if (reward.ItemId)
1321 {
1322 if (!sObjectMgr->GetItemTemplate(reward.ItemId))
1323 {
1324 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);
1325 reward.ItemId = 0;
1326 }
1327 }
1328
1329 _achievementRewards[id] = reward;
1330 } while (result->NextRow());
1331
1332 TC_LOG_INFO("server.loading", ">> Loaded {} achievement rewards in {} ms.", uint32(_achievementRewards.size()), GetMSTimeDiffToNow(oldMSTime));
1333}
1334
1336{
1337 uint32 oldMSTime = getMSTime();
1338
1339 _achievementRewardLocales.clear(); // need for reload case
1340
1341 // 0 1 2 3
1342 QueryResult result = WorldDatabase.Query("SELECT ID, Locale, Subject, Body FROM achievement_reward_locale");
1343
1344 if (!result)
1345 {
1346 TC_LOG_INFO("server.loading", ">> Loaded 0 achievement reward locale strings. DB table `achievement_reward_locale` is empty.");
1347 return;
1348 }
1349
1350 do
1351 {
1352 Field* fields = result->Fetch();
1353
1354 uint32 id = fields[0].GetUInt32();
1355 std::string_view localeName = fields[1].GetStringView();
1356
1357 if (_achievementRewards.find(id) == _achievementRewards.end())
1358 {
1359 TC_LOG_ERROR("sql.sql", "Table `achievement_reward_locale` (ID: {}) contains locale strings for a non-existing achievement reward.", id);
1360 continue;
1361 }
1362
1363 LocaleConstant locale = GetLocaleByName(localeName);
1364 if (!IsValidLocale(locale) || locale == LOCALE_enUS)
1365 continue;
1366
1368 ObjectMgr::AddLocaleString(fields[2].GetStringView(), locale, data.Subject);
1369 ObjectMgr::AddLocaleString(fields[3].GetStringView(), locale, data.Body);
1370 } while (result->NextRow());
1371
1372 TC_LOG_INFO("server.loading", ">> Loaded {} achievement reward locale strings in {} ms.", uint32(_achievementRewardLocales.size()), GetMSTimeDiffToNow(oldMSTime));
1373}
1374
1376{
1377 if (uint32 const* scriptId = Trinity::Containers::MapGetValuePtr(_achievementScripts, achievementId))
1378 return *scriptId;
1379 return 0;
1380}
#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:546
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:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
#define TC_LOG_TRACE(filterType__, message__,...)
Definition Log.h:178
@ MAIL_CREATURE
Definition Mail.h:41
#define sObjectMgr
Definition ObjectMgr.h:1885
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
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:205
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:179
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:1611
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:3061
static Team TeamForRace(uint8 race)
Definition Player.cpp:6461
Gender GetNativeGender() const override
Definition Player.h:1350
void SendDirectMessage(WorldPacket const *data) const
Definition Player.cpp:6283
Guild * GetGuild()
Definition Player.cpp:30511
WorldSession * GetSession() const
Definition Player.h:2272
void UpdateCriteria(CriteriaType type, uint64 miscValue1=0, uint64 miscValue2=0, uint64 miscValue3=0, WorldObject *ref=nullptr)
Definition Player.cpp:27588
ObjectGuid::LowType GetGuildId() const
Definition Player.h:2164
bool IsGameMaster() const
Definition Player.h:1309
void SetTitle(CharTitlesEntry const *title, bool lost=false)
Definition Player.cpp:27067
Group * GetGroup(Optional< uint8 > partyIndex)
Definition Player.h:2796
Team GetTeam() const
Definition Player.h:2423
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:761
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:916
@ CONFIG_LISTEN_RANGE_SAY
Definition World.h:209
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, bool dont_load=true)
Definition CellImpl.h:191
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