[7161] Implemented configurable option to allow/disallow achievements gain for GMs.
[getmangos.git] / src / game / AchievementMgr.cpp
blob0561daf1acca262b73b30f9a236d0abe05d74734
1 /*
2 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "AchievementMgr.h"
20 #include "Common.h"
21 #include "Player.h"
22 #include "WorldPacket.h"
23 #include "Database/DBCEnums.h"
24 #include "ObjectMgr.h"
25 #include "Guild.h"
26 #include "Database/DatabaseEnv.h"
27 #include "GameEvent.h"
28 #include "World.h"
29 #include "SpellMgr.h"
30 #include "ProgressBar.h"
32 #include "Policies/SingletonImp.h"
34 INSTANTIATE_SINGLETON_1(AchievementGlobalMgr);
36 const CriteriaCastSpellRequirement AchievementGlobalMgr::m_criteriaCastSpellRequirements[CRITERIA_CAST_SPELL_REQ_COUNT] =
38 {5272, 3057, 0, 0},
39 {5273, 2784, 0, 0},
40 {5752, 9099, 0, 0},
41 {5753, 8403, 0, 0},
42 {5772, 0, 0, RACE_GNOME},
43 {5774, 0, 0, RACE_BLOODELF},
44 {5775, 0, 0, RACE_DRAENEI},
45 {5776, 0, 0, RACE_DWARF},
46 {5777, 0, 0, RACE_HUMAN},
47 {5778, 0, 0, RACE_NIGHTELF},
48 {5779, 0, 0, RACE_ORC},
49 {5780, 0, 0, RACE_TAUREN},
50 {5781, 0, 0, RACE_TROLL},
51 {5782, 0, 0, RACE_UNDEAD_PLAYER},
52 {6225, 5661, 0, 0},
53 {6226, 26044, 0, 0},
54 {6228, 739, 0, 0},
55 {6229, 927, 0, 0},
56 {6230, 1444, 0, 0},
57 {6231, 8140, 0, 0},
58 {6232, 5489, 0, 0},
59 {6233,12336, 0, 0},
60 {6234, 1351, 0, 0},
61 {6235, 5484, 0, 0},
62 {6236, 1182, 0, 0},
63 {6237, 0, CLASS_DEATH_KNIGHT, RACE_ORC},
64 {6238, 0, CLASS_WARRIOR, RACE_HUMAN},
65 {6239, 0, CLASS_SHAMAN, RACE_TAUREN},
66 {6240, 0, CLASS_DRUID, RACE_NIGHTELF},
67 {6241, 0, CLASS_ROGUE, RACE_UNDEAD_PLAYER},
68 {6242, 0, CLASS_HUNTER, RACE_TROLL},
69 {6243, 0, CLASS_MAGE, RACE_GNOME},
70 {6244, 0, CLASS_PALADIN, RACE_DWARF},
71 {6245, 0, CLASS_WARLOCK, RACE_BLOODELF},
72 {6246, 0, CLASS_PRIEST, RACE_DRAENEI},
73 {6312, 0, CLASS_WARLOCK, RACE_GNOME},
74 {6313, 0, CLASS_DEATH_KNIGHT, RACE_HUMAN},
75 {6314, 0, CLASS_PRIEST, RACE_NIGHTELF},
76 {6315, 0, CLASS_SHAMAN, RACE_ORC},
77 {6316, 0, CLASS_DRUID, RACE_TAUREN},
78 {6317, 0, CLASS_ROGUE, RACE_TROLL},
79 {6318, 0, CLASS_WARRIOR, RACE_UNDEAD_PLAYER},
80 {6319, 0, CLASS_MAGE, RACE_BLOODELF},
81 {6320, 0, CLASS_PALADIN, RACE_DRAENEI},
82 {6321, 0, CLASS_HUNTER, RACE_DWARF},
83 {6662, 31261, 0, 0}
86 AchievementMgr::AchievementMgr(Player *player)
88 m_player = player;
91 AchievementMgr::~AchievementMgr()
95 void AchievementMgr::SaveToDB()
97 if(!m_completedAchievements.empty())
99 bool need_execute = false;
100 std::ostringstream ssdel;
101 std::ostringstream ssins;
102 for(CompletedAchievementMap::iterator iter = m_completedAchievements.begin(); iter!=m_completedAchievements.end(); iter++)
104 if(!iter->second.changed)
105 continue;
107 /// first new/changed record prefix
108 if(!need_execute)
110 ssdel << "DELETE FROM character_achievement WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND achievement IN (";
111 ssins << "INSERT INTO character_achievement (guid, achievement, date) VALUES ";
112 need_execute = true;
114 /// next new/changed record prefix
115 else
117 ssdel << ", ";
118 ssins << ", ";
121 // new/changed record data
122 ssdel << iter->first;
123 ssins << "("<<GetPlayer()->GetGUIDLow() << ", " << iter->first << ", " << uint64(iter->second.date) << ")";
125 /// mark as saved in db
126 iter->second.changed = false;
129 if(need_execute)
130 ssdel << ")";
132 if(need_execute)
134 CharacterDatabase.BeginTransaction ();
135 CharacterDatabase.Execute( ssdel.str().c_str() );
136 CharacterDatabase.Execute( ssins.str().c_str() );
137 CharacterDatabase.CommitTransaction ();
141 if(!m_criteriaProgress.empty())
143 /// prepare deleting and insert
144 bool need_execute_del = false;
145 bool need_execute_ins = false;
146 std::ostringstream ssdel;
147 std::ostringstream ssins;
148 for(CriteriaProgressMap::iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter)
150 if(!iter->second.changed)
151 continue;
153 // deleted data (including 0 progress state)
155 /// first new/changed record prefix (for any counter value)
156 if(!need_execute_del)
158 ssdel << "DELETE FROM character_achievement_progress WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND criteria IN (";
159 need_execute_del = true;
161 /// next new/changed record prefix
162 else
163 ssdel << ", ";
165 // new/changed record data
166 ssdel << iter->first;
169 // store data only for real progress
170 if(iter->second.counter != 0)
172 /// first new/changed record prefix
173 if(!need_execute_ins)
175 ssins << "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES ";
176 need_execute_ins = true;
178 /// next new/changed record prefix
179 else
180 ssins << ", ";
182 // new/changed record data
183 ssins << "(" << GetPlayer()->GetGUIDLow() << ", " << iter->first << ", " << iter->second.counter << ", " << iter->second.date << ")";
186 /// mark as updated in db
187 iter->second.changed = false;
190 if(need_execute_del) // DELETE ... IN (.... _)_
191 ssdel << ")";
193 if(need_execute_del || need_execute_ins)
195 CharacterDatabase.BeginTransaction ();
196 if(need_execute_del)
197 CharacterDatabase.Execute( ssdel.str().c_str() );
198 if(need_execute_ins)
199 CharacterDatabase.Execute( ssins.str().c_str() );
200 CharacterDatabase.CommitTransaction ();
205 void AchievementMgr::LoadFromDB(QueryResult *achievementResult, QueryResult *criteriaResult)
207 if(achievementResult)
211 Field *fields = achievementResult->Fetch();
212 CompletedAchievementData& ca = m_completedAchievements[fields[0].GetUInt32()];
213 ca.date = time_t(fields[1].GetUInt64());
214 ca.changed = false;
215 } while(achievementResult->NextRow());
216 delete achievementResult;
219 if(criteriaResult)
223 Field *fields = criteriaResult->Fetch();
225 uint32 id = fields[0].GetUInt32();
226 uint32 counter = fields[1].GetUInt32();
227 time_t date = time_t(fields[2].GetUInt64());
229 AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(id);
230 if(!criteria || criteria->timeLimit && date + criteria->timeLimit < time(NULL))
231 continue;
233 CriteriaProgress& progress = m_criteriaProgress[id];
234 progress.counter = counter;
235 progress.date = date;
236 progress.changed = false;
237 } while(criteriaResult->NextRow());
238 delete criteriaResult;
243 void AchievementMgr::SendAchievementEarned(AchievementEntry const* achievement)
245 sLog.outString("AchievementMgr::SendAchievementEarned(%u)", achievement->ID);
247 const char *msg = "|Hplayer:$N|h[$N]|h has earned the achievement $a!";
248 if(Guild* guild = objmgr.GetGuildById(GetPlayer()->GetGuildId()))
250 WorldPacket data(SMSG_MESSAGECHAT, 200);
251 data << uint8(CHAT_MSG_ACHIEVEMENT);
252 data << uint8(CHAT_MSG_GUILD_ACHIEVEMENT);
253 data << uint32(LANG_UNIVERSAL);
254 data << uint64(GetPlayer()->GetGUID());
255 data << uint32(5);
256 data << uint64(GetPlayer()->GetGUID());
257 data << uint32(strlen(msg)+1);
258 data << msg;
259 data << uint8(0);
260 data << uint32(achievement->ID);
261 guild->BroadcastPacket(&data);
263 if(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_KILL|ACHIEVEMENT_FLAG_REALM_FIRST_REACH))
265 // broadcast realm first reached
266 WorldPacket data(SMSG_SERVER_FIRST_ACHIEVEMENT, strlen(GetPlayer()->GetName())+1+8+4+4);
267 data << GetPlayer()->GetName();
268 data << uint64(GetPlayer()->GetGUID());
269 data << uint32(achievement->ID);
270 data << uint32(0); // 1=link supplied string as player name, 0=display plain string
271 sWorld.SendGlobalMessage(&data);
273 else
275 WorldPacket data(SMSG_MESSAGECHAT, 200);
276 data << uint8(CHAT_MSG_ACHIEVEMENT);
277 data << uint32(LANG_UNIVERSAL);
278 data << uint64(GetPlayer()->GetGUID());
279 data << uint32(5);
280 data << uint64(GetPlayer()->GetGUID());
281 data << uint32(strlen(msg)+1);
282 data << msg;
283 data << uint8(0);
284 data << uint32(achievement->ID);
285 GetPlayer()->SendMessageToSet(&data, true);
288 WorldPacket data(SMSG_ACHIEVEMENT_EARNED, 8+4+8);
289 data.append(GetPlayer()->GetPackGUID());
290 data << uint32(achievement->ID);
291 data << uint32(secsToTimeBitFields(time(NULL)));
292 data << uint32(0);
293 GetPlayer()->SendMessageToSet(&data, true);
296 void AchievementMgr::SendCriteriaUpdate(uint32 id, CriteriaProgress const* progress)
298 WorldPacket data(SMSG_CRITERIA_UPDATE, 8+4+8);
299 data << uint32(id);
301 // the counter is packed like a packed Guid
302 data.appendPackGUID(progress->counter);
304 data.append(GetPlayer()->GetPackGUID());
305 data << uint32(0);
306 data << uint32(secsToTimeBitFields(progress->date));
307 data << uint32(0); // timer 1
308 data << uint32(0); // timer 2
309 GetPlayer()->SendMessageToSet(&data, true);
313 * called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet
315 void AchievementMgr::CheckAllAchievementCriteria()
317 // suppress sending packets
318 for(uint32 i=0; i<ACHIEVEMENT_CRITERIA_TYPE_TOTAL; i++)
319 UpdateAchievementCriteria(AchievementCriteriaTypes(i));
323 * this function will be called whenever the user might have done a criteria relevant action
325 void AchievementMgr::UpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscvalue1, uint32 miscvalue2, Unit *unit, uint32 time)
327 sLog.outDetail("AchievementMgr::UpdateAchievementCriteria(%u, %u, %u, %u)", type, miscvalue1, miscvalue2, time);
329 if (!sWorld.getConfig(CONFIG_GM_ALLOW_ACHIEVEMENT_GAINS) && m_player->GetSession()->GetSecurity() > SEC_PLAYER)
330 return;
332 AchievementCriteriaEntryList const& achievementCriteriaList = achievementmgr.GetAchievementCriteriaByType(type);
333 for(AchievementCriteriaEntryList::const_iterator i = achievementCriteriaList.begin(); i!=achievementCriteriaList.end(); ++i)
335 AchievementCriteriaEntry const *achievementCriteria = (*i);
337 // don't update already completed criteria
338 if(IsCompletedCriteria(achievementCriteria))
339 continue;
341 if(achievementCriteria->groupFlag & ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP && GetPlayer()->GetGroup())
342 continue;
344 AchievementEntry const *achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement);
345 if(!achievement)
346 continue;
348 if(achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_HORDE && GetPlayer()->GetTeam() != HORDE ||
349 achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_ALLIANCE && GetPlayer()->GetTeam() != ALLIANCE)
350 continue;
352 switch (type)
354 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL:
355 SetCriteriaProgress(achievementCriteria, GetPlayer()->getLevel());
356 break;
357 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT:
358 SetCriteriaProgress(achievementCriteria, GetPlayer()->GetByteValue(PLAYER_BYTES_2, 2)+1);
359 break;
360 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE:
361 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
362 if(!miscvalue1)
363 continue;
364 if(achievementCriteria->kill_creature.creatureID != miscvalue1)
365 continue;
366 SetCriteriaProgress(achievementCriteria, miscvalue2, true);
367 break;
368 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL:
369 if(uint32 skillvalue = GetPlayer()->GetBaseSkillValue(achievementCriteria->reach_skill_level.skillID))
370 SetCriteriaProgress(achievementCriteria, skillvalue);
371 break;
372 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT:
374 uint32 counter =0;
375 for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++)
376 if(itr->second.m_rewarded)
377 counter++;
378 SetCriteriaProgress(achievementCriteria, counter);
379 break;
381 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE:
383 uint32 counter =0;
384 for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++)
386 Quest const* quest = objmgr.GetQuestTemplate(itr->first);
387 if(itr->second.m_rewarded && quest->GetZoneOrSort() == achievementCriteria->complete_quests_in_zone.zoneID)
388 counter++;
390 SetCriteriaProgress(achievementCriteria, counter);
391 break;
393 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST:
394 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
395 if(!miscvalue1)
396 continue;
397 SetCriteriaProgress(achievementCriteria, miscvalue1, true);
398 break;
399 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND:
400 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
401 if(!miscvalue1)
402 continue;
403 if(GetPlayer()->GetMapId() != achievementCriteria->complete_battleground.mapID)
404 continue;
405 SetCriteriaProgress(achievementCriteria, miscvalue1, true);
406 break;
407 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL:
408 if(GetPlayer()->HasSpell(achievementCriteria->learn_spell.spellID))
409 SetCriteriaProgress(achievementCriteria, 1);
410 break;
411 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT:
412 if(m_completedAchievements.find(achievementCriteria->complete_achievement.linkedAchievement) != m_completedAchievements.end())
413 SetCriteriaProgress(achievementCriteria, 1);
414 break;
415 case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP:
416 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
417 if(!miscvalue1)
418 continue;
419 if(GetPlayer()->GetMapId() != achievementCriteria->death_at_map.mapID)
420 continue;
421 SetCriteriaProgress(achievementCriteria, 1, true);
422 break;
423 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE:
424 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
425 if(!miscvalue1)
426 continue;
427 if(miscvalue1 != achievementCriteria->killed_by_creature.creatureEntry)
428 continue;
429 SetCriteriaProgress(achievementCriteria, 1, true);
430 break;
431 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER:
432 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
433 if(!miscvalue1)
434 continue;
435 SetCriteriaProgress(achievementCriteria, 1, true);
436 break;
437 case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING:
439 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
440 if(!miscvalue1)
441 continue;
442 if(achievement->ID == 1260)
444 if(Player::GetDrunkenstateByValue(GetPlayer()->GetDrunkValue()) != DRUNKEN_SMASHED)
445 continue;
446 // TODO: hardcoding eventid is bad, it can differ from DB to DB - maye implement something using HolidayNames.dbc?
447 if(!gameeventmgr.IsActiveEvent(26))
448 continue;
450 // miscvalue1 is the ingame fallheight*100 as stored in dbc
451 SetCriteriaProgress(achievementCriteria, miscvalue1);
452 break;
454 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST:
455 if(GetPlayer()->GetQuestRewardStatus(achievementCriteria->complete_quest.questID))
456 SetCriteriaProgress(achievementCriteria, 1);
457 break;
458 case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM:
459 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
460 if(!miscvalue1)
461 continue;
462 if(achievementCriteria->use_item.itemID != miscvalue1)
463 continue;
464 SetCriteriaProgress(achievementCriteria, 1, true);
465 break;
466 case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM:
467 // speedup for non-login case
468 if(miscvalue1 && achievementCriteria->own_item.itemID!=miscvalue1)
469 continue;
470 SetCriteriaProgress(achievementCriteria, GetPlayer()->GetItemCount(achievementCriteria->own_item.itemID, true));
471 break;
472 case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM:
473 // You _have_ to loot that item, just owning it when logging in does _not_ count!
474 if(!miscvalue1)
475 continue;
476 if(miscvalue1 != achievementCriteria->own_item.itemID)
477 continue;
478 SetCriteriaProgress(achievementCriteria, miscvalue2, true);
479 break;
480 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET:
481 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2:
482 if (!miscvalue1 || miscvalue1 != achievementCriteria->be_spell_target.spellID)
483 continue;
484 SetCriteriaProgress(achievementCriteria, 1, true);
485 break;
486 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL:
487 if (!miscvalue1 || miscvalue1 != achievementCriteria->cast_spell.spellID)
488 continue;
489 SetCriteriaProgress(achievementCriteria, 1, true);
490 break;
491 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2:
493 if (!miscvalue1 || miscvalue1 != achievementCriteria->cast_spell.spellID)
494 continue;
496 // those requirements couldn't be found in the dbc
497 if (CriteriaCastSpellRequirement const* requirement = AchievementGlobalMgr::GetCriteriaCastSpellRequirement(achievementCriteria))
499 if (!unit)
500 continue;
502 if (requirement->creatureEntry && unit->GetEntry() != requirement->creatureEntry)
503 continue;
505 if (requirement->playerRace && (unit->GetTypeId() != TYPEID_PLAYER || unit->getRace()!=requirement->playerRace))
506 continue;
508 if (requirement->playerClass && (unit->GetTypeId() != TYPEID_PLAYER || unit->getClass()!=requirement->playerClass))
509 continue;
512 SetCriteriaProgress(achievementCriteria, 1, true);
513 break;
515 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS:
517 uint32 spellCount = 0;
518 for (PlayerSpellMap::const_iterator spellIter = GetPlayer()->GetSpellMap().begin();
519 spellIter != GetPlayer()->GetSpellMap().end();
520 spellIter++)
522 for(SkillLineAbilityMap::const_iterator skillIter = spellmgr.GetBeginSkillLineAbilityMap(spellIter->first);
523 skillIter != spellmgr.GetEndSkillLineAbilityMap(spellIter->first);
524 skillIter++)
526 if(skillIter->second->skillId == achievementCriteria->learn_skilline_spell.skillLine)
527 spellCount++;
530 SetCriteriaProgress(achievementCriteria, spellCount);
531 break;
533 case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP:
535 // skip for login case
536 if(!miscvalue1)
537 continue;
538 SetCriteriaProgress(achievementCriteria, 1);
539 break;
541 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION:
543 int32 reputation = GetPlayer()->GetReputation(achievementCriteria->gain_reputation.factionID);
544 if (reputation > 0)
545 SetCriteriaProgress(achievementCriteria, reputation);
546 break;
548 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION:
550 uint32 counter = 0;
551 const FactionStateList factionStateList = GetPlayer()->GetFactionStateList();
552 for (FactionStateList::const_iterator iter = factionStateList.begin(); iter!= factionStateList.end(); iter++)
554 if(GetPlayer()->ReputationToRank(iter->second.Standing) >= REP_EXALTED)
555 ++counter;
557 SetCriteriaProgress(achievementCriteria, counter);
558 break;
560 case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA:
562 WorldMapOverlayEntry const* worldOverlayEntry = sWorldMapOverlayStore.LookupEntry(achievementCriteria->explore_area.areaReference);
563 if(!worldOverlayEntry)
564 break;
566 int32 exploreFlag = GetAreaFlagByAreaID(worldOverlayEntry->areatableID);
567 if(exploreFlag < 0)
568 break;
570 uint32 playerIndexOffset = uint32(exploreFlag) / 32;
571 uint32 mask = 1<< (uint32(exploreFlag) % 32);
573 if(GetPlayer()->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + playerIndexOffset) & mask)
574 SetCriteriaProgress(achievementCriteria, 1);
575 break;
577 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT:
578 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT:
580 // miscvalue1 = itemid
581 // miscvalue2 = diced value
582 if(!miscvalue1)
583 continue;
584 if(miscvalue2 != achievementCriteria->roll_greed_on_loot.rollValue)
585 continue;
586 ItemPrototype const *pProto = objmgr.GetItemPrototype( miscvalue1 );
588 uint32 requiredItemLevel = 0;
589 if (achievementCriteria->ID == 2412 || achievementCriteria->ID == 2358)
590 requiredItemLevel = 185;
592 if(!pProto || pProto->ItemLevel <requiredItemLevel)
593 continue;
594 SetCriteriaProgress(achievementCriteria, 1, true);
595 break;
598 if(IsCompletedCriteria(achievementCriteria))
599 CompletedCriteria(achievementCriteria);
603 bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry const* achievementCriteria)
605 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement);
606 if(!achievement)
607 return false;
609 // counter can never complete
610 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER)
611 return false;
613 if(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
615 // someone on this realm has already completed that achievement
616 if(achievementmgr.IsRealmCompleted(achievement))
617 return false;
620 CriteriaProgressMap::const_iterator itr = m_criteriaProgress.find(achievementCriteria->ID);
621 if(itr == m_criteriaProgress.end())
622 return false;
624 CriteriaProgress const* progress = &itr->second;
626 switch(achievementCriteria->requiredType)
628 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL:
629 if(achievement->ID == 467 && GetPlayer()->getClass() != CLASS_SHAMAN ||
630 achievement->ID == 466 && GetPlayer()->getClass() != CLASS_DRUID ||
631 achievement->ID == 465 && GetPlayer()->getClass() != CLASS_PALADIN ||
632 achievement->ID == 464 && GetPlayer()->getClass() != CLASS_PRIEST ||
633 achievement->ID == 463 && GetPlayer()->getClass() != CLASS_WARLOCK ||
634 achievement->ID == 462 && GetPlayer()->getClass() != CLASS_HUNTER ||
635 achievement->ID == 461 && GetPlayer()->getClass() != CLASS_DEATH_KNIGHT ||
636 achievement->ID == 460 && GetPlayer()->getClass() != CLASS_MAGE ||
637 achievement->ID == 459 && GetPlayer()->getClass() != CLASS_WARRIOR ||
638 achievement->ID == 458 && GetPlayer()->getClass() != CLASS_ROGUE ||
640 achievement->ID == 1404 && GetPlayer()->getRace() != RACE_GNOME ||
641 achievement->ID == 1405 && GetPlayer()->getRace() != RACE_BLOODELF ||
642 achievement->ID == 1406 && GetPlayer()->getRace() != RACE_DRAENEI ||
643 achievement->ID == 1407 && GetPlayer()->getRace() != RACE_DWARF ||
644 achievement->ID == 1408 && GetPlayer()->getRace() != RACE_HUMAN ||
645 achievement->ID == 1409 && GetPlayer()->getRace() != RACE_NIGHTELF ||
646 achievement->ID == 1410 && GetPlayer()->getRace() != RACE_ORC ||
647 achievement->ID == 1411 && GetPlayer()->getRace() != RACE_TAUREN ||
648 achievement->ID == 1412 && GetPlayer()->getRace() != RACE_TROLL ||
649 achievement->ID == 1413 && GetPlayer()->getRace() != RACE_UNDEAD_PLAYER )
650 return false;
651 return progress->counter >= achievementCriteria->reach_level.level;
652 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT:
653 return progress->counter >= achievementCriteria->buy_bank_slot.numberOfSlots;
654 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE:
655 return progress->counter >= achievementCriteria->kill_creature.creatureCount;
656 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT:
657 return progress->counter >= 1;
658 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL:
659 return progress->counter >= achievementCriteria->reach_skill_level.skillLevel;
660 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT:
661 return progress->counter >= achievementCriteria->complete_quest_count.totalQuestCount;
662 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE:
663 return progress->counter >= achievementCriteria->complete_quests_in_zone.questCount;
664 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST:
665 return progress->counter >= achievementCriteria->complete_daily_quest.questCount;
666 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL:
667 return progress->counter >= 1;
668 case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING:
669 return progress->counter >= achievementCriteria->fall_without_dying.fallHeight;
670 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST:
671 return progress->counter >= 1;
672 case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM:
673 return progress->counter >= achievementCriteria->use_item.itemCount;
674 case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM:
675 return progress->counter >= achievementCriteria->own_item.itemCount;
676 case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM:
677 return progress->counter >= achievementCriteria->loot_item.itemCount;
678 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET:
679 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2:
680 return progress->counter >= achievementCriteria->be_spell_target.spellCount;
681 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL:
682 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2:
683 return progress->counter >= achievementCriteria->cast_spell.castCount;
684 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS:
685 return progress->counter >= achievementCriteria->learn_skilline_spell.spellCount;
686 case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP:
687 return progress->counter >= achievementCriteria->visit_barber.numberOfVisits;
688 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION:
689 return progress->counter >= achievementCriteria->gain_reputation.reputationAmount;
690 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION:
691 return progress->counter >= achievementCriteria->gain_exalted_reputation.numberOfExaltedFactions;
692 case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA:
693 return progress->counter >= 1;
694 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT:
695 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT:
696 return progress->counter >= achievementCriteria->roll_greed_on_loot.count;
698 // handle all statistic-only criteria here
699 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND:
700 case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP:
701 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE:
702 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER:
703 return false;
705 return false;
708 void AchievementMgr::CompletedCriteria(AchievementCriteriaEntry const* criteria)
710 AchievementEntry const* achievement = sAchievementStore.LookupEntry(criteria->referredAchievement);
711 if(!achievement)
712 return;
713 // counter can never complete
714 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER)
715 return;
717 if(criteria->completionFlag & ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL || GetAchievementCompletionState(achievement)==ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED)
719 CompletedAchievement(achievement);
723 // TODO: achievement 705 requires 4 criteria to be fulfilled
724 AchievementCompletionState AchievementMgr::GetAchievementCompletionState(AchievementEntry const* entry)
726 if(m_completedAchievements.find(entry->ID)!=m_completedAchievements.end())
727 return ACHIEVEMENT_COMPLETED_COMPLETED_STORED;
729 bool foundOutstanding = false;
730 for (uint32 entryId = 0; entryId<sAchievementCriteriaStore.GetNumRows(); entryId++)
732 AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(entryId);
733 if(!criteria || criteria->referredAchievement!= entry->ID)
734 continue;
736 if(IsCompletedCriteria(criteria) && criteria->completionFlag & ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL)
737 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED;
739 // found an umcompleted criteria, but DONT return false yet - there might be a completed criteria with ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
740 if(!IsCompletedCriteria(criteria))
741 foundOutstanding = true;
743 if(foundOutstanding)
744 return ACHIEVEMENT_COMPLETED_NONE;
745 else
746 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED;
749 void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 newValue, bool relative)
751 sLog.outDetail("AchievementMgr::SetCriteriaProgress(%u, %u)", entry->ID, newValue);
752 CriteriaProgress *progress = NULL;
754 CriteriaProgressMap::iterator iter = m_criteriaProgress.find(entry->ID);
756 if(iter == m_criteriaProgress.end())
758 // not create record for 0 counter
759 if(newValue == 0)
760 return;
762 progress = &m_criteriaProgress[entry->ID];
763 progress->counter = newValue;
764 progress->date = time(NULL);
766 else
768 progress = &iter->second;
769 if(relative)
770 newValue += progress->counter;
772 // not update (not mark as changed) if counter will have same value
773 if(progress->counter == newValue)
774 return;
776 progress->counter = newValue;
779 progress->changed = true;
781 if(entry->timeLimit)
783 time_t now = time(NULL);
784 if(progress->date + entry->timeLimit < now)
786 progress->counter = 1;
788 // also it seems illogical, the timeframe will be extended at every criteria update
789 progress->date = now;
791 SendCriteriaUpdate(entry->ID,progress);
794 void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement)
796 sLog.outDetail("AchievementMgr::CompletedAchievement(%u)", achievement->ID);
797 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER || m_completedAchievements.find(achievement->ID)!=m_completedAchievements.end())
798 return;
800 SendAchievementEarned(achievement);
801 CompletedAchievementData& ca = m_completedAchievements[achievement->ID];
802 ca.date = time(NULL);
803 ca.changed = true;
805 // don't insert for ACHIEVEMENT_FLAG_REALM_FIRST_KILL since otherwise only the first group member would reach that achievement
806 // TODO: where do set this instead?
807 if(!(achievement->flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
808 achievementmgr.SetRealmCompleted(achievement);
810 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT);
812 // reward items and titles if any
813 AchievementReward const* reward = achievementmgr.GetAchievementReward(achievement);
815 // no rewards
816 if(!reward)
817 return;
819 // titles
820 if(uint32 titleId = reward->titleId[GetPlayer()->GetTeam() == HORDE?0:1])
822 if(CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId))
823 GetPlayer()->SetTitle(titleEntry);
826 // mail
827 if(reward->sender)
829 Item* item = reward->itemId ? Item::CreateItem(reward->itemId,1,GetPlayer ()) : NULL;
831 MailItemsInfo mi;
832 if(item)
834 // save new item before send
835 item->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted
837 // item
838 mi.AddItem(item->GetGUIDLow(), item->GetEntry(), item);
841 int loc_idx = GetPlayer()->GetSession()->GetSessionDbLocaleIndex();
843 // subject and text
844 std::string subject = reward->subject;
845 std::string text = reward->text;
846 if ( loc_idx >= 0 )
848 if(AchievementRewardLocale const* loc = achievementmgr.GetAchievementRewardLocale(achievement))
850 if (loc->subject.size() > size_t(loc_idx) && !loc->subject[loc_idx].empty())
851 subject = loc->subject[loc_idx];
852 if (loc->text.size() > size_t(loc_idx) && !loc->text[loc_idx].empty())
853 text = loc->text[loc_idx];
857 uint32 itemTextId = objmgr.CreateItemText( text );
859 WorldSession::SendMailTo(GetPlayer(), MAIL_CREATURE, MAIL_STATIONERY_NORMAL, reward->sender, GetPlayer()->GetGUIDLow(), subject, itemTextId , &mi, 0, 0, MAIL_CHECK_MASK_NONE);
863 void AchievementMgr::SendAllAchievementData()
865 // since we don't know the exact size of the packed GUIDs this is just an approximation
866 WorldPacket data(SMSG_ALL_ACHIEVEMENT_DATA, 4*2+m_completedAchievements.size()*4*2+m_completedAchievements.size()*7*4);
867 BuildAllDataPacket(&data);
868 GetPlayer()->GetSession()->SendPacket(&data);
871 void AchievementMgr::SendRespondInspectAchievements(Player* player)
873 // since we don't know the exact size of the packed GUIDs this is just an approximation
874 WorldPacket data(SMSG_RESPOND_INSPECT_ACHIEVEMENTS, 4+4*2+m_completedAchievements.size()*4*2+m_completedAchievements.size()*7*4);
875 data.append(GetPlayer()->GetPackGUID());
876 BuildAllDataPacket(&data);
877 player->GetSession()->SendPacket(&data);
881 * used by both SMSG_ALL_ACHIEVEMENT_DATA and SMSG_RESPOND_INSPECT_ACHIEVEMENT
883 void AchievementMgr::BuildAllDataPacket(WorldPacket *data)
885 for(CompletedAchievementMap::const_iterator iter = m_completedAchievements.begin(); iter!=m_completedAchievements.end(); ++iter)
887 *data << uint32(iter->first);
888 *data << uint32(secsToTimeBitFields(iter->second.date));
890 *data << int32(-1);
892 for(CriteriaProgressMap::const_iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter)
894 *data << uint32(iter->first);
895 data->appendPackGUID(iter->second.counter);
896 data->append(GetPlayer()->GetPackGUID());
897 *data << uint32(0);
898 *data << uint32(secsToTimeBitFields(iter->second.date));
899 *data << uint32(0);
900 *data << uint32(0);
903 *data << int32(-1);
906 //==========================================================
907 AchievementCriteriaEntryList const& AchievementGlobalMgr::GetAchievementCriteriaByType(AchievementCriteriaTypes type)
909 return m_AchievementCriteriasByType[type];
912 void AchievementGlobalMgr::LoadAchievementCriteriaList()
914 if(sAchievementCriteriaStore.GetNumRows()==0)
916 barGoLink bar(1);
917 bar.step();
919 sLog.outString("");
920 sLog.outErrorDb(">> Loaded 0 achievement criteria.");
921 return;
924 barGoLink bar( sAchievementCriteriaStore.GetNumRows() );
925 for (uint32 entryId = 0; entryId<sAchievementCriteriaStore.GetNumRows(); entryId++)
927 bar.step();
929 AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(entryId);
930 if(!criteria)
931 continue;
933 m_AchievementCriteriasByType[criteria->requiredType].push_back(criteria);
936 sLog.outString();
937 sLog.outErrorDb(">> Loaded %u achievement criteria.",m_AchievementCriteriasByType->size());
941 void AchievementGlobalMgr::LoadCompletedAchievements()
943 QueryResult *result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement");
945 if(!result)
947 barGoLink bar(1);
948 bar.step();
950 sLog.outString("");
951 sLog.outString(">> Loaded 0 realm completed achievements . DB table `character_achievement` is empty.");
952 return;
955 barGoLink bar(result->GetRowCount());
958 bar.step();
959 Field *fields = result->Fetch();
960 m_allCompletedAchievements.insert(fields[0].GetUInt32());
961 } while(result->NextRow());
963 delete result;
965 sLog.outString("");
966 sLog.outString(">> Loaded %u realm completed achievements.",m_allCompletedAchievements.size());
969 void AchievementGlobalMgr::LoadRewards()
971 m_achievementRewards.clear(); // need for reload case
973 // 0 1 2 3 4 5 6
974 QueryResult *result = WorldDatabase.Query("SELECT entry, title_A, title_H, item, sender, subject, text FROM achievement_reward");
976 if(!result)
978 barGoLink bar(1);
980 bar.step();
982 sLog.outString("");
983 sLog.outErrorDb(">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
984 return;
987 barGoLink bar(result->GetRowCount());
991 bar.step();
993 Field *fields = result->Fetch();
994 uint32 entry = fields[0].GetUInt32();
995 if (!sAchievementStore.LookupEntry(entry))
997 sLog.outErrorDb( "Table `achievement_reward` has wrong achievement (Entry: %u), ignore", entry);
998 continue;
1001 AchievementReward reward;
1002 reward.titleId[0] = fields[1].GetUInt32();
1003 reward.titleId[1] = fields[2].GetUInt32();
1004 reward.itemId = fields[3].GetUInt32();
1005 reward.sender = fields[4].GetUInt32();
1006 reward.subject = fields[5].GetCppString();
1007 reward.text = fields[6].GetCppString();
1009 if ((reward.titleId[0]==0)!=(reward.titleId[1]==0))
1010 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has title (A: %u H: %u) only for one from teams.", entry, reward.titleId[0], reward.titleId[1]);
1012 // must be title or mail at least
1013 if (!reward.titleId[0] && !reward.titleId[1] && !reward.sender)
1015 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have title or item reward data, ignore.", entry);
1016 continue;
1019 if (reward.titleId[0])
1021 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.titleId[0]);
1022 if (!titleEntry)
1024 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry, reward.titleId[0]);
1025 reward.titleId[0] = 0;
1029 if (reward.titleId[1])
1031 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.titleId[1]);
1032 if (!titleEntry)
1034 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry, reward.titleId[1]);
1035 reward.titleId[1] = 0;
1039 //check mail data before item for report including wrong item case
1040 if (reward.sender)
1042 if (!objmgr.GetCreatureTemplate(reward.sender))
1044 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid creature entry %u as sender, mail reward skipped.", entry, reward.sender);
1045 reward.sender = 0;
1048 else
1050 if (reward.itemId)
1051 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have item reward, item will not rewarded", entry);
1053 if (!reward.subject.empty())
1054 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail subject.", entry);
1056 if (!reward.text.empty())
1057 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail text.", entry);
1060 if (reward.itemId)
1062 if (!objmgr.GetItemPrototype(reward.itemId))
1064 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid item id %u, reward mail will be without item.", entry, reward.itemId);
1065 reward.itemId = 0;
1069 m_achievementRewards[entry] = reward;
1071 } while (result->NextRow());
1073 delete result;
1075 sLog.outString();
1076 sLog.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales.size() );
1079 void AchievementGlobalMgr::LoadRewardLocales()
1081 m_achievementRewardLocales.clear(); // need for reload case
1083 QueryResult *result = WorldDatabase.Query("SELECT entry,subject_loc1,text_loc1,subject_loc2,text_loc2,subject_loc3,text_loc3,subject_loc4,text_loc4,subject_loc5,text_loc5,subject_loc6,text_loc6,subject_loc7,text_loc7,subject_loc8,text_loc8 FROM locales_achievement_reward");
1085 if(!result)
1087 barGoLink bar(1);
1089 bar.step();
1091 sLog.outString("");
1092 sLog.outString(">> Loaded 0 achievement reward locale strings. DB table `locales_achievement_reward` is empty.");
1093 return;
1096 barGoLink bar(result->GetRowCount());
1100 Field *fields = result->Fetch();
1101 bar.step();
1103 uint32 entry = fields[0].GetUInt32();
1105 if(m_achievementRewards.find(entry)==m_achievementRewards.end())
1107 sLog.outErrorDb( "Table `locales_achievement_reward` (Entry: %u) has locale strings for not existed achievement reward .", entry);
1108 continue;
1111 AchievementRewardLocale& data = m_achievementRewardLocales[entry];
1113 for(int i = 1; i < MAX_LOCALE; ++i)
1115 std::string str = fields[1+2*(i-1)].GetCppString();
1116 if(!str.empty())
1118 int idx = objmgr.GetOrNewIndexForLocale(LocaleConstant(i));
1119 if(idx >= 0)
1121 if(data.subject.size() <= idx)
1122 data.subject.resize(idx+1);
1124 data.subject[idx] = str;
1127 str = fields[1+2*(i-1)+1].GetCppString();
1128 if(!str.empty())
1130 int idx = objmgr.GetOrNewIndexForLocale(LocaleConstant(i));
1131 if(idx >= 0)
1133 if(data.text.size() <= idx)
1134 data.text.resize(idx+1);
1136 data.text[idx] = str;
1140 } while (result->NextRow());
1142 delete result;
1144 sLog.outString();
1145 sLog.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales.size() );