2 * Copyright (C) 2005-2008 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"
28 AchievementMgr::AchievementMgr(Player
*player
)
33 AchievementMgr::~AchievementMgr()
35 for(CriteriaProgressMap::iterator iter
= m_criteriaProgress
.begin(); iter
!=m_criteriaProgress
.end(); ++iter
)
37 m_criteriaProgress
.clear();
40 void AchievementMgr::SaveToDB()
42 if(!m_completedAchievements
.empty())
44 CharacterDatabase
.PExecute("DELETE FROM character_achievement WHERE guid = %u", GetPlayer()->GetGUIDLow());
46 std::ostringstream ss
;
47 ss
<< "INSERT INTO character_achievement (guid, achievement, date) VALUES ";
48 for(CompletedAchievementMap::iterator iter
= m_completedAchievements
.begin(); iter
!=m_completedAchievements
.end(); iter
++)
50 if(iter
!= m_completedAchievements
.begin())
52 ss
<< "("<<GetPlayer()->GetGUIDLow() << ", " << iter
->first
<< ", " << iter
->second
<< ")";
54 CharacterDatabase
.Execute( ss
.str().c_str() );
57 if(!m_criteriaProgress
.empty())
59 CharacterDatabase
.PExecute("DELETE FROM character_achievement_progress WHERE guid = %u", GetPlayer()->GetGUIDLow());
61 std::ostringstream ss
;
62 ss
<< "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES ";
63 for(CriteriaProgressMap::iterator iter
= m_criteriaProgress
.begin(); iter
!=m_criteriaProgress
.end(); ++iter
)
65 if(iter
!= m_criteriaProgress
.begin())
67 ss
<< "(" << GetPlayer()->GetGUIDLow() << ", " << iter
->first
<< ", " << iter
->second
->counter
<< ", " << iter
->second
->date
<< ")";
69 CharacterDatabase
.Execute( ss
.str().c_str() );
73 void AchievementMgr::LoadFromDB(QueryResult
*achievementResult
, QueryResult
*criteriaResult
)
79 Field
*fields
= achievementResult
->Fetch();
80 m_completedAchievements
[fields
[0].GetUInt32()] = fields
[1].GetUInt32();
81 } while(achievementResult
->NextRow());
82 delete achievementResult
;
89 Field
*fields
= criteriaResult
->Fetch();
90 CriteriaProgress
*progress
= new CriteriaProgress(fields
[0].GetUInt32(), fields
[1].GetUInt32(), fields
[2].GetUInt64());
91 m_criteriaProgress
[progress
->id
] = progress
;
92 } while(criteriaResult
->NextRow());
93 delete criteriaResult
;
98 void AchievementMgr::SendAchievementEarned(uint32 achievementId
)
100 sLog
.outString("AchievementMgr::SendAchievementEarned(%u)", achievementId
);
102 WorldPacket
data(SMSG_MESSAGECHAT
, 200);
103 data
<< uint8(CHAT_MSG_ACHIEVEMENT
);
104 data
<< uint32(LANG_UNIVERSAL
);
105 data
<< uint64(GetPlayer()->GetGUID());
107 data
<< uint64(GetPlayer()->GetGUID());
108 const char *msg
= "|Hplayer:$N|h[$N]|h has earned the achievement $a!";
109 data
<< uint32(strlen(msg
)+1);
112 data
<< uint32(achievementId
);
113 GetPlayer()->SendMessageToSet(&data
, true);
115 if(Guild
* guild
= objmgr
.GetGuildById(GetPlayer()->GetGuildId()))
117 data
.Initialize(SMSG_MESSAGECHAT
, 200);
118 data
<< uint8(CHAT_MSG_GUILD_ACHIEVEMENT
);
119 data
<< uint32(LANG_UNIVERSAL
);
120 data
<< uint64(GetPlayer()->GetGUID());
122 data
<< uint64(GetPlayer()->GetGUID());
123 data
<< uint32(strlen(msg
)+1);
126 data
<< uint32(achievementId
);
127 guild
->BroadcastPacket(&data
);
130 data
.Initialize(SMSG_ACHIEVEMENT_EARNED
, 8+4+8);
131 data
.append(GetPlayer()->GetPackGUID());
132 data
<< uint32(achievementId
);
133 data
<< uint32(secsToTimeBitFields(time(NULL
)));
135 GetPlayer()->SendMessageToSet(&data
, true);
138 void AchievementMgr::SendCriteriaUpdate(CriteriaProgress
*progress
)
140 WorldPacket
data(SMSG_CRITERIA_UPDATE
, 8+4+8);
141 data
<< uint32(progress
->id
);
143 // the counter is packed like a packed Guid
144 data
.appendPackGUID(progress
->counter
);
146 data
.append(GetPlayer()->GetPackGUID());
148 data
<< uint32(secsToTimeBitFields(progress
->date
));
149 data
<< uint32(0); // timer 1
150 data
<< uint32(0); // timer 2
151 GetPlayer()->SendMessageToSet(&data
, true);
155 * called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet
157 void AchievementMgr::CheckAllAchievementCriteria()
159 // suppress sending packets
160 for(uint32 i
=0; i
<ACHIEVEMENT_CRITERIA_TYPE_TOTAL
; i
++)
161 UpdateAchievementCriteria(AchievementCriteriaTypes(i
));
165 * this function will be called whenever the user might have done a criteria relevant action
167 void AchievementMgr::UpdateAchievementCriteria(AchievementCriteriaTypes type
, uint32 miscvalue1
, uint32 miscvalue2
, uint32 time
)
169 sLog
.outString("AchievementMgr::UpdateAchievementCriteria(%u, %u, %u, %u)", type
, miscvalue1
, miscvalue2
, time
);
170 AchievementCriteriaEntryList
const& achievementCriteriaList
= objmgr
.GetAchievementCriteriaByType(type
);
171 for(AchievementCriteriaEntryList::const_iterator i
= achievementCriteriaList
.begin(); i
!=achievementCriteriaList
.end(); ++i
)
173 AchievementCriteriaEntry
const *achievementCriteria
= (*i
);
175 // don't update already completed criteria
176 if(IsCompletedCriteria(achievementCriteria
))
179 if(achievementCriteria
->groupFlag
& ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP
&& GetPlayer()->GetGroup())
182 AchievementEntry
const *achievement
= sAchievementStore
.LookupEntry(achievementCriteria
->referredAchievement
);
186 if(achievement
->factionFlag
== ACHIEVEMENT_FACTION_FLAG_HORDE
&& GetPlayer()->GetTeam() != HORDE
||
187 achievement
->factionFlag
== ACHIEVEMENT_FACTION_FLAG_ALLIANCE
&& GetPlayer()->GetTeam() != ALLIANCE
)
192 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL
:
193 SetCriteriaProgress(achievementCriteria
, GetPlayer()->getLevel());
195 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT
:
196 SetCriteriaProgress(achievementCriteria
, GetPlayer()->GetByteValue(PLAYER_BYTES_2
, 2)+1);
198 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE
:
199 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
202 if(achievementCriteria
->kill_creature
.creatureID
!= miscvalue1
)
204 SetCriteriaProgress(achievementCriteria
, miscvalue2
, true);
206 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL
:
207 if(uint32 skillvalue
= GetPlayer()->GetBaseSkillValue(achievementCriteria
->reach_skill_level
.skillID
))
208 SetCriteriaProgress(achievementCriteria
, skillvalue
);
210 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT
:
213 for(QuestStatusMap::iterator itr
= GetPlayer()->getQuestStatusMap().begin(); itr
!=GetPlayer()->getQuestStatusMap().end(); itr
++)
214 if(itr
->second
.m_rewarded
)
216 SetCriteriaProgress(achievementCriteria
, counter
);
219 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE
:
222 for(QuestStatusMap::iterator itr
= GetPlayer()->getQuestStatusMap().begin(); itr
!=GetPlayer()->getQuestStatusMap().end(); itr
++)
224 Quest
const* quest
= objmgr
.GetQuestTemplate(itr
->first
);
225 if(itr
->second
.m_rewarded
&& quest
->GetZoneOrSort() == achievementCriteria
->complete_quests_in_zone
.zoneID
)
228 SetCriteriaProgress(achievementCriteria
, counter
);
231 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST
:
232 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
235 SetCriteriaProgress(achievementCriteria
, miscvalue1
, true);
237 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND
:
238 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
241 if(GetPlayer()->GetMapID() != achievementCriteria
->complete_battleground
.mapID
)
243 SetCriteriaProgress(achievementCriteria
, miscvalue1
, true);
247 if(IsCompletedCriteria(achievementCriteria
))
248 CompletedCriteria(achievementCriteria
);
251 bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry
const* achievementCriteria
)
253 AchievementEntry
const* achievement
= sAchievementStore
.LookupEntry(achievementCriteria
->referredAchievement
);
257 // counter can never complete
258 if(achievement
->flags
& ACHIEVEMENT_FLAG_COUNTER
)
261 if(achievement
->flags
& (ACHIEVEMENT_FLAG_REALM_FIRST_REACH
| ACHIEVEMENT_FLAG_REALM_FIRST_KILL
))
263 // someone on this realm has already completed that achievement
264 if(objmgr
.allCompletedAchievements
.find(achievement
->ID
)!=objmgr
.allCompletedAchievements
.end())
268 CriteriaProgressMap::iterator itr
= m_criteriaProgress
.find(achievementCriteria
->ID
);
269 if(itr
== m_criteriaProgress
.end())
272 CriteriaProgress
*progress
= itr
->second
;
274 switch(achievementCriteria
->requiredType
)
276 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL
:
277 if(achievement
->ID
== 467 && GetPlayer()->getClass() != CLASS_SHAMAN
||
278 achievement
->ID
== 466 && GetPlayer()->getClass() != CLASS_DRUID
||
279 achievement
->ID
== 465 && GetPlayer()->getClass() != CLASS_PALADIN
||
280 achievement
->ID
== 464 && GetPlayer()->getClass() != CLASS_PRIEST
||
281 achievement
->ID
== 463 && GetPlayer()->getClass() != CLASS_WARLOCK
||
282 achievement
->ID
== 462 && GetPlayer()->getClass() != CLASS_HUNTER
||
283 achievement
->ID
== 461 && GetPlayer()->getClass() != CLASS_DEATH_KNIGHT
||
284 achievement
->ID
== 460 && GetPlayer()->getClass() != CLASS_MAGE
||
285 achievement
->ID
== 459 && GetPlayer()->getClass() != CLASS_WARRIOR
||
286 achievement
->ID
== 458 && GetPlayer()->getClass() != CLASS_ROGUE
||
288 achievement
->ID
== 1404 && GetPlayer()->getRace() != RACE_GNOME
||
289 achievement
->ID
== 1405 && GetPlayer()->getRace() != RACE_BLOODELF
||
290 achievement
->ID
== 1406 && GetPlayer()->getRace() != RACE_DRAENEI
||
291 achievement
->ID
== 1407 && GetPlayer()->getRace() != RACE_DWARF
||
292 achievement
->ID
== 1408 && GetPlayer()->getRace() != RACE_HUMAN
||
293 achievement
->ID
== 1409 && GetPlayer()->getRace() != RACE_NIGHTELF
||
294 achievement
->ID
== 1410 && GetPlayer()->getRace() != RACE_ORC
||
295 achievement
->ID
== 1411 && GetPlayer()->getRace() != RACE_TAUREN
||
296 achievement
->ID
== 1412 && GetPlayer()->getRace() != RACE_TROLL
||
297 achievement
->ID
== 1413 && GetPlayer()->getRace() != RACE_UNDEAD_PLAYER
)
299 return progress
->counter
>= achievementCriteria
->reach_level
.level
;
300 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT
:
301 return progress
->counter
>= achievementCriteria
->buy_bank_slot
.numberOfSlots
;
302 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE
:
303 return progress
->counter
>= achievementCriteria
->kill_creature
.creatureCount
;
304 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT
:
305 return m_completedAchievements
.find(achievementCriteria
->complete_achievement
.linkedAchievement
) != m_completedAchievements
.end();
306 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL
:
307 return progress
->counter
>= achievementCriteria
->reach_skill_level
.skillLevel
;
308 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE
:
309 return progress
->counter
>= achievementCriteria
->complete_quests_in_zone
.questCount
;
310 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST
:
311 return progress
->counter
>= achievementCriteria
->complete_daily_quest
.questCount
;
312 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND
:
313 // just used as a counter - return false
319 void AchievementMgr::CompletedCriteria(AchievementCriteriaEntry
const* criteria
)
321 AchievementEntry
const* achievement
= sAchievementStore
.LookupEntry(criteria
->referredAchievement
);
324 // counter can never complete
325 if(achievement
->flags
& ACHIEVEMENT_FLAG_COUNTER
)
328 if(criteria
->completionFlag
& ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
|| GetAchievementCompletionState(achievement
)==ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED
)
330 CompletedAchievement(achievement
);
334 // TODO: achievement 705 requires 4 criteria to be fulfilled
335 AchievementCompletionState
AchievementMgr::GetAchievementCompletionState(AchievementEntry
const* entry
)
337 if(m_completedAchievements
.find(entry
->ID
)!=m_completedAchievements
.end())
338 return ACHIEVEMENT_COMPLETED_COMPLETED_STORED
;
340 bool foundOutstanding
= false;
341 for (uint32 entryId
= 0; entryId
<sAchievementCriteriaStore
.GetNumRows(); entryId
++)
343 AchievementCriteriaEntry
const* criteria
= sAchievementCriteriaStore
.LookupEntry(entryId
);
344 if(!criteria
|| criteria
->referredAchievement
!= entry
->ID
)
347 if(IsCompletedCriteria(criteria
) && criteria
->completionFlag
& ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
)
348 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED
;
350 // found an umcompleted criteria, but DONT return false yet - there might be a completed criteria with ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL
351 if(!IsCompletedCriteria(criteria
))
352 foundOutstanding
= true;
355 return ACHIEVEMENT_COMPLETED_NONE
;
357 return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED
;
360 void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry
const* entry
, uint32 newValue
, bool relative
)
362 sLog
.outString("AchievementMgr::SetCriteriaProgress(%u, %u)", entry
->ID
, newValue
);
363 CriteriaProgress
*progress
= NULL
;
365 if(m_criteriaProgress
.find(entry
->ID
) == m_criteriaProgress
.end())
367 progress
= new CriteriaProgress(entry
->ID
, newValue
);
368 m_criteriaProgress
[entry
->ID
]=progress
;
372 progress
= m_criteriaProgress
[entry
->ID
];
374 newValue
+= progress
->counter
;
375 if(progress
->counter
== newValue
)
377 progress
->counter
= newValue
;
379 SendCriteriaUpdate(progress
);
382 void AchievementMgr::CompletedAchievement(AchievementEntry
const* achievement
)
384 sLog
.outString("AchievementMgr::CompletedAchievement(%u)", achievement
->ID
);
385 if(achievement
->flags
& ACHIEVEMENT_FLAG_COUNTER
|| m_completedAchievements
.find(achievement
->ID
)!=m_completedAchievements
.end())
388 SendAchievementEarned(achievement
->ID
);
389 m_completedAchievements
[achievement
->ID
] = time(NULL
);
391 // don't insert for ACHIEVEMENT_FLAG_REALM_FIRST_KILL since otherwise only the first group member would reach that achievement
392 // TODO: where do set this instead?
393 if(!(achievement
->flags
& ACHIEVEMENT_FLAG_REALM_FIRST_KILL
))
394 objmgr
.allCompletedAchievements
.insert(achievement
->ID
);
396 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT
);
397 // TODO: reward titles and items
400 void AchievementMgr::SendAllAchievementData()
402 // since we don't know the exact size of the packed GUIDs this is just an approximation
403 WorldPacket
data(SMSG_ALL_ACHIEVEMENT_DATA
, 4*2+m_completedAchievements
.size()*4*2+m_completedAchievements
.size()*7*4);
404 BuildAllDataPacket(&data
);
405 GetPlayer()->GetSession()->SendPacket(&data
);
408 void AchievementMgr::SendRespondInspectAchievements(Player
* player
)
410 // since we don't know the exact size of the packed GUIDs this is just an approximation
411 WorldPacket
data(SMSG_RESPOND_INSPECT_ACHIEVEMENTS
, 4+4*2+m_completedAchievements
.size()*4*2+m_completedAchievements
.size()*7*4);
412 data
.append(GetPlayer()->GetPackGUID());
413 BuildAllDataPacket(&data
);
414 player
->GetSession()->SendPacket(&data
);
418 * used by both SMSG_ALL_ACHIEVEMENT_DATA and SMSG_RESPOND_INSPECT_ACHIEVEMENT
420 void AchievementMgr::BuildAllDataPacket(WorldPacket
*data
)
422 for(CompletedAchievementMap::iterator iter
= m_completedAchievements
.begin(); iter
!=m_completedAchievements
.end(); ++iter
)
424 *data
<< uint32(iter
->first
);
425 *data
<< uint32(secsToTimeBitFields(iter
->second
));
429 for(CriteriaProgressMap::iterator iter
= m_criteriaProgress
.begin(); iter
!=m_criteriaProgress
.end(); ++iter
)
431 *data
<< uint32(iter
->second
->id
);
432 data
->appendPackGUID(iter
->second
->counter
);
433 data
->append(GetPlayer()->GetPackGUID());
435 *data
<< uint32(secsToTimeBitFields(iter
->second
->date
));