Use configuration values for arena directly instead of values cached in local variabl...
[getmangos.git] / src / game / AchievementMgr.cpp
blobfb66848922cc2cce5725c70e068f792e50542356
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 = false;
145 std::ostringstream ssdel;
146 std::ostringstream ssins;
147 for(CriteriaProgressMap::iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter)
149 if(!iter->second.changed)
150 continue;
152 /// first new/changed record prefix
153 if(!need_execute)
155 ssdel << "DELETE FROM character_achievement_progress WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND criteria IN (";
156 ssins << "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES ";
157 need_execute = true;
159 /// next new/changed record prefix
160 else
162 ssdel << ", ";
163 ssins << ", ";
166 // new/changed record data
167 ssdel << iter->first;
168 ssins << "(" << GetPlayer()->GetGUIDLow() << ", " << iter->first << ", " << iter->second.counter << ", " << iter->second.date << ")";
170 /// mark as saved in db
171 iter->second.changed = false;
174 if(need_execute)
175 ssdel << ")";
177 if(need_execute)
179 CharacterDatabase.BeginTransaction ();
180 CharacterDatabase.Execute( ssdel.str().c_str() );
181 CharacterDatabase.Execute( ssins.str().c_str() );
182 CharacterDatabase.CommitTransaction ();
187 void AchievementMgr::LoadFromDB(QueryResult *achievementResult, QueryResult *criteriaResult)
189 if(achievementResult)
193 Field *fields = achievementResult->Fetch();
194 CompletedAchievementData& ca = m_completedAchievements[fields[0].GetUInt32()];
195 ca.date = time_t(fields[1].GetUInt64());
196 ca.changed = false;
197 } while(achievementResult->NextRow());
198 delete achievementResult;
201 if(criteriaResult)
205 Field *fields = criteriaResult->Fetch();
207 uint32 id = fields[0].GetUInt32();
208 uint32 counter = fields[1].GetUInt32();
209 time_t date = time_t(fields[2].GetUInt64());
211 AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(id);
212 if(!criteria || criteria->timeLimit && date + criteria->timeLimit < time(NULL))
213 continue;
215 CriteriaProgress& progress = m_criteriaProgress[id];
216 progress.counter = counter;
217 progress.date = date;
218 progress.changed = false;
219 } while(criteriaResult->NextRow());
220 delete criteriaResult;
225 void AchievementMgr::SendAchievementEarned(AchievementEntry const* achievement)
227 sLog.outString("AchievementMgr::SendAchievementEarned(%u)", achievement->ID);
229 const char *msg = "|Hplayer:$N|h[$N]|h has earned the achievement $a!";
230 if(Guild* guild = objmgr.GetGuildById(GetPlayer()->GetGuildId()))
232 WorldPacket data(SMSG_MESSAGECHAT, 200);
233 data << uint8(CHAT_MSG_ACHIEVEMENT);
234 data << uint8(CHAT_MSG_GUILD_ACHIEVEMENT);
235 data << uint32(LANG_UNIVERSAL);
236 data << uint64(GetPlayer()->GetGUID());
237 data << uint32(5);
238 data << uint64(GetPlayer()->GetGUID());
239 data << uint32(strlen(msg)+1);
240 data << msg;
241 data << uint8(0);
242 data << uint32(achievement->ID);
243 guild->BroadcastPacket(&data);
245 if(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_KILL|ACHIEVEMENT_FLAG_REALM_FIRST_REACH))
247 // broadcast realm first reached
248 WorldPacket data(SMSG_SERVER_FIRST_ACHIEVEMENT, strlen(GetPlayer()->GetName())+1+8+4+4);
249 data << GetPlayer()->GetName();
250 data << uint64(GetPlayer()->GetGUID());
251 data << uint32(achievement->ID);
252 data << uint32(0); // 1=link supplied string as player name, 0=display plain string
253 sWorld.SendGlobalMessage(&data);
255 else
257 WorldPacket data(SMSG_MESSAGECHAT, 200);
258 data << uint8(CHAT_MSG_ACHIEVEMENT);
259 data << uint32(LANG_UNIVERSAL);
260 data << uint64(GetPlayer()->GetGUID());
261 data << uint32(5);
262 data << uint64(GetPlayer()->GetGUID());
263 data << uint32(strlen(msg)+1);
264 data << msg;
265 data << uint8(0);
266 data << uint32(achievement->ID);
267 GetPlayer()->SendMessageToSet(&data, true);
270 WorldPacket data(SMSG_ACHIEVEMENT_EARNED, 8+4+8);
271 data.append(GetPlayer()->GetPackGUID());
272 data << uint32(achievement->ID);
273 data << uint32(secsToTimeBitFields(time(NULL)));
274 data << uint32(0);
275 GetPlayer()->SendMessageToSet(&data, true);
278 void AchievementMgr::SendCriteriaUpdate(uint32 id, CriteriaProgress const* progress)
280 WorldPacket data(SMSG_CRITERIA_UPDATE, 8+4+8);
281 data << uint32(id);
283 // the counter is packed like a packed Guid
284 data.appendPackGUID(progress->counter);
286 data.append(GetPlayer()->GetPackGUID());
287 data << uint32(0);
288 data << uint32(secsToTimeBitFields(progress->date));
289 data << uint32(0); // timer 1
290 data << uint32(0); // timer 2
291 GetPlayer()->SendMessageToSet(&data, true);
295 * called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet
297 void AchievementMgr::CheckAllAchievementCriteria()
299 // suppress sending packets
300 for(uint32 i=0; i<ACHIEVEMENT_CRITERIA_TYPE_TOTAL; i++)
301 UpdateAchievementCriteria(AchievementCriteriaTypes(i));
305 * this function will be called whenever the user might have done a criteria relevant action
307 void AchievementMgr::UpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscvalue1, uint32 miscvalue2, Unit *unit, uint32 time)
309 sLog.outDetail("AchievementMgr::UpdateAchievementCriteria(%u, %u, %u, %u)", type, miscvalue1, miscvalue2, time);
310 AchievementCriteriaEntryList const& achievementCriteriaList = achievementmgr.GetAchievementCriteriaByType(type);
311 for(AchievementCriteriaEntryList::const_iterator i = achievementCriteriaList.begin(); i!=achievementCriteriaList.end(); ++i)
313 AchievementCriteriaEntry const *achievementCriteria = (*i);
315 // don't update already completed criteria
316 if(IsCompletedCriteria(achievementCriteria))
317 continue;
319 if(achievementCriteria->groupFlag & ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP && GetPlayer()->GetGroup())
320 continue;
322 AchievementEntry const *achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement);
323 if(!achievement)
324 continue;
326 if(achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_HORDE && GetPlayer()->GetTeam() != HORDE ||
327 achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_ALLIANCE && GetPlayer()->GetTeam() != ALLIANCE)
328 continue;
330 switch (type)
332 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL:
333 SetCriteriaProgress(achievementCriteria, GetPlayer()->getLevel());
334 break;
335 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT:
336 SetCriteriaProgress(achievementCriteria, GetPlayer()->GetByteValue(PLAYER_BYTES_2, 2)+1);
337 break;
338 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE:
339 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
340 if(!miscvalue1)
341 continue;
342 if(achievementCriteria->kill_creature.creatureID != miscvalue1)
343 continue;
344 SetCriteriaProgress(achievementCriteria, miscvalue2, true);
345 break;
346 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL:
347 if(uint32 skillvalue = GetPlayer()->GetBaseSkillValue(achievementCriteria->reach_skill_level.skillID))
348 SetCriteriaProgress(achievementCriteria, skillvalue);
349 break;
350 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT:
352 uint32 counter =0;
353 for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++)
354 if(itr->second.m_rewarded)
355 counter++;
356 SetCriteriaProgress(achievementCriteria, counter);
357 break;
359 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE:
361 uint32 counter =0;
362 for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++)
364 Quest const* quest = objmgr.GetQuestTemplate(itr->first);
365 if(itr->second.m_rewarded && quest->GetZoneOrSort() == achievementCriteria->complete_quests_in_zone.zoneID)
366 counter++;
368 SetCriteriaProgress(achievementCriteria, counter);
369 break;
371 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST:
372 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
373 if(!miscvalue1)
374 continue;
375 SetCriteriaProgress(achievementCriteria, miscvalue1, true);
376 break;
377 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND:
378 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
379 if(!miscvalue1)
380 continue;
381 if(GetPlayer()->GetMapId() != achievementCriteria->complete_battleground.mapID)
382 continue;
383 SetCriteriaProgress(achievementCriteria, miscvalue1, true);
384 break;
385 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL:
386 if(GetPlayer()->HasSpell(achievementCriteria->learn_spell.spellID))
387 SetCriteriaProgress(achievementCriteria, 1);
388 break;
389 case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP:
390 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
391 if(!miscvalue1)
392 continue;
393 if(GetPlayer()->GetMapId() != achievementCriteria->death_at_map.mapID)
394 continue;
395 SetCriteriaProgress(achievementCriteria, 1, true);
396 break;
397 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE:
398 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
399 if(!miscvalue1)
400 continue;
401 if(miscvalue1 != achievementCriteria->killed_by_creature.creatureEntry)
402 continue;
403 SetCriteriaProgress(achievementCriteria, 1, true);
404 break;
405 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER:
406 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
407 if(!miscvalue1)
408 continue;
409 SetCriteriaProgress(achievementCriteria, 1, true);
410 break;
411 case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING:
413 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
414 if(!miscvalue1)
415 continue;
416 if(achievement->ID == 1260)
418 if(Player::GetDrunkenstateByValue(GetPlayer()->GetDrunkValue()) != DRUNKEN_SMASHED)
419 continue;
420 // TODO: hardcoding eventid is bad, it can differ from DB to DB - maye implement something using HolidayNames.dbc?
421 if(!gameeventmgr.IsActiveEvent(26))
422 continue;
424 // miscvalue1 is the ingame fallheight*100 as stored in dbc
425 SetCriteriaProgress(achievementCriteria, miscvalue1);
426 break;
428 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST:
429 if(GetPlayer()->GetQuestRewardStatus(achievementCriteria->complete_quest.questID))
430 SetCriteriaProgress(achievementCriteria, 1);
431 break;
432 case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM:
433 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
434 if(!miscvalue1)
435 continue;
436 if(achievementCriteria->use_item.itemID != miscvalue1)
437 continue;
438 SetCriteriaProgress(achievementCriteria, 1, true);
439 break;
440 case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM:
441 // speedup for non-login case
442 if(miscvalue1 && achievementCriteria->own_item.itemID!=miscvalue1)
443 continue;
444 SetCriteriaProgress(achievementCriteria, GetPlayer()->GetItemCount(achievementCriteria->own_item.itemID, true));
445 break;
446 case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM:
447 // You _have_ to loot that item, just owning it when logging in does _not_ count!
448 if(!miscvalue1)
449 continue;
450 if(miscvalue1 != achievementCriteria->own_item.itemID)
451 continue;
452 SetCriteriaProgress(achievementCriteria, miscvalue2, true);
453 break;
454 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET:
455 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2:
456 if (!miscvalue1 || miscvalue1 != achievementCriteria->be_spell_target.spellID)
457 continue;
458 SetCriteriaProgress(achievementCriteria, 1, true);
459 break;
460 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL:
461 if (!miscvalue1 || miscvalue1 != achievementCriteria->cast_spell.spellID)
462 continue;
463 SetCriteriaProgress(achievementCriteria, 1, true);
464 break;
465 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2:
467 if (!miscvalue1 || miscvalue1 != achievementCriteria->cast_spell.spellID)
468 continue;
470 // those requirements couldn't be found in the dbc
471 if (CriteriaCastSpellRequirement const* requirement = AchievementGlobalMgr::GetCriteriaCastSpellRequirement(achievementCriteria))
473 if (!unit)
474 continue;
476 if (requirement->creatureEntry && unit->GetEntry() != requirement->creatureEntry)
477 continue;
479 if (requirement->playerRace && (unit->GetTypeId() != TYPEID_PLAYER || unit->getRace()!=requirement->playerRace))
480 continue;
482 if (requirement->playerClass && (unit->GetTypeId() != TYPEID_PLAYER || unit->getClass()!=requirement->playerClass))
483 continue;
486 SetCriteriaProgress(achievementCriteria, 1, true);
487 break;
489 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS:
491 uint32 spellCount = 0;
492 for (PlayerSpellMap::const_iterator spellIter = GetPlayer()->GetSpellMap().begin();
493 spellIter != GetPlayer()->GetSpellMap().end();
494 spellIter++)
496 for(SkillLineAbilityMap::const_iterator skillIter = spellmgr.GetBeginSkillLineAbilityMap(spellIter->first);
497 skillIter != spellmgr.GetEndSkillLineAbilityMap(spellIter->first);
498 skillIter++)
500 if(skillIter->second->skillId == achievementCriteria->learn_skilline_spell.skillLine)
501 spellCount++;
504 SetCriteriaProgress(achievementCriteria, spellCount);
505 break;
507 case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP:
509 // skip for login case
510 if(!miscvalue1)
511 continue;
512 SetCriteriaProgress(achievementCriteria, 1);
513 break;
515 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION:
517 int32 reputation = GetPlayer()->GetReputation(achievementCriteria->gain_reputation.factionID);
518 if (reputation > 0)
519 SetCriteriaProgress(achievementCriteria, reputation);
520 break;
522 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION:
524 uint32 counter = 0;
525 const FactionStateList factionStateList = GetPlayer()->GetFactionStateList();
526 for (FactionStateList::const_iterator iter = factionStateList.begin(); iter!= factionStateList.end(); iter++)
528 if(GetPlayer()->ReputationToRank(iter->second.Standing) >= REP_EXALTED)
529 ++counter;
531 SetCriteriaProgress(achievementCriteria, counter);
532 break;
534 case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA:
536 WorldMapOverlayEntry const* worldOverlayEntry = sWorldMapOverlayStore.LookupEntry(achievementCriteria->explore_area.areaReference);
537 if(!worldOverlayEntry)
538 break;
540 int32 exploreFlag = GetAreaFlagByAreaID(worldOverlayEntry->areatableID);
541 if(exploreFlag < 0)
542 break;
544 uint32 playerIndexOffset = uint32(exploreFlag) / 32;
545 uint32 mask = 1<< (uint32(exploreFlag) % 32);
547 if(GetPlayer()->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + playerIndexOffset) & mask)
548 SetCriteriaProgress(achievementCriteria, 1);
549 break;
551 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT:
552 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT:
554 // miscvalue1 = itemid
555 // miscvalue2 = diced value
556 if(!miscvalue1)
557 continue;
558 if(miscvalue2 != achievementCriteria->roll_greed_on_loot.rollValue)
559 continue;
560 ItemPrototype const *pProto = objmgr.GetItemPrototype( miscvalue1 );
562 uint32 requiredItemLevel = 0;
563 if (achievementCriteria->ID == 2412 || achievementCriteria->ID == 2358)
564 requiredItemLevel = 185;
566 if(!pProto || pProto->ItemLevel <requiredItemLevel)
567 continue;
568 SetCriteriaProgress(achievementCriteria, 1, true);
569 break;
572 if(IsCompletedCriteria(achievementCriteria))
573 CompletedCriteria(achievementCriteria);
577 bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry const* achievementCriteria)
579 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement);
580 if(!achievement)
581 return false;
583 // counter can never complete
584 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER)
585 return false;
587 if(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
589 // someone on this realm has already completed that achievement
590 if(achievementmgr.IsRealmCompleted(achievement))
591 return false;
594 CriteriaProgressMap::const_iterator itr = m_criteriaProgress.find(achievementCriteria->ID);
595 if(itr == m_criteriaProgress.end())
596 return false;
598 CriteriaProgress const* progress = &itr->second;
600 switch(achievementCriteria->requiredType)
602 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL:
603 if(achievement->ID == 467 && GetPlayer()->getClass() != CLASS_SHAMAN ||
604 achievement->ID == 466 && GetPlayer()->getClass() != CLASS_DRUID ||
605 achievement->ID == 465 && GetPlayer()->getClass() != CLASS_PALADIN ||
606 achievement->ID == 464 && GetPlayer()->getClass() != CLASS_PRIEST ||
607 achievement->ID == 463 && GetPlayer()->getClass() != CLASS_WARLOCK ||
608 achievement->ID == 462 && GetPlayer()->getClass() != CLASS_HUNTER ||
609 achievement->ID == 461 && GetPlayer()->getClass() != CLASS_DEATH_KNIGHT ||
610 achievement->ID == 460 && GetPlayer()->getClass() != CLASS_MAGE ||
611 achievement->ID == 459 && GetPlayer()->getClass() != CLASS_WARRIOR ||
612 achievement->ID == 458 && GetPlayer()->getClass() != CLASS_ROGUE ||
614 achievement->ID == 1404 && GetPlayer()->getRace() != RACE_GNOME ||
615 achievement->ID == 1405 && GetPlayer()->getRace() != RACE_BLOODELF ||
616 achievement->ID == 1406 && GetPlayer()->getRace() != RACE_DRAENEI ||
617 achievement->ID == 1407 && GetPlayer()->getRace() != RACE_DWARF ||
618 achievement->ID == 1408 && GetPlayer()->getRace() != RACE_HUMAN ||
619 achievement->ID == 1409 && GetPlayer()->getRace() != RACE_NIGHTELF ||
620 achievement->ID == 1410 && GetPlayer()->getRace() != RACE_ORC ||
621 achievement->ID == 1411 && GetPlayer()->getRace() != RACE_TAUREN ||
622 achievement->ID == 1412 && GetPlayer()->getRace() != RACE_TROLL ||
623 achievement->ID == 1413 && GetPlayer()->getRace() != RACE_UNDEAD_PLAYER )
624 return false;
625 return progress->counter >= achievementCriteria->reach_level.level;
626 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT:
627 return progress->counter >= achievementCriteria->buy_bank_slot.numberOfSlots;
628 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE:
629 return progress->counter >= achievementCriteria->kill_creature.creatureCount;
630 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT:
631 return m_completedAchievements.find(achievementCriteria->complete_achievement.linkedAchievement) != m_completedAchievements.end();
632 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL:
633 return progress->counter >= achievementCriteria->reach_skill_level.skillLevel;
634 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT:
635 return progress->counter >= achievementCriteria->complete_quest_count.totalQuestCount;
636 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE:
637 return progress->counter >= achievementCriteria->complete_quests_in_zone.questCount;
638 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST:
639 return progress->counter >= achievementCriteria->complete_daily_quest.questCount;
640 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL:
641 return progress->counter >= 1;
642 case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING:
643 return progress->counter >= achievementCriteria->fall_without_dying.fallHeight;
644 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST:
645 return progress->counter >= 1;
646 case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM:
647 return progress->counter >= achievementCriteria->use_item.itemCount;
648 case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM:
649 return progress->counter >= achievementCriteria->own_item.itemCount;
650 case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM:
651 return progress->counter >= achievementCriteria->loot_item.itemCount;
652 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET:
653 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2:
654 return progress->counter >= achievementCriteria->be_spell_target.spellCount;
655 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL:
656 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2:
657 return progress->counter >= achievementCriteria->cast_spell.castCount;
658 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS:
659 return progress->counter >= achievementCriteria->learn_skilline_spell.spellCount;
660 case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP:
661 return progress->counter >= achievementCriteria->visit_barber.numberOfVisits;
662 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION:
663 return progress->counter >= achievementCriteria->gain_reputation.reputationAmount;
664 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION:
665 return progress->counter >= achievementCriteria->gain_exalted_reputation.numberOfExaltedFactions;
666 case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA:
667 return progress->counter >= 1;
668 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT:
669 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT:
670 return progress->counter >= achievementCriteria->roll_greed_on_loot.count;
672 // handle all statistic-only criteria here
673 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND:
674 case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP:
675 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE:
676 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER:
677 return false;
679 return false;
682 void AchievementMgr::CompletedCriteria(AchievementCriteriaEntry const* criteria)
684 AchievementEntry const* achievement = sAchievementStore.LookupEntry(criteria->referredAchievement);
685 if(!achievement)
686 return;
687 // counter can never complete
688 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER)
689 return;
691 if(criteria->completionFlag & ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL || GetAchievementCompletionState(achievement)==ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED)
693 CompletedAchievement(achievement);
697 // TODO: achievement 705 requires 4 criteria to be fulfilled
698 AchievementCompletionState AchievementMgr::GetAchievementCompletionState(AchievementEntry const* entry)
700 if(m_completedAchievements.find(entry->ID)!=m_completedAchievements.end())
701 return ACHIEVEMENT_COMPLETED_COMPLETED_STORED;
703 bool foundOutstanding = false;
704 for (uint32 entryId = 0; entryId<sAchievementCriteriaStore.GetNumRows(); entryId++)
706 AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(entryId);
707 if(!criteria || criteria->referredAchievement!= entry->ID)
708 continue;
710 if(IsCompletedCriteria(criteria) && criteria->completionFlag & ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL)
711 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED;
713 // found an umcompleted criteria, but DONT return false yet - there might be a completed criteria with ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
714 if(!IsCompletedCriteria(criteria))
715 foundOutstanding = true;
717 if(foundOutstanding)
718 return ACHIEVEMENT_COMPLETED_NONE;
719 else
720 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED;
723 void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 newValue, bool relative)
725 sLog.outDetail("AchievementMgr::SetCriteriaProgress(%u, %u)", entry->ID, newValue);
726 CriteriaProgress *progress = NULL;
728 CriteriaProgressMap::iterator iter = m_criteriaProgress.find(entry->ID);
730 if(iter == m_criteriaProgress.end())
732 progress = &m_criteriaProgress[entry->ID];
733 progress->counter = newValue;
734 progress->date = time(NULL);
736 else
738 progress = &iter->second;
739 if(relative)
740 newValue += progress->counter;
741 if(progress->counter == newValue)
742 return;
743 progress->counter = newValue;
746 progress->changed = true;
748 if(entry->timeLimit)
750 time_t now = time(NULL);
751 if(progress->date + entry->timeLimit < now)
753 progress->counter = 1;
755 // also it seems illogical, the timeframe will be extended at every criteria update
756 progress->date = now;
758 SendCriteriaUpdate(entry->ID,progress);
761 void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement)
763 sLog.outDetail("AchievementMgr::CompletedAchievement(%u)", achievement->ID);
764 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER || m_completedAchievements.find(achievement->ID)!=m_completedAchievements.end())
765 return;
767 SendAchievementEarned(achievement);
768 CompletedAchievementData& ca = m_completedAchievements[achievement->ID];
769 ca.date = time(NULL);
770 ca.changed = true;
772 // don't insert for ACHIEVEMENT_FLAG_REALM_FIRST_KILL since otherwise only the first group member would reach that achievement
773 // TODO: where do set this instead?
774 if(!(achievement->flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL))
775 achievementmgr.SetRealmCompleted(achievement);
777 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT);
779 // reward items and titles if any
780 AchievementReward const* reward = achievementmgr.GetAchievementReward(achievement);
782 // no rewards
783 if(!reward)
784 return;
786 // titles
787 if(uint32 titleId = reward->titleId[GetPlayer()->GetTeam() == HORDE?0:1])
789 if(CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId))
790 GetPlayer()->SetTitle(titleEntry);
793 // mail
794 if(reward->sender)
796 Item* item = reward->itemId ? Item::CreateItem(reward->itemId,1,GetPlayer ()) : NULL;
798 MailItemsInfo mi;
799 if(item)
801 // save new item before send
802 item->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted
804 // item
805 mi.AddItem(item->GetGUIDLow(), item->GetEntry(), item);
808 int loc_idx = GetPlayer()->GetSession()->GetSessionDbLocaleIndex();
810 // subject and text
811 std::string subject = reward->subject;
812 std::string text = reward->text;
813 if ( loc_idx >= 0 )
815 if(AchievementRewardLocale const* loc = achievementmgr.GetAchievementRewardLocale(achievement))
817 if (loc->subject.size() > size_t(loc_idx) && !loc->subject[loc_idx].empty())
818 subject = loc->subject[loc_idx];
819 if (loc->text.size() > size_t(loc_idx) && !loc->text[loc_idx].empty())
820 text = loc->text[loc_idx];
824 uint32 itemTextId = objmgr.CreateItemText( text );
826 WorldSession::SendMailTo(GetPlayer(), MAIL_CREATURE, MAIL_STATIONERY_NORMAL, reward->sender, GetPlayer()->GetGUIDLow(), subject, itemTextId , &mi, 0, 0, MAIL_CHECK_MASK_NONE);
830 void AchievementMgr::SendAllAchievementData()
832 // since we don't know the exact size of the packed GUIDs this is just an approximation
833 WorldPacket data(SMSG_ALL_ACHIEVEMENT_DATA, 4*2+m_completedAchievements.size()*4*2+m_completedAchievements.size()*7*4);
834 BuildAllDataPacket(&data);
835 GetPlayer()->GetSession()->SendPacket(&data);
838 void AchievementMgr::SendRespondInspectAchievements(Player* player)
840 // since we don't know the exact size of the packed GUIDs this is just an approximation
841 WorldPacket data(SMSG_RESPOND_INSPECT_ACHIEVEMENTS, 4+4*2+m_completedAchievements.size()*4*2+m_completedAchievements.size()*7*4);
842 data.append(GetPlayer()->GetPackGUID());
843 BuildAllDataPacket(&data);
844 player->GetSession()->SendPacket(&data);
848 * used by both SMSG_ALL_ACHIEVEMENT_DATA and SMSG_RESPOND_INSPECT_ACHIEVEMENT
850 void AchievementMgr::BuildAllDataPacket(WorldPacket *data)
852 for(CompletedAchievementMap::const_iterator iter = m_completedAchievements.begin(); iter!=m_completedAchievements.end(); ++iter)
854 *data << uint32(iter->first);
855 *data << uint32(secsToTimeBitFields(iter->second.date));
857 *data << int32(-1);
859 for(CriteriaProgressMap::const_iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter)
861 *data << uint32(iter->first);
862 data->appendPackGUID(iter->second.counter);
863 data->append(GetPlayer()->GetPackGUID());
864 *data << uint32(0);
865 *data << uint32(secsToTimeBitFields(iter->second.date));
866 *data << uint32(0);
867 *data << uint32(0);
870 *data << int32(-1);
873 //==========================================================
874 AchievementCriteriaEntryList const& AchievementGlobalMgr::GetAchievementCriteriaByType(AchievementCriteriaTypes type)
876 return m_AchievementCriteriasByType[type];
879 void AchievementGlobalMgr::LoadAchievementCriteriaList()
881 for (uint32 entryId = 0; entryId<sAchievementCriteriaStore.GetNumRows(); entryId++)
883 AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(entryId);
884 if(!criteria)
885 continue;
887 m_AchievementCriteriasByType[criteria->requiredType].push_back(criteria);
892 void AchievementGlobalMgr::LoadCompletedAchievements()
894 QueryResult *result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement");
896 if(!result)
897 return;
901 Field *fields = result->Fetch();
902 m_allCompletedAchievements.insert(fields[0].GetUInt32());
903 } while(result->NextRow());
905 delete result;
908 void AchievementGlobalMgr::LoadRewards()
910 m_achievementRewards.clear(); // need for reload case
912 // 0 1 2 3 4 5 6
913 QueryResult *result = WorldDatabase.Query("SELECT entry, title_A, title_H, item, sender, subject, text FROM achievement_reward");
915 if(!result)
917 barGoLink bar(1);
919 bar.step();
921 sLog.outString("");
922 sLog.outString(">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
923 return;
926 barGoLink bar(result->GetRowCount());
930 Field *fields = result->Fetch();
931 bar.step();
933 uint32 entry = fields[0].GetUInt32();
934 if (!sAchievementStore.LookupEntry(entry))
936 sLog.outErrorDb( "Table `achievement_reward` has wrong achievement (Entry: %u), ignore", entry);
937 continue;
940 AchievementReward reward;
941 reward.titleId[0] = fields[1].GetUInt32();
942 reward.titleId[1] = fields[2].GetUInt32();
943 reward.itemId = fields[3].GetUInt32();
944 reward.sender = fields[4].GetUInt32();
945 reward.subject = fields[5].GetCppString();
946 reward.text = fields[6].GetCppString();
948 if ((reward.titleId[0]==0)!=(reward.titleId[1]==0))
949 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]);
951 // must be title or mail at least
952 if (!reward.titleId[0] && !reward.titleId[1] && !reward.sender)
954 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have title or item reward data, ignore.", entry);
955 continue;
958 if (reward.titleId[0])
960 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.titleId[0]);
961 if (!titleEntry)
963 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry, reward.titleId[0]);
964 reward.titleId[0] = 0;
968 if (reward.titleId[1])
970 CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.titleId[1]);
971 if (!titleEntry)
973 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry, reward.titleId[1]);
974 reward.titleId[1] = 0;
978 //check mail data before item for report including wrong item case
979 if (reward.sender)
981 if (!objmgr.GetCreatureTemplate(reward.sender))
983 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid creature entry %u as sender, mail reward skipped.", entry, reward.sender);
984 reward.sender = 0;
987 else
989 if (reward.itemId)
990 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have item reward, item will not rewarded", entry);
992 if (!reward.subject.empty())
993 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail subject.", entry);
995 if (!reward.text.empty())
996 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail text.", entry);
999 if (reward.itemId)
1001 if (!objmgr.GetItemPrototype(reward.itemId))
1003 sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid item id %u, reward mail will be without item.", entry, reward.itemId);
1004 reward.itemId = 0;
1008 m_achievementRewards[entry] = reward;
1010 } while (result->NextRow());
1012 delete result;
1014 sLog.outString();
1015 sLog.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales.size() );
1018 void AchievementGlobalMgr::LoadRewardLocales()
1020 m_achievementRewardLocales.clear(); // need for reload case
1022 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");
1024 if(!result)
1026 barGoLink bar(1);
1028 bar.step();
1030 sLog.outString("");
1031 sLog.outString(">> Loaded 0 achievement reward locale strings. DB table `locales_achievement_reward` is empty.");
1032 return;
1035 barGoLink bar(result->GetRowCount());
1039 Field *fields = result->Fetch();
1040 bar.step();
1042 uint32 entry = fields[0].GetUInt32();
1044 if(m_achievementRewards.find(entry)==m_achievementRewards.end())
1046 sLog.outErrorDb( "Table `locales_achievement_reward` (Entry: %u) has locale strings for not existed achievement reward .", entry);
1047 continue;
1050 AchievementRewardLocale& data = m_achievementRewardLocales[entry];
1052 for(int i = 1; i < MAX_LOCALE; ++i)
1054 std::string str = fields[1+2*(i-1)].GetCppString();
1055 if(!str.empty())
1057 int idx = objmgr.GetOrNewIndexForLocale(LocaleConstant(i));
1058 if(idx >= 0)
1060 if(data.subject.size() <= idx)
1061 data.subject.resize(idx+1);
1063 data.subject[idx] = str;
1066 str = fields[1+2*(i-1)+1].GetCppString();
1067 if(!str.empty())
1069 int idx = objmgr.GetOrNewIndexForLocale(LocaleConstant(i));
1070 if(idx >= 0)
1072 if(data.text.size() <= idx)
1073 data.text.resize(idx+1);
1075 data.text[idx] = str;
1079 } while (result->NextRow());
1081 delete result;
1083 sLog.outString();
1084 sLog.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales.size() );