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"
22 #include "WorldPacket.h"
23 #include "Database/DBCEnums.h"
24 #include "ObjectMgr.h"
26 #include "Database/DatabaseEnv.h"
27 #include "GameEvent.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
] =
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
},
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
},
86 AchievementMgr::AchievementMgr(Player
*player
)
91 AchievementMgr::~AchievementMgr()
95 void AchievementMgr::Reset()
97 for(CompletedAchievementMap::iterator iter
= m_completedAchievements
.begin(); iter
!=m_completedAchievements
.end(); ++iter
)
99 WorldPacket
data(SMSG_ACHIEVEMENT_DELETED
,4);
100 data
<< uint32(iter
->first
);
101 m_player
->SendDirectMessage(&data
);
104 for(CriteriaProgressMap::iterator iter
= m_criteriaProgress
.begin(); iter
!=m_criteriaProgress
.end(); ++iter
)
106 WorldPacket
data(SMSG_CRITERIA_DELETED
,4);
107 data
<< uint32(iter
->first
);
108 m_player
->SendDirectMessage(&data
);
111 m_completedAchievements
.clear();
112 m_criteriaProgress
.clear();
113 DeleteFromDB(m_player
->GetGUIDLow());
116 CheckAllAchievementCriteria();
119 void AchievementMgr::DeleteFromDB(uint32 lowguid
)
121 CharacterDatabase
.BeginTransaction ();
122 CharacterDatabase
.PExecute("DELETE FROM character_achievement WHERE guid = %u",lowguid
);
123 CharacterDatabase
.PExecute("DELETE FROM character_achievement_progress WHERE guid = %u",lowguid
);
124 CharacterDatabase
.CommitTransaction ();
127 void AchievementMgr::SaveToDB()
129 if(!m_completedAchievements
.empty())
131 bool need_execute
= false;
132 std::ostringstream ssdel
;
133 std::ostringstream ssins
;
134 for(CompletedAchievementMap::iterator iter
= m_completedAchievements
.begin(); iter
!=m_completedAchievements
.end(); ++iter
)
136 if(!iter
->second
.changed
)
139 /// first new/changed record prefix
142 ssdel
<< "DELETE FROM character_achievement WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND achievement IN (";
143 ssins
<< "INSERT INTO character_achievement (guid, achievement, date) VALUES ";
146 /// next new/changed record prefix
153 // new/changed record data
154 ssdel
<< iter
->first
;
155 ssins
<< "("<<GetPlayer()->GetGUIDLow() << ", " << iter
->first
<< ", " << uint64(iter
->second
.date
) << ")";
157 /// mark as saved in db
158 iter
->second
.changed
= false;
166 CharacterDatabase
.BeginTransaction ();
167 CharacterDatabase
.Execute( ssdel
.str().c_str() );
168 CharacterDatabase
.Execute( ssins
.str().c_str() );
169 CharacterDatabase
.CommitTransaction ();
173 if(!m_criteriaProgress
.empty())
175 /// prepare deleting and insert
176 bool need_execute_del
= false;
177 bool need_execute_ins
= false;
178 std::ostringstream ssdel
;
179 std::ostringstream ssins
;
180 for(CriteriaProgressMap::iterator iter
= m_criteriaProgress
.begin(); iter
!=m_criteriaProgress
.end(); ++iter
)
182 if(!iter
->second
.changed
)
185 // deleted data (including 0 progress state)
187 /// first new/changed record prefix (for any counter value)
188 if(!need_execute_del
)
190 ssdel
<< "DELETE FROM character_achievement_progress WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND criteria IN (";
191 need_execute_del
= true;
193 /// next new/changed record prefix
197 // new/changed record data
198 ssdel
<< iter
->first
;
201 // store data only for real progress
202 if(iter
->second
.counter
!= 0)
204 /// first new/changed record prefix
205 if(!need_execute_ins
)
207 ssins
<< "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES ";
208 need_execute_ins
= true;
210 /// next new/changed record prefix
214 // new/changed record data
215 ssins
<< "(" << GetPlayer()->GetGUIDLow() << ", " << iter
->first
<< ", " << iter
->second
.counter
<< ", " << iter
->second
.date
<< ")";
218 /// mark as updated in db
219 iter
->second
.changed
= false;
222 if(need_execute_del
) // DELETE ... IN (.... _)_
225 if(need_execute_del
|| need_execute_ins
)
227 CharacterDatabase
.BeginTransaction ();
229 CharacterDatabase
.Execute( ssdel
.str().c_str() );
231 CharacterDatabase
.Execute( ssins
.str().c_str() );
232 CharacterDatabase
.CommitTransaction ();
237 void AchievementMgr::LoadFromDB(QueryResult
*achievementResult
, QueryResult
*criteriaResult
)
239 if(achievementResult
)
243 Field
*fields
= achievementResult
->Fetch();
244 CompletedAchievementData
& ca
= m_completedAchievements
[fields
[0].GetUInt32()];
245 ca
.date
= time_t(fields
[1].GetUInt64());
247 } while(achievementResult
->NextRow());
248 delete achievementResult
;
255 Field
*fields
= criteriaResult
->Fetch();
257 uint32 id
= fields
[0].GetUInt32();
258 uint32 counter
= fields
[1].GetUInt32();
259 time_t date
= time_t(fields
[2].GetUInt64());
261 AchievementCriteriaEntry
const* criteria
= sAchievementCriteriaStore
.LookupEntry(id
);
262 if(!criteria
|| criteria
->timeLimit
&& date
+ criteria
->timeLimit
< time(NULL
))
265 CriteriaProgress
& progress
= m_criteriaProgress
[id
];
266 progress
.counter
= counter
;
267 progress
.date
= date
;
268 progress
.changed
= false;
269 } while(criteriaResult
->NextRow());
270 delete criteriaResult
;
275 void AchievementMgr::SendAchievementEarned(AchievementEntry
const* achievement
)
277 sLog
.outDebug("AchievementMgr::SendAchievementEarned(%u)", achievement
->ID
);
279 const char *msg
= "|Hplayer:$N|h[$N]|h has earned the achievement $a!";
280 if(Guild
* guild
= objmgr
.GetGuildById(GetPlayer()->GetGuildId()))
282 WorldPacket
data(SMSG_MESSAGECHAT
, 200);
283 data
<< uint8(CHAT_MSG_GUILD_ACHIEVEMENT
);
284 data
<< uint32(LANG_UNIVERSAL
);
285 data
<< uint64(GetPlayer()->GetGUID());
287 data
<< uint64(GetPlayer()->GetGUID());
288 data
<< uint32(strlen(msg
)+1);
291 data
<< uint32(achievement
->ID
);
292 guild
->BroadcastPacket(&data
);
294 if(achievement
->flags
& (ACHIEVEMENT_FLAG_REALM_FIRST_KILL
|ACHIEVEMENT_FLAG_REALM_FIRST_REACH
))
296 // broadcast realm first reached
297 WorldPacket
data(SMSG_SERVER_FIRST_ACHIEVEMENT
, strlen(GetPlayer()->GetName())+1+8+4+4);
298 data
<< GetPlayer()->GetName();
299 data
<< uint64(GetPlayer()->GetGUID());
300 data
<< uint32(achievement
->ID
);
301 data
<< uint32(0); // 1=link supplied string as player name, 0=display plain string
302 sWorld
.SendGlobalMessage(&data
);
306 WorldPacket
data(SMSG_MESSAGECHAT
, 200);
307 data
<< uint8(CHAT_MSG_ACHIEVEMENT
);
308 data
<< uint32(LANG_UNIVERSAL
);
309 data
<< uint64(GetPlayer()->GetGUID());
311 data
<< uint64(GetPlayer()->GetGUID());
312 data
<< uint32(strlen(msg
)+1);
315 data
<< uint32(achievement
->ID
);
316 GetPlayer()->SendMessageToSet(&data
, true);
319 WorldPacket
data(SMSG_ACHIEVEMENT_EARNED
, 8+4+8);
320 data
.append(GetPlayer()->GetPackGUID());
321 data
<< uint32(achievement
->ID
);
322 data
<< uint32(secsToTimeBitFields(time(NULL
)));
324 GetPlayer()->SendMessageToSet(&data
, true);
327 void AchievementMgr::SendCriteriaUpdate(uint32 id
, CriteriaProgress
const* progress
)
329 WorldPacket
data(SMSG_CRITERIA_UPDATE
, 8+4+8);
332 // the counter is packed like a packed Guid
333 data
.appendPackGUID(progress
->counter
);
335 data
.append(GetPlayer()->GetPackGUID());
337 data
<< uint32(secsToTimeBitFields(progress
->date
));
338 data
<< uint32(0); // timer 1
339 data
<< uint32(0); // timer 2
340 GetPlayer()->SendMessageToSet(&data
, true);
344 * called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet
346 void AchievementMgr::CheckAllAchievementCriteria()
348 // suppress sending packets
349 for(uint32 i
=0; i
<ACHIEVEMENT_CRITERIA_TYPE_TOTAL
; ++i
)
350 UpdateAchievementCriteria(AchievementCriteriaTypes(i
));
354 * this function will be called whenever the user might have done a criteria relevant action
356 void AchievementMgr::UpdateAchievementCriteria(AchievementCriteriaTypes type
, uint32 miscvalue1
, uint32 miscvalue2
, Unit
*unit
, uint32 time
)
358 sLog
.outDetail("AchievementMgr::UpdateAchievementCriteria(%u, %u, %u, %u)", type
, miscvalue1
, miscvalue2
, time
);
360 if (!sWorld
.getConfig(CONFIG_GM_ALLOW_ACHIEVEMENT_GAINS
) && m_player
->GetSession()->GetSecurity() > SEC_PLAYER
)
363 AchievementCriteriaEntryList
const& achievementCriteriaList
= achievementmgr
.GetAchievementCriteriaByType(type
);
364 for(AchievementCriteriaEntryList::const_iterator i
= achievementCriteriaList
.begin(); i
!=achievementCriteriaList
.end(); ++i
)
366 AchievementCriteriaEntry
const *achievementCriteria
= (*i
);
368 // don't update already completed criteria
369 if(IsCompletedCriteria(achievementCriteria
))
372 if(achievementCriteria
->groupFlag
& ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP
&& GetPlayer()->GetGroup())
375 AchievementEntry
const *achievement
= sAchievementStore
.LookupEntry(achievementCriteria
->referredAchievement
);
379 if(achievement
->factionFlag
== ACHIEVEMENT_FACTION_FLAG_HORDE
&& GetPlayer()->GetTeam() != HORDE
||
380 achievement
->factionFlag
== ACHIEVEMENT_FACTION_FLAG_ALLIANCE
&& GetPlayer()->GetTeam() != ALLIANCE
)
385 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL
:
386 SetCriteriaProgress(achievementCriteria
, GetPlayer()->getLevel());
388 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT
:
389 SetCriteriaProgress(achievementCriteria
, GetPlayer()->GetByteValue(PLAYER_BYTES_2
, 2)+1);
391 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE
:
392 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
395 if(achievementCriteria
->kill_creature
.creatureID
!= miscvalue1
)
397 SetCriteriaProgress(achievementCriteria
, miscvalue2
, true);
399 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL
:
400 if(uint32 skillvalue
= GetPlayer()->GetBaseSkillValue(achievementCriteria
->reach_skill_level
.skillID
))
401 SetCriteriaProgress(achievementCriteria
, skillvalue
);
403 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT
:
406 for(QuestStatusMap::iterator itr
= GetPlayer()->getQuestStatusMap().begin(); itr
!=GetPlayer()->getQuestStatusMap().end(); itr
++)
407 if(itr
->second
.m_rewarded
)
409 SetCriteriaProgress(achievementCriteria
, counter
);
412 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE
:
415 for(QuestStatusMap::iterator itr
= GetPlayer()->getQuestStatusMap().begin(); itr
!=GetPlayer()->getQuestStatusMap().end(); itr
++)
417 Quest
const* quest
= objmgr
.GetQuestTemplate(itr
->first
);
418 if(itr
->second
.m_rewarded
&& quest
->GetZoneOrSort() == achievementCriteria
->complete_quests_in_zone
.zoneID
)
421 SetCriteriaProgress(achievementCriteria
, counter
);
424 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST
:
425 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
428 SetCriteriaProgress(achievementCriteria
, miscvalue1
, true);
430 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND
:
431 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
434 if(GetPlayer()->GetMapId() != achievementCriteria
->complete_battleground
.mapID
)
436 SetCriteriaProgress(achievementCriteria
, miscvalue1
, true);
438 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL
:
439 if(GetPlayer()->HasSpell(achievementCriteria
->learn_spell
.spellID
))
440 SetCriteriaProgress(achievementCriteria
, 1);
442 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT
:
443 if(m_completedAchievements
.find(achievementCriteria
->complete_achievement
.linkedAchievement
) != m_completedAchievements
.end())
444 SetCriteriaProgress(achievementCriteria
, 1);
446 case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP
:
447 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
450 if(GetPlayer()->GetMapId() != achievementCriteria
->death_at_map
.mapID
)
452 SetCriteriaProgress(achievementCriteria
, 1, true);
454 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE
:
455 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
458 if(miscvalue1
!= achievementCriteria
->killed_by_creature
.creatureEntry
)
460 SetCriteriaProgress(achievementCriteria
, 1, true);
462 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER
:
463 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
466 SetCriteriaProgress(achievementCriteria
, 1, true);
468 case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING
:
470 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
473 if(achievement
->ID
== 1260)
475 if(Player::GetDrunkenstateByValue(GetPlayer()->GetDrunkValue()) != DRUNKEN_SMASHED
)
477 // TODO: hardcoding eventid is bad, it can differ from DB to DB - maye implement something using HolidayNames.dbc?
478 if(!gameeventmgr
.IsActiveEvent(26))
481 // miscvalue1 is the ingame fallheight*100 as stored in dbc
482 SetCriteriaProgress(achievementCriteria
, miscvalue1
);
485 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST
:
486 if(GetPlayer()->GetQuestRewardStatus(achievementCriteria
->complete_quest
.questID
))
487 SetCriteriaProgress(achievementCriteria
, 1);
489 case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM
:
490 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
493 if(achievementCriteria
->use_item
.itemID
!= miscvalue1
)
495 SetCriteriaProgress(achievementCriteria
, 1, true);
497 case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM
:
498 // speedup for non-login case
499 if(miscvalue1
&& achievementCriteria
->own_item
.itemID
!=miscvalue1
)
501 SetCriteriaProgress(achievementCriteria
, GetPlayer()->GetItemCount(achievementCriteria
->own_item
.itemID
, true));
503 case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM
:
504 // You _have_ to loot that item, just owning it when logging in does _not_ count!
507 if(miscvalue1
!= achievementCriteria
->own_item
.itemID
)
509 SetCriteriaProgress(achievementCriteria
, miscvalue2
, true);
511 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET
:
512 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2
:
513 if (!miscvalue1
|| miscvalue1
!= achievementCriteria
->be_spell_target
.spellID
)
515 SetCriteriaProgress(achievementCriteria
, 1, true);
517 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL
:
518 if (!miscvalue1
|| miscvalue1
!= achievementCriteria
->cast_spell
.spellID
)
520 SetCriteriaProgress(achievementCriteria
, 1, true);
522 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2
:
524 if (!miscvalue1
|| miscvalue1
!= achievementCriteria
->cast_spell
.spellID
)
527 // those requirements couldn't be found in the dbc
528 if (CriteriaCastSpellRequirement
const* requirement
= AchievementGlobalMgr::GetCriteriaCastSpellRequirement(achievementCriteria
))
533 if (requirement
->creatureEntry
&& unit
->GetEntry() != requirement
->creatureEntry
)
536 if (requirement
->playerRace
&& (unit
->GetTypeId() != TYPEID_PLAYER
|| unit
->getRace()!=requirement
->playerRace
))
539 if (requirement
->playerClass
&& (unit
->GetTypeId() != TYPEID_PLAYER
|| unit
->getClass()!=requirement
->playerClass
))
543 SetCriteriaProgress(achievementCriteria
, 1, true);
546 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS
:
548 uint32 spellCount
= 0;
549 for (PlayerSpellMap::const_iterator spellIter
= GetPlayer()->GetSpellMap().begin();
550 spellIter
!= GetPlayer()->GetSpellMap().end();
553 for(SkillLineAbilityMap::const_iterator skillIter
= spellmgr
.GetBeginSkillLineAbilityMap(spellIter
->first
);
554 skillIter
!= spellmgr
.GetEndSkillLineAbilityMap(spellIter
->first
);
557 if(skillIter
->second
->skillId
== achievementCriteria
->learn_skilline_spell
.skillLine
)
561 SetCriteriaProgress(achievementCriteria
, spellCount
);
564 case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP
:
566 // skip for login case
569 SetCriteriaProgress(achievementCriteria
, 1);
572 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION
:
574 int32 reputation
= GetPlayer()->GetReputation(achievementCriteria
->gain_reputation
.factionID
);
576 SetCriteriaProgress(achievementCriteria
, reputation
);
579 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION
:
582 const FactionStateList factionStateList
= GetPlayer()->GetFactionStateList();
583 for (FactionStateList::const_iterator iter
= factionStateList
.begin(); iter
!= factionStateList
.end(); iter
++)
585 if(GetPlayer()->ReputationToRank(iter
->second
.Standing
) >= REP_EXALTED
)
588 SetCriteriaProgress(achievementCriteria
, counter
);
591 case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA
:
593 WorldMapOverlayEntry
const* worldOverlayEntry
= sWorldMapOverlayStore
.LookupEntry(achievementCriteria
->explore_area
.areaReference
);
594 if(!worldOverlayEntry
)
597 int32 exploreFlag
= GetAreaFlagByAreaID(worldOverlayEntry
->areatableID
);
601 uint32 playerIndexOffset
= uint32(exploreFlag
) / 32;
602 uint32 mask
= 1<< (uint32(exploreFlag
) % 32);
604 if(GetPlayer()->GetUInt32Value(PLAYER_EXPLORED_ZONES_1
+ playerIndexOffset
) & mask
)
605 SetCriteriaProgress(achievementCriteria
, 1);
608 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT
:
609 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT
:
611 // miscvalue1 = itemid
612 // miscvalue2 = diced value
615 if(miscvalue2
!= achievementCriteria
->roll_greed_on_loot
.rollValue
)
617 ItemPrototype
const *pProto
= objmgr
.GetItemPrototype( miscvalue1
);
619 uint32 requiredItemLevel
= 0;
620 if (achievementCriteria
->ID
== 2412 || achievementCriteria
->ID
== 2358)
621 requiredItemLevel
= 185;
623 if(!pProto
|| pProto
->ItemLevel
<requiredItemLevel
)
625 SetCriteriaProgress(achievementCriteria
, 1, true);
629 if(IsCompletedCriteria(achievementCriteria
))
630 CompletedCriteria(achievementCriteria
);
634 bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry
const* achievementCriteria
)
636 AchievementEntry
const* achievement
= sAchievementStore
.LookupEntry(achievementCriteria
->referredAchievement
);
640 // counter can never complete
641 if(achievement
->flags
& ACHIEVEMENT_FLAG_COUNTER
)
644 if(achievement
->flags
& (ACHIEVEMENT_FLAG_REALM_FIRST_REACH
| ACHIEVEMENT_FLAG_REALM_FIRST_KILL
))
646 // someone on this realm has already completed that achievement
647 if(achievementmgr
.IsRealmCompleted(achievement
))
651 CriteriaProgressMap::const_iterator itr
= m_criteriaProgress
.find(achievementCriteria
->ID
);
652 if(itr
== m_criteriaProgress
.end())
655 CriteriaProgress
const* progress
= &itr
->second
;
657 switch(achievementCriteria
->requiredType
)
659 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL
:
660 if(achievement
->ID
== 467 && GetPlayer()->getClass() != CLASS_SHAMAN
||
661 achievement
->ID
== 466 && GetPlayer()->getClass() != CLASS_DRUID
||
662 achievement
->ID
== 465 && GetPlayer()->getClass() != CLASS_PALADIN
||
663 achievement
->ID
== 464 && GetPlayer()->getClass() != CLASS_PRIEST
||
664 achievement
->ID
== 463 && GetPlayer()->getClass() != CLASS_WARLOCK
||
665 achievement
->ID
== 462 && GetPlayer()->getClass() != CLASS_HUNTER
||
666 achievement
->ID
== 461 && GetPlayer()->getClass() != CLASS_DEATH_KNIGHT
||
667 achievement
->ID
== 460 && GetPlayer()->getClass() != CLASS_MAGE
||
668 achievement
->ID
== 459 && GetPlayer()->getClass() != CLASS_WARRIOR
||
669 achievement
->ID
== 458 && GetPlayer()->getClass() != CLASS_ROGUE
||
671 achievement
->ID
== 1404 && GetPlayer()->getRace() != RACE_GNOME
||
672 achievement
->ID
== 1405 && GetPlayer()->getRace() != RACE_BLOODELF
||
673 achievement
->ID
== 1406 && GetPlayer()->getRace() != RACE_DRAENEI
||
674 achievement
->ID
== 1407 && GetPlayer()->getRace() != RACE_DWARF
||
675 achievement
->ID
== 1408 && GetPlayer()->getRace() != RACE_HUMAN
||
676 achievement
->ID
== 1409 && GetPlayer()->getRace() != RACE_NIGHTELF
||
677 achievement
->ID
== 1410 && GetPlayer()->getRace() != RACE_ORC
||
678 achievement
->ID
== 1411 && GetPlayer()->getRace() != RACE_TAUREN
||
679 achievement
->ID
== 1412 && GetPlayer()->getRace() != RACE_TROLL
||
680 achievement
->ID
== 1413 && GetPlayer()->getRace() != RACE_UNDEAD_PLAYER
)
682 return progress
->counter
>= achievementCriteria
->reach_level
.level
;
683 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT
:
684 return progress
->counter
>= achievementCriteria
->buy_bank_slot
.numberOfSlots
;
685 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE
:
686 return progress
->counter
>= achievementCriteria
->kill_creature
.creatureCount
;
687 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT
:
688 return progress
->counter
>= 1;
689 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL
:
690 return progress
->counter
>= achievementCriteria
->reach_skill_level
.skillLevel
;
691 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT
:
692 return progress
->counter
>= achievementCriteria
->complete_quest_count
.totalQuestCount
;
693 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE
:
694 return progress
->counter
>= achievementCriteria
->complete_quests_in_zone
.questCount
;
695 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST
:
696 return progress
->counter
>= achievementCriteria
->complete_daily_quest
.questCount
;
697 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL
:
698 return progress
->counter
>= 1;
699 case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING
:
700 return progress
->counter
>= achievementCriteria
->fall_without_dying
.fallHeight
;
701 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST
:
702 return progress
->counter
>= 1;
703 case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM
:
704 return progress
->counter
>= achievementCriteria
->use_item
.itemCount
;
705 case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM
:
706 return progress
->counter
>= achievementCriteria
->own_item
.itemCount
;
707 case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM
:
708 return progress
->counter
>= achievementCriteria
->loot_item
.itemCount
;
709 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET
:
710 case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2
:
711 return progress
->counter
>= achievementCriteria
->be_spell_target
.spellCount
;
712 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL
:
713 case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2
:
714 return progress
->counter
>= achievementCriteria
->cast_spell
.castCount
;
715 case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS
:
716 return progress
->counter
>= achievementCriteria
->learn_skilline_spell
.spellCount
;
717 case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP
:
718 return progress
->counter
>= achievementCriteria
->visit_barber
.numberOfVisits
;
719 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION
:
720 return progress
->counter
>= achievementCriteria
->gain_reputation
.reputationAmount
;
721 case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION
:
722 return progress
->counter
>= achievementCriteria
->gain_exalted_reputation
.numberOfExaltedFactions
;
723 case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA
:
724 return progress
->counter
>= 1;
725 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT
:
726 case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT
:
727 return progress
->counter
>= achievementCriteria
->roll_greed_on_loot
.count
;
729 // handle all statistic-only criteria here
730 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND
:
731 case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP
:
732 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE
:
733 case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER
:
739 void AchievementMgr::CompletedCriteria(AchievementCriteriaEntry
const* criteria
)
741 AchievementEntry
const* achievement
= sAchievementStore
.LookupEntry(criteria
->referredAchievement
);
744 // counter can never complete
745 if(achievement
->flags
& ACHIEVEMENT_FLAG_COUNTER
)
748 if(criteria
->completionFlag
& ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
|| GetAchievementCompletionState(achievement
)==ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED
)
750 CompletedAchievement(achievement
);
754 // TODO: achievement 705 requires 4 criteria to be fulfilled
755 AchievementCompletionState
AchievementMgr::GetAchievementCompletionState(AchievementEntry
const* entry
)
757 if(m_completedAchievements
.find(entry
->ID
)!=m_completedAchievements
.end())
758 return ACHIEVEMENT_COMPLETED_COMPLETED_STORED
;
760 bool foundOutstanding
= false;
761 for (uint32 entryId
= 0; entryId
<sAchievementCriteriaStore
.GetNumRows(); entryId
++)
763 AchievementCriteriaEntry
const* criteria
= sAchievementCriteriaStore
.LookupEntry(entryId
);
764 if(!criteria
|| criteria
->referredAchievement
!= entry
->ID
)
767 if(IsCompletedCriteria(criteria
) && criteria
->completionFlag
& ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
)
768 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED
;
770 // found an umcompleted criteria, but DONT return false yet - there might be a completed criteria with ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
771 if(!IsCompletedCriteria(criteria
))
772 foundOutstanding
= true;
775 return ACHIEVEMENT_COMPLETED_NONE
;
777 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED
;
780 void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry
const* entry
, uint32 newValue
, bool relative
)
782 sLog
.outDetail("AchievementMgr::SetCriteriaProgress(%u, %u)", entry
->ID
, newValue
);
783 CriteriaProgress
*progress
= NULL
;
785 CriteriaProgressMap::iterator iter
= m_criteriaProgress
.find(entry
->ID
);
787 if(iter
== m_criteriaProgress
.end())
789 // not create record for 0 counter
793 progress
= &m_criteriaProgress
[entry
->ID
];
794 progress
->counter
= newValue
;
795 progress
->date
= time(NULL
);
799 progress
= &iter
->second
;
801 newValue
+= progress
->counter
;
803 // not update (not mark as changed) if counter will have same value
804 if(progress
->counter
== newValue
)
807 progress
->counter
= newValue
;
810 progress
->changed
= true;
814 time_t now
= time(NULL
);
815 if(progress
->date
+ entry
->timeLimit
< now
)
817 progress
->counter
= 1;
819 // also it seems illogical, the timeframe will be extended at every criteria update
820 progress
->date
= now
;
822 SendCriteriaUpdate(entry
->ID
,progress
);
825 void AchievementMgr::CompletedAchievement(AchievementEntry
const* achievement
)
827 sLog
.outDetail("AchievementMgr::CompletedAchievement(%u)", achievement
->ID
);
828 if(achievement
->flags
& ACHIEVEMENT_FLAG_COUNTER
|| m_completedAchievements
.find(achievement
->ID
)!=m_completedAchievements
.end())
831 SendAchievementEarned(achievement
);
832 CompletedAchievementData
& ca
= m_completedAchievements
[achievement
->ID
];
833 ca
.date
= time(NULL
);
836 // don't insert for ACHIEVEMENT_FLAG_REALM_FIRST_KILL since otherwise only the first group member would reach that achievement
837 // TODO: where do set this instead?
838 if(!(achievement
->flags
& ACHIEVEMENT_FLAG_REALM_FIRST_KILL
))
839 achievementmgr
.SetRealmCompleted(achievement
);
841 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT
);
843 // reward items and titles if any
844 AchievementReward
const* reward
= achievementmgr
.GetAchievementReward(achievement
);
851 if(uint32 titleId
= reward
->titleId
[GetPlayer()->GetTeam() == HORDE
?0:1])
853 if(CharTitlesEntry
const* titleEntry
= sCharTitlesStore
.LookupEntry(titleId
))
854 GetPlayer()->SetTitle(titleEntry
);
860 Item
* item
= reward
->itemId
? Item::CreateItem(reward
->itemId
,1,GetPlayer ()) : NULL
;
865 // save new item before send
866 item
->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted
869 mi
.AddItem(item
->GetGUIDLow(), item
->GetEntry(), item
);
872 int loc_idx
= GetPlayer()->GetSession()->GetSessionDbLocaleIndex();
875 std::string subject
= reward
->subject
;
876 std::string text
= reward
->text
;
879 if(AchievementRewardLocale
const* loc
= achievementmgr
.GetAchievementRewardLocale(achievement
))
881 if (loc
->subject
.size() > size_t(loc_idx
) && !loc
->subject
[loc_idx
].empty())
882 subject
= loc
->subject
[loc_idx
];
883 if (loc
->text
.size() > size_t(loc_idx
) && !loc
->text
[loc_idx
].empty())
884 text
= loc
->text
[loc_idx
];
888 uint32 itemTextId
= objmgr
.CreateItemText( text
);
890 WorldSession::SendMailTo(GetPlayer(), MAIL_CREATURE
, MAIL_STATIONERY_NORMAL
, reward
->sender
, GetPlayer()->GetGUIDLow(), subject
, itemTextId
, &mi
, 0, 0, MAIL_CHECK_MASK_NONE
);
894 void AchievementMgr::SendAllAchievementData()
896 // since we don't know the exact size of the packed GUIDs this is just an approximation
897 WorldPacket
data(SMSG_ALL_ACHIEVEMENT_DATA
, 4*2+m_completedAchievements
.size()*4*2+m_completedAchievements
.size()*7*4);
898 BuildAllDataPacket(&data
);
899 GetPlayer()->GetSession()->SendPacket(&data
);
902 void AchievementMgr::SendRespondInspectAchievements(Player
* player
)
904 // since we don't know the exact size of the packed GUIDs this is just an approximation
905 WorldPacket
data(SMSG_RESPOND_INSPECT_ACHIEVEMENTS
, 4+4*2+m_completedAchievements
.size()*4*2+m_completedAchievements
.size()*7*4);
906 data
.append(GetPlayer()->GetPackGUID());
907 BuildAllDataPacket(&data
);
908 player
->GetSession()->SendPacket(&data
);
912 * used by both SMSG_ALL_ACHIEVEMENT_DATA and SMSG_RESPOND_INSPECT_ACHIEVEMENT
914 void AchievementMgr::BuildAllDataPacket(WorldPacket
*data
)
916 for(CompletedAchievementMap::const_iterator iter
= m_completedAchievements
.begin(); iter
!=m_completedAchievements
.end(); ++iter
)
918 *data
<< uint32(iter
->first
);
919 *data
<< uint32(secsToTimeBitFields(iter
->second
.date
));
923 for(CriteriaProgressMap::const_iterator iter
= m_criteriaProgress
.begin(); iter
!=m_criteriaProgress
.end(); ++iter
)
925 *data
<< uint32(iter
->first
);
926 data
->appendPackGUID(iter
->second
.counter
);
927 data
->append(GetPlayer()->GetPackGUID());
929 *data
<< uint32(secsToTimeBitFields(iter
->second
.date
));
937 //==========================================================
938 AchievementCriteriaEntryList
const& AchievementGlobalMgr::GetAchievementCriteriaByType(AchievementCriteriaTypes type
)
940 return m_AchievementCriteriasByType
[type
];
943 void AchievementGlobalMgr::LoadAchievementCriteriaList()
945 if(sAchievementCriteriaStore
.GetNumRows()==0)
951 sLog
.outErrorDb(">> Loaded 0 achievement criteria.");
955 barGoLink
bar( sAchievementCriteriaStore
.GetNumRows() );
956 for (uint32 entryId
= 0; entryId
<sAchievementCriteriaStore
.GetNumRows(); entryId
++)
960 AchievementCriteriaEntry
const* criteria
= sAchievementCriteriaStore
.LookupEntry(entryId
);
964 m_AchievementCriteriasByType
[criteria
->requiredType
].push_back(criteria
);
968 sLog
.outString(">> Loaded %u achievement criteria.",m_AchievementCriteriasByType
->size());
972 void AchievementGlobalMgr::LoadCompletedAchievements()
974 QueryResult
*result
= CharacterDatabase
.Query("SELECT achievement FROM character_achievement GROUP BY achievement");
982 sLog
.outString(">> Loaded 0 realm completed achievements . DB table `character_achievement` is empty.");
986 barGoLink
bar(result
->GetRowCount());
990 Field
*fields
= result
->Fetch();
991 m_allCompletedAchievements
.insert(fields
[0].GetUInt32());
992 } while(result
->NextRow());
997 sLog
.outString(">> Loaded %u realm completed achievements.",m_allCompletedAchievements
.size());
1000 void AchievementGlobalMgr::LoadRewards()
1002 m_achievementRewards
.clear(); // need for reload case
1005 QueryResult
*result
= WorldDatabase
.Query("SELECT entry, title_A, title_H, item, sender, subject, text FROM achievement_reward");
1014 sLog
.outErrorDb(">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
1018 barGoLink
bar(result
->GetRowCount());
1024 Field
*fields
= result
->Fetch();
1025 uint32 entry
= fields
[0].GetUInt32();
1026 if (!sAchievementStore
.LookupEntry(entry
))
1028 sLog
.outErrorDb( "Table `achievement_reward` has wrong achievement (Entry: %u), ignore", entry
);
1032 AchievementReward reward
;
1033 reward
.titleId
[0] = fields
[1].GetUInt32();
1034 reward
.titleId
[1] = fields
[2].GetUInt32();
1035 reward
.itemId
= fields
[3].GetUInt32();
1036 reward
.sender
= fields
[4].GetUInt32();
1037 reward
.subject
= fields
[5].GetCppString();
1038 reward
.text
= fields
[6].GetCppString();
1040 if ((reward
.titleId
[0]==0)!=(reward
.titleId
[1]==0))
1041 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]);
1043 // must be title or mail at least
1044 if (!reward
.titleId
[0] && !reward
.titleId
[1] && !reward
.sender
)
1046 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) not have title or item reward data, ignore.", entry
);
1050 if (reward
.titleId
[0])
1052 CharTitlesEntry
const* titleEntry
= sCharTitlesStore
.LookupEntry(reward
.titleId
[0]);
1055 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry
, reward
.titleId
[0]);
1056 reward
.titleId
[0] = 0;
1060 if (reward
.titleId
[1])
1062 CharTitlesEntry
const* titleEntry
= sCharTitlesStore
.LookupEntry(reward
.titleId
[1]);
1065 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry
, reward
.titleId
[1]);
1066 reward
.titleId
[1] = 0;
1070 //check mail data before item for report including wrong item case
1073 if (!objmgr
.GetCreatureTemplate(reward
.sender
))
1075 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid creature entry %u as sender, mail reward skipped.", entry
, reward
.sender
);
1082 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have item reward, item will not rewarded", entry
);
1084 if (!reward
.subject
.empty())
1085 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail subject.", entry
);
1087 if (!reward
.text
.empty())
1088 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail text.", entry
);
1093 if (!objmgr
.GetItemPrototype(reward
.itemId
))
1095 sLog
.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid item id %u, reward mail will be without item.", entry
, reward
.itemId
);
1100 m_achievementRewards
[entry
] = reward
;
1102 } while (result
->NextRow());
1107 sLog
.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales
.size() );
1110 void AchievementGlobalMgr::LoadRewardLocales()
1112 m_achievementRewardLocales
.clear(); // need for reload case
1114 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");
1123 sLog
.outString(">> Loaded 0 achievement reward locale strings. DB table `locales_achievement_reward` is empty.");
1127 barGoLink
bar(result
->GetRowCount());
1131 Field
*fields
= result
->Fetch();
1134 uint32 entry
= fields
[0].GetUInt32();
1136 if(m_achievementRewards
.find(entry
)==m_achievementRewards
.end())
1138 sLog
.outErrorDb( "Table `locales_achievement_reward` (Entry: %u) has locale strings for not existed achievement reward .", entry
);
1142 AchievementRewardLocale
& data
= m_achievementRewardLocales
[entry
];
1144 for(int i
= 1; i
< MAX_LOCALE
; ++i
)
1146 std::string str
= fields
[1+2*(i
-1)].GetCppString();
1149 int idx
= objmgr
.GetOrNewIndexForLocale(LocaleConstant(i
));
1152 if(data
.subject
.size() <= idx
)
1153 data
.subject
.resize(idx
+1);
1155 data
.subject
[idx
] = str
;
1158 str
= fields
[1+2*(i
-1)+1].GetCppString();
1161 int idx
= objmgr
.GetOrNewIndexForLocale(LocaleConstant(i
));
1164 if(data
.text
.size() <= idx
)
1165 data
.text
.resize(idx
+1);
1167 data
.text
[idx
] = str
;
1171 } while (result
->NextRow());
1176 sLog
.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales
.size() );