Implemented ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, ACHIEVEMENT_CRITERIA_TYPE_C...
[AHbot.git] / src / game / AchievementMgr.cpp
blobc24d7d043b9b3a17624ed87af579834637718d96
1 /*
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"
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"
28 AchievementMgr::AchievementMgr(Player *player)
30 m_player = player;
33 AchievementMgr::~AchievementMgr()
35 for(CriteriaProgressMap::iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter)
36 delete iter->second;
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())
51 ss << ", ";
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())
66 ss << ", ";
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)
75 if(achievementResult)
79 Field *fields = achievementResult->Fetch();
80 m_completedAchievements[fields[0].GetUInt32()] = fields[1].GetUInt32();
81 } while(achievementResult->NextRow());
82 delete achievementResult;
85 if(criteriaResult)
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());
106 data << uint32(5);
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);
110 data << msg;
111 data << uint8(0);
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());
121 data << uint32(5);
122 data << uint64(GetPlayer()->GetGUID());
123 data << uint32(strlen(msg)+1);
124 data << msg;
125 data << uint8(0);
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)));
134 data << uint32(0);
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());
147 data << uint32(0);
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))
177 continue;
179 if(achievementCriteria->groupFlag & ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP && GetPlayer()->GetGroup())
180 continue;
182 AchievementEntry const *achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement);
183 if(!achievement)
184 continue;
186 if(achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_HORDE && GetPlayer()->GetTeam() != HORDE ||
187 achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_ALLIANCE && GetPlayer()->GetTeam() != ALLIANCE)
188 continue;
190 switch (type)
192 case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL:
193 SetCriteriaProgress(achievementCriteria, GetPlayer()->getLevel());
194 break;
195 case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT:
196 SetCriteriaProgress(achievementCriteria, GetPlayer()->GetByteValue(PLAYER_BYTES_2, 2)+1);
197 break;
198 case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE:
199 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
200 if(!miscvalue1)
201 continue;
202 if(achievementCriteria->kill_creature.creatureID != miscvalue1)
203 continue;
204 SetCriteriaProgress(achievementCriteria, miscvalue2, true);
205 break;
206 case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL:
207 if(uint32 skillvalue = GetPlayer()->GetBaseSkillValue(achievementCriteria->reach_skill_level.skillID))
208 SetCriteriaProgress(achievementCriteria, skillvalue);
209 break;
210 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT:
212 uint32 counter =0;
213 for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++)
214 if(itr->second.m_rewarded)
215 counter++;
216 SetCriteriaProgress(achievementCriteria, counter);
217 break;
219 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE:
221 uint32 counter =0;
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)
226 counter++;
228 SetCriteriaProgress(achievementCriteria, counter);
229 break;
231 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST:
232 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
233 if(!miscvalue1)
234 continue;
235 SetCriteriaProgress(achievementCriteria, miscvalue1, true);
236 break;
237 case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND:
238 // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case
239 if(!miscvalue1)
240 continue;
241 if(GetPlayer()->GetMapID() != achievementCriteria->complete_battleground.mapID)
242 continue;
243 SetCriteriaProgress(achievementCriteria, miscvalue1, true);
244 break;
247 if(IsCompletedCriteria(achievementCriteria))
248 CompletedCriteria(achievementCriteria);
251 bool AchievementMgr::IsCompletedCriteria(AchievementCriteriaEntry const* achievementCriteria)
253 AchievementEntry const* achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement);
254 if(!achievement)
255 return false;
257 // counter can never complete
258 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER)
259 return false;
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())
265 return false;
268 CriteriaProgressMap::iterator itr = m_criteriaProgress.find(achievementCriteria->ID);
269 if(itr == m_criteriaProgress.end())
270 return false;
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 )
298 return false;
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
314 return false;
316 return false;
319 void AchievementMgr::CompletedCriteria(AchievementCriteriaEntry const* criteria)
321 AchievementEntry const* achievement = sAchievementStore.LookupEntry(criteria->referredAchievement);
322 if(!achievement)
323 return;
324 // counter can never complete
325 if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER)
326 return;
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)
345 continue;
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;
354 if(foundOutstanding)
355 return ACHIEVEMENT_COMPLETED_NONE;
356 else
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;
370 else
372 progress = m_criteriaProgress[entry->ID];
373 if(relative)
374 newValue += progress->counter;
375 if(progress->counter == newValue)
376 return;
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())
386 return;
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));
427 *data << int32(-1);
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());
434 *data << uint32(0);
435 *data << uint32(secsToTimeBitFields(iter->second->date));
436 *data << uint32(0);
437 *data << uint32(0);
440 *data << int32(-1);