/* * Copyright (C) 2005-2008 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "AchievementMgr.h" #include "Common.h" #include "Player.h" #include "WorldPacket.h" #include "Database/DBCEnums.h" #include "ObjectMgr.h" #include "Guild.h" #include "Database/DatabaseEnv.h" #include "GameEvent.h" #include "World.h" #include "SpellMgr.h" #include "ProgressBar.h" #include "Policies/SingletonImp.h" INSTANTIATE_SINGLETON_1(AchievementGlobalMgr); const CriteriaCastSpellRequirement AchievementGlobalMgr::m_criteriaCastSpellRequirements[CRITERIA_CAST_SPELL_REQ_COUNT] = { {5272, 3057, 0, 0}, {5273, 2784, 0, 0}, {5752, 9099, 0, 0}, {5753, 8403, 0, 0}, {5772, 0, 0, RACE_GNOME}, {5774, 0, 0, RACE_BLOODELF}, {5775, 0, 0, RACE_DRAENEI}, {5776, 0, 0, RACE_DWARF}, {5777, 0, 0, RACE_HUMAN}, {5778, 0, 0, RACE_NIGHTELF}, {5779, 0, 0, RACE_ORC}, {5780, 0, 0, RACE_TAUREN}, {5781, 0, 0, RACE_TROLL}, {5782, 0, 0, RACE_UNDEAD_PLAYER}, {6225, 5661, 0, 0}, {6226, 26044, 0, 0}, {6228, 739, 0, 0}, {6229, 927, 0, 0}, {6230, 1444, 0, 0}, {6231, 8140, 0, 0}, {6232, 5489, 0, 0}, {6233,12336, 0, 0}, {6234, 1351, 0, 0}, {6235, 5484, 0, 0}, {6236, 1182, 0, 0}, {6237, 0, CLASS_DEATH_KNIGHT, RACE_ORC}, {6238, 0, CLASS_WARRIOR, RACE_HUMAN}, {6239, 0, CLASS_SHAMAN, RACE_TAUREN}, {6240, 0, CLASS_DRUID, RACE_NIGHTELF}, {6241, 0, CLASS_ROGUE, RACE_UNDEAD_PLAYER}, {6242, 0, CLASS_HUNTER, RACE_TROLL}, {6243, 0, CLASS_MAGE, RACE_GNOME}, {6244, 0, CLASS_PALADIN, RACE_DWARF}, {6245, 0, CLASS_WARLOCK, RACE_BLOODELF}, {6246, 0, CLASS_PRIEST, RACE_DRAENEI}, {6312, 0, CLASS_WARLOCK, RACE_GNOME}, {6313, 0, CLASS_DEATH_KNIGHT, RACE_HUMAN}, {6314, 0, CLASS_PRIEST, RACE_NIGHTELF}, {6315, 0, CLASS_SHAMAN, RACE_ORC}, {6316, 0, CLASS_DRUID, RACE_TAUREN}, {6317, 0, CLASS_ROGUE, RACE_TROLL}, {6318, 0, CLASS_WARRIOR, RACE_UNDEAD_PLAYER}, {6319, 0, CLASS_MAGE, RACE_BLOODELF}, {6320, 0, CLASS_PALADIN, RACE_DRAENEI}, {6321, 0, CLASS_HUNTER, RACE_DWARF}, {6662, 31261, 0, 0} }; AchievementMgr::AchievementMgr(Player *player) { m_player = player; } AchievementMgr::~AchievementMgr() { } void AchievementMgr::SaveToDB() { if(!m_completedAchievements.empty()) { bool need_execute = false; std::ostringstream ssdel; std::ostringstream ssins; for(CompletedAchievementMap::iterator iter = m_completedAchievements.begin(); iter!=m_completedAchievements.end(); iter++) { if(!iter->second.changed) continue; /// first new/changed record prefix if(!need_execute) { ssdel << "DELETE FROM character_achievement WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND achievement IN ("; ssins << "INSERT INTO character_achievement (guid, achievement, date) VALUES "; need_execute = true; } /// next new/changed record prefix else { ssdel << ", "; ssins << ", "; } // new/changed record data ssdel << iter->first; ssins << "("<GetGUIDLow() << ", " << iter->first << ", " << uint64(iter->second.date) << ")"; /// mark as saved in db iter->second.changed = false; } if(need_execute) ssdel << ")"; if(need_execute) { CharacterDatabase.BeginTransaction (); CharacterDatabase.Execute( ssdel.str().c_str() ); CharacterDatabase.Execute( ssins.str().c_str() ); CharacterDatabase.CommitTransaction (); } } if(!m_criteriaProgress.empty()) { /// prepare deleting and insert bool need_execute = false; std::ostringstream ssdel; std::ostringstream ssins; for(CriteriaProgressMap::iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter) { if(!iter->second.changed) continue; /// first new/changed record prefix if(!need_execute) { ssdel << "DELETE FROM character_achievement_progress WHERE guid = " << GetPlayer()->GetGUIDLow() << " AND criteria IN ("; ssins << "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES "; need_execute = true; } /// next new/changed record prefix else { ssdel << ", "; ssins << ", "; } // new/changed record data ssdel << iter->first; ssins << "(" << GetPlayer()->GetGUIDLow() << ", " << iter->first << ", " << iter->second.counter << ", " << iter->second.date << ")"; /// mark as saved in db iter->second.changed = false; } if(need_execute) ssdel << ")"; if(need_execute) { CharacterDatabase.BeginTransaction (); CharacterDatabase.Execute( ssdel.str().c_str() ); CharacterDatabase.Execute( ssins.str().c_str() ); CharacterDatabase.CommitTransaction (); } } } void AchievementMgr::LoadFromDB(QueryResult *achievementResult, QueryResult *criteriaResult) { if(achievementResult) { do { Field *fields = achievementResult->Fetch(); CompletedAchievementData& ca = m_completedAchievements[fields[0].GetUInt32()]; ca.date = time_t(fields[1].GetUInt64()); ca.changed = false; } while(achievementResult->NextRow()); delete achievementResult; } if(criteriaResult) { do { Field *fields = criteriaResult->Fetch(); uint32 id = fields[0].GetUInt32(); uint32 counter = fields[1].GetUInt32(); time_t date = time_t(fields[2].GetUInt64()); AchievementCriteriaEntry const* criteria = sAchievementCriteriaStore.LookupEntry(id); if(!criteria || criteria->timeLimit && date + criteria->timeLimit < time(NULL)) continue; CriteriaProgress& progress = m_criteriaProgress[id]; progress.counter = counter; progress.date = date; progress.changed = false; } while(criteriaResult->NextRow()); delete criteriaResult; } } void AchievementMgr::SendAchievementEarned(AchievementEntry const* achievement) { sLog.outString("AchievementMgr::SendAchievementEarned(%u)", achievement->ID); const char *msg = "|Hplayer:$N|h[$N]|h has earned the achievement $a!"; if(Guild* guild = objmgr.GetGuildById(GetPlayer()->GetGuildId())) { WorldPacket data(SMSG_MESSAGECHAT, 200); data << uint8(CHAT_MSG_ACHIEVEMENT); data << uint8(CHAT_MSG_GUILD_ACHIEVEMENT); data << uint32(LANG_UNIVERSAL); data << uint64(GetPlayer()->GetGUID()); data << uint32(5); data << uint64(GetPlayer()->GetGUID()); data << uint32(strlen(msg)+1); data << msg; data << uint8(0); data << uint32(achievement->ID); guild->BroadcastPacket(&data); } if(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_KILL|ACHIEVEMENT_FLAG_REALM_FIRST_REACH)) { // broadcast realm first reached WorldPacket data(SMSG_SERVER_FIRST_ACHIEVEMENT, strlen(GetPlayer()->GetName())+1+8+4+4); data << GetPlayer()->GetName(); data << uint64(GetPlayer()->GetGUID()); data << uint32(achievement->ID); data << uint32(0); // 1=link supplied string as player name, 0=display plain string sWorld.SendGlobalMessage(&data); } else { WorldPacket data(SMSG_MESSAGECHAT, 200); data << uint8(CHAT_MSG_ACHIEVEMENT); data << uint32(LANG_UNIVERSAL); data << uint64(GetPlayer()->GetGUID()); data << uint32(5); data << uint64(GetPlayer()->GetGUID()); data << uint32(strlen(msg)+1); data << msg; data << uint8(0); data << uint32(achievement->ID); GetPlayer()->SendMessageToSet(&data, true); } WorldPacket data(SMSG_ACHIEVEMENT_EARNED, 8+4+8); data.append(GetPlayer()->GetPackGUID()); data << uint32(achievement->ID); data << uint32(secsToTimeBitFields(time(NULL))); data << uint32(0); GetPlayer()->SendMessageToSet(&data, true); } void AchievementMgr::SendCriteriaUpdate(uint32 id, CriteriaProgress const* progress) { WorldPacket data(SMSG_CRITERIA_UPDATE, 8+4+8); data << uint32(id); // the counter is packed like a packed Guid data.appendPackGUID(progress->counter); data.append(GetPlayer()->GetPackGUID()); data << uint32(0); data << uint32(secsToTimeBitFields(progress->date)); data << uint32(0); // timer 1 data << uint32(0); // timer 2 GetPlayer()->SendMessageToSet(&data, true); } /** * called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet */ void AchievementMgr::CheckAllAchievementCriteria() { // suppress sending packets for(uint32 i=0; igroupFlag & ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP && GetPlayer()->GetGroup()) continue; AchievementEntry const *achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement); if(!achievement) continue; if(achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_HORDE && GetPlayer()->GetTeam() != HORDE || achievement->factionFlag == ACHIEVEMENT_FACTION_FLAG_ALLIANCE && GetPlayer()->GetTeam() != ALLIANCE) continue; switch (type) { case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL: SetCriteriaProgress(achievementCriteria, GetPlayer()->getLevel()); break; case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT: SetCriteriaProgress(achievementCriteria, GetPlayer()->GetByteValue(PLAYER_BYTES_2, 2)+1); break; case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; if(achievementCriteria->kill_creature.creatureID != miscvalue1) continue; SetCriteriaProgress(achievementCriteria, miscvalue2, true); break; case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL: if(uint32 skillvalue = GetPlayer()->GetBaseSkillValue(achievementCriteria->reach_skill_level.skillID)) SetCriteriaProgress(achievementCriteria, skillvalue); break; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT: { uint32 counter =0; for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++) if(itr->second.m_rewarded) counter++; SetCriteriaProgress(achievementCriteria, counter); break; } case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE: { uint32 counter =0; for(QuestStatusMap::iterator itr = GetPlayer()->getQuestStatusMap().begin(); itr!=GetPlayer()->getQuestStatusMap().end(); itr++) { Quest const* quest = objmgr.GetQuestTemplate(itr->first); if(itr->second.m_rewarded && quest->GetZoneOrSort() == achievementCriteria->complete_quests_in_zone.zoneID) counter++; } SetCriteriaProgress(achievementCriteria, counter); break; } case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; SetCriteriaProgress(achievementCriteria, miscvalue1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; if(GetPlayer()->GetMapId() != achievementCriteria->complete_battleground.mapID) continue; SetCriteriaProgress(achievementCriteria, miscvalue1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL: if(GetPlayer()->HasSpell(achievementCriteria->learn_spell.spellID)) SetCriteriaProgress(achievementCriteria, 1); break; case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; if(GetPlayer()->GetMapId() != achievementCriteria->death_at_map.mapID) continue; SetCriteriaProgress(achievementCriteria, 1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; if(miscvalue1 != achievementCriteria->killed_by_creature.creatureEntry) continue; SetCriteriaProgress(achievementCriteria, 1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; SetCriteriaProgress(achievementCriteria, 1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING: { // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; if(achievement->ID == 1260) { if(Player::GetDrunkenstateByValue(GetPlayer()->GetDrunkValue()) != DRUNKEN_SMASHED) continue; // TODO: hardcoding eventid is bad, it can differ from DB to DB - maye implement something using HolidayNames.dbc? if(!gameeventmgr.IsActiveEvent(26)) continue; } // miscvalue1 is the ingame fallheight*100 as stored in dbc SetCriteriaProgress(achievementCriteria, miscvalue1); break; } case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST: if(GetPlayer()->GetQuestRewardStatus(achievementCriteria->complete_quest.questID)) SetCriteriaProgress(achievementCriteria, 1); break; case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM: // AchievementMgr::UpdateAchievementCriteria might also be called on login - skip in this case if(!miscvalue1) continue; if(achievementCriteria->use_item.itemID != miscvalue1) continue; SetCriteriaProgress(achievementCriteria, 1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM: // speedup for non-login case if(miscvalue1 && achievementCriteria->own_item.itemID!=miscvalue1) continue; SetCriteriaProgress(achievementCriteria, GetPlayer()->GetItemCount(achievementCriteria->own_item.itemID, true)); break; case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM: // You _have_ to loot that item, just owning it when logging in does _not_ count! if(!miscvalue1) continue; if(miscvalue1 != achievementCriteria->own_item.itemID) continue; SetCriteriaProgress(achievementCriteria, miscvalue2, true); break; case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET: case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2: if (!miscvalue1 || miscvalue1 != achievementCriteria->be_spell_target.spellID) continue; SetCriteriaProgress(achievementCriteria, 1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL: if (!miscvalue1 || miscvalue1 != achievementCriteria->cast_spell.spellID) continue; SetCriteriaProgress(achievementCriteria, 1, true); break; case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2: { if (!miscvalue1 || miscvalue1 != achievementCriteria->cast_spell.spellID) continue; // those requirements couldn't be found in the dbc if (CriteriaCastSpellRequirement const* requirement = AchievementGlobalMgr::GetCriteriaCastSpellRequirement(achievementCriteria)) { if (!unit) continue; if (requirement->creatureEntry && unit->GetEntry() != requirement->creatureEntry) continue; if (requirement->playerRace && (unit->GetTypeId() != TYPEID_PLAYER || unit->getRace()!=requirement->playerRace)) continue; if (requirement->playerClass && (unit->GetTypeId() != TYPEID_PLAYER || unit->getClass()!=requirement->playerClass)) continue; } SetCriteriaProgress(achievementCriteria, 1, true); break; } case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS: { uint32 spellCount = 0; for (PlayerSpellMap::const_iterator spellIter = GetPlayer()->GetSpellMap().begin(); spellIter != GetPlayer()->GetSpellMap().end(); spellIter++) { for(SkillLineAbilityMap::const_iterator skillIter = spellmgr.GetBeginSkillLineAbilityMap(spellIter->first); skillIter != spellmgr.GetEndSkillLineAbilityMap(spellIter->first); skillIter++) { if(skillIter->second->skillId == achievementCriteria->learn_skilline_spell.skillLine) spellCount++; } } SetCriteriaProgress(achievementCriteria, spellCount); break; } case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP: { // skip for login case if(!miscvalue1) continue; SetCriteriaProgress(achievementCriteria, 1); break; } case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION: { int32 reputation = GetPlayer()->GetReputation(achievementCriteria->gain_reputation.factionID); if (reputation > 0) SetCriteriaProgress(achievementCriteria, reputation); break; } case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION: { uint32 counter = 0; const FactionStateList factionStateList = GetPlayer()->GetFactionStateList(); for (FactionStateList::const_iterator iter = factionStateList.begin(); iter!= factionStateList.end(); iter++) { if(GetPlayer()->ReputationToRank(iter->second.Standing) >= REP_EXALTED) ++counter; } SetCriteriaProgress(achievementCriteria, counter); break; } case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA: { WorldMapOverlayEntry const* worldOverlayEntry = sWorldMapOverlayStore.LookupEntry(achievementCriteria->explore_area.areaReference); if(!worldOverlayEntry) break; int32 exploreFlag = GetAreaFlagByAreaID(worldOverlayEntry->areatableID); if(exploreFlag < 0) break; uint32 playerIndexOffset = uint32(exploreFlag) / 32; uint32 mask = 1<< (uint32(exploreFlag) % 32); if(GetPlayer()->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + playerIndexOffset) & mask) SetCriteriaProgress(achievementCriteria, 1); break; } case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT: case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT: { // miscvalue1 = itemid // miscvalue2 = diced value if(!miscvalue1) continue; if(miscvalue2 != achievementCriteria->roll_greed_on_loot.rollValue) continue; ItemPrototype const *pProto = objmgr.GetItemPrototype( miscvalue1 ); uint32 requiredItemLevel = 0; if (achievementCriteria->ID == 2412 || achievementCriteria->ID == 2358) requiredItemLevel = 185; if(!pProto || pProto->ItemLevel referredAchievement); if(!achievement) return false; // counter can never complete if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER) return false; if(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) { // someone on this realm has already completed that achievement if(achievementmgr.IsRealmCompleted(achievement)) return false; } CriteriaProgressMap::const_iterator itr = m_criteriaProgress.find(achievementCriteria->ID); if(itr == m_criteriaProgress.end()) return false; CriteriaProgress const* progress = &itr->second; switch(achievementCriteria->requiredType) { case ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL: if(achievement->ID == 467 && GetPlayer()->getClass() != CLASS_SHAMAN || achievement->ID == 466 && GetPlayer()->getClass() != CLASS_DRUID || achievement->ID == 465 && GetPlayer()->getClass() != CLASS_PALADIN || achievement->ID == 464 && GetPlayer()->getClass() != CLASS_PRIEST || achievement->ID == 463 && GetPlayer()->getClass() != CLASS_WARLOCK || achievement->ID == 462 && GetPlayer()->getClass() != CLASS_HUNTER || achievement->ID == 461 && GetPlayer()->getClass() != CLASS_DEATH_KNIGHT || achievement->ID == 460 && GetPlayer()->getClass() != CLASS_MAGE || achievement->ID == 459 && GetPlayer()->getClass() != CLASS_WARRIOR || achievement->ID == 458 && GetPlayer()->getClass() != CLASS_ROGUE || achievement->ID == 1404 && GetPlayer()->getRace() != RACE_GNOME || achievement->ID == 1405 && GetPlayer()->getRace() != RACE_BLOODELF || achievement->ID == 1406 && GetPlayer()->getRace() != RACE_DRAENEI || achievement->ID == 1407 && GetPlayer()->getRace() != RACE_DWARF || achievement->ID == 1408 && GetPlayer()->getRace() != RACE_HUMAN || achievement->ID == 1409 && GetPlayer()->getRace() != RACE_NIGHTELF || achievement->ID == 1410 && GetPlayer()->getRace() != RACE_ORC || achievement->ID == 1411 && GetPlayer()->getRace() != RACE_TAUREN || achievement->ID == 1412 && GetPlayer()->getRace() != RACE_TROLL || achievement->ID == 1413 && GetPlayer()->getRace() != RACE_UNDEAD_PLAYER ) return false; return progress->counter >= achievementCriteria->reach_level.level; case ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT: return progress->counter >= achievementCriteria->buy_bank_slot.numberOfSlots; case ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE: return progress->counter >= achievementCriteria->kill_creature.creatureCount; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT: return m_completedAchievements.find(achievementCriteria->complete_achievement.linkedAchievement) != m_completedAchievements.end(); case ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL: return progress->counter >= achievementCriteria->reach_skill_level.skillLevel; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT: return progress->counter >= achievementCriteria->complete_quest_count.totalQuestCount; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE: return progress->counter >= achievementCriteria->complete_quests_in_zone.questCount; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST: return progress->counter >= achievementCriteria->complete_daily_quest.questCount; case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL: return progress->counter >= 1; case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING: return progress->counter >= achievementCriteria->fall_without_dying.fallHeight; case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST: return progress->counter >= 1; case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM: return progress->counter >= achievementCriteria->use_item.itemCount; case ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM: return progress->counter >= achievementCriteria->own_item.itemCount; case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM: return progress->counter >= achievementCriteria->loot_item.itemCount; case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET: case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2: return progress->counter >= achievementCriteria->be_spell_target.spellCount; case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL: case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2: return progress->counter >= achievementCriteria->cast_spell.castCount; case ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS: return progress->counter >= achievementCriteria->learn_skilline_spell.spellCount; case ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP: return progress->counter >= achievementCriteria->visit_barber.numberOfVisits; case ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION: return progress->counter >= achievementCriteria->gain_reputation.reputationAmount; case ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION: return progress->counter >= achievementCriteria->gain_exalted_reputation.numberOfExaltedFactions; case ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA: return progress->counter >= 1; case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT: case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT: return progress->counter >= achievementCriteria->roll_greed_on_loot.count; // handle all statistic-only criteria here case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND: case ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP: case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE: case ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER: return false; } return false; } void AchievementMgr::CompletedCriteria(AchievementCriteriaEntry const* criteria) { AchievementEntry const* achievement = sAchievementStore.LookupEntry(criteria->referredAchievement); if(!achievement) return; // counter can never complete if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER) return; if(criteria->completionFlag & ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL || GetAchievementCompletionState(achievement)==ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED) { CompletedAchievement(achievement); } } // TODO: achievement 705 requires 4 criteria to be fulfilled AchievementCompletionState AchievementMgr::GetAchievementCompletionState(AchievementEntry const* entry) { if(m_completedAchievements.find(entry->ID)!=m_completedAchievements.end()) return ACHIEVEMENT_COMPLETED_COMPLETED_STORED; bool foundOutstanding = false; for (uint32 entryId = 0; entryIdreferredAchievement!= entry->ID) continue; if(IsCompletedCriteria(criteria) && criteria->completionFlag & ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL) return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED; // found an umcompleted criteria, but DONT return false yet - there might be a completed criteria with ACHIEVEMENT_CRITERIA_COMPLETE_FLAG_ALL if(!IsCompletedCriteria(criteria)) foundOutstanding = true; } if(foundOutstanding) return ACHIEVEMENT_COMPLETED_NONE; else return ACHIEVEMENT_COMPLETED_COMPLETED_NOT_STORED; } void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 newValue, bool relative) { sLog.outDetail("AchievementMgr::SetCriteriaProgress(%u, %u)", entry->ID, newValue); CriteriaProgress *progress = NULL; CriteriaProgressMap::iterator iter = m_criteriaProgress.find(entry->ID); if(iter == m_criteriaProgress.end()) { progress = &m_criteriaProgress[entry->ID]; progress->counter = newValue; progress->date = time(NULL); } else { progress = &iter->second; if(relative) newValue += progress->counter; if(progress->counter == newValue) return; progress->counter = newValue; } progress->changed = true; if(entry->timeLimit) { time_t now = time(NULL); if(progress->date + entry->timeLimit < now) { progress->counter = 1; } // also it seems illogical, the timeframe will be extended at every criteria update progress->date = now; } SendCriteriaUpdate(entry->ID,progress); } void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) { sLog.outDetail("AchievementMgr::CompletedAchievement(%u)", achievement->ID); if(achievement->flags & ACHIEVEMENT_FLAG_COUNTER || m_completedAchievements.find(achievement->ID)!=m_completedAchievements.end()) return; SendAchievementEarned(achievement); CompletedAchievementData& ca = m_completedAchievements[achievement->ID]; ca.date = time(NULL); ca.changed = true; // don't insert for ACHIEVEMENT_FLAG_REALM_FIRST_KILL since otherwise only the first group member would reach that achievement // TODO: where do set this instead? if(!(achievement->flags & ACHIEVEMENT_FLAG_REALM_FIRST_KILL)) achievementmgr.SetRealmCompleted(achievement); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT); // reward items and titles if any AchievementReward const* reward = achievementmgr.GetAchievementReward(achievement); // no rewards if(!reward) return; // titles if(uint32 titleId = reward->titleId[GetPlayer()->GetTeam() == HORDE?0:1]) { if(CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId)) GetPlayer()->SetTitle(titleEntry); } // mail if(reward->sender) { Item* item = reward->itemId ? Item::CreateItem(reward->itemId,1,GetPlayer ()) : NULL; MailItemsInfo mi; if(item) { // save new item before send item->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted // item mi.AddItem(item->GetGUIDLow(), item->GetEntry(), item); } int loc_idx = GetPlayer()->GetSession()->GetSessionDbLocaleIndex(); // subject and text std::string subject = reward->subject; std::string text = reward->text; if ( loc_idx >= 0 ) { if(AchievementRewardLocale const* loc = achievementmgr.GetAchievementRewardLocale(achievement)) { if (loc->subject.size() > size_t(loc_idx) && !loc->subject[loc_idx].empty()) subject = loc->subject[loc_idx]; if (loc->text.size() > size_t(loc_idx) && !loc->text[loc_idx].empty()) text = loc->text[loc_idx]; } } uint32 itemTextId = objmgr.CreateItemText( text ); WorldSession::SendMailTo(GetPlayer(), MAIL_CREATURE, MAIL_STATIONERY_NORMAL, reward->sender, GetPlayer()->GetGUIDLow(), subject, itemTextId , &mi, 0, 0, MAIL_CHECK_MASK_NONE); } } void AchievementMgr::SendAllAchievementData() { // since we don't know the exact size of the packed GUIDs this is just an approximation WorldPacket data(SMSG_ALL_ACHIEVEMENT_DATA, 4*2+m_completedAchievements.size()*4*2+m_completedAchievements.size()*7*4); BuildAllDataPacket(&data); GetPlayer()->GetSession()->SendPacket(&data); } void AchievementMgr::SendRespondInspectAchievements(Player* player) { // since we don't know the exact size of the packed GUIDs this is just an approximation WorldPacket data(SMSG_RESPOND_INSPECT_ACHIEVEMENTS, 4+4*2+m_completedAchievements.size()*4*2+m_completedAchievements.size()*7*4); data.append(GetPlayer()->GetPackGUID()); BuildAllDataPacket(&data); player->GetSession()->SendPacket(&data); } /** * used by both SMSG_ALL_ACHIEVEMENT_DATA and SMSG_RESPOND_INSPECT_ACHIEVEMENT */ void AchievementMgr::BuildAllDataPacket(WorldPacket *data) { for(CompletedAchievementMap::const_iterator iter = m_completedAchievements.begin(); iter!=m_completedAchievements.end(); ++iter) { *data << uint32(iter->first); *data << uint32(secsToTimeBitFields(iter->second.date)); } *data << int32(-1); for(CriteriaProgressMap::const_iterator iter = m_criteriaProgress.begin(); iter!=m_criteriaProgress.end(); ++iter) { *data << uint32(iter->first); data->appendPackGUID(iter->second.counter); data->append(GetPlayer()->GetPackGUID()); *data << uint32(0); *data << uint32(secsToTimeBitFields(iter->second.date)); *data << uint32(0); *data << uint32(0); } *data << int32(-1); } //========================================================== AchievementCriteriaEntryList const& AchievementGlobalMgr::GetAchievementCriteriaByType(AchievementCriteriaTypes type) { return m_AchievementCriteriasByType[type]; } void AchievementGlobalMgr::LoadAchievementCriteriaList() { for (uint32 entryId = 0; entryIdrequiredType].push_back(criteria); } } void AchievementGlobalMgr::LoadCompletedAchievements() { QueryResult *result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement"); if(!result) return; do { Field *fields = result->Fetch(); m_allCompletedAchievements.insert(fields[0].GetUInt32()); } while(result->NextRow()); delete result; } void AchievementGlobalMgr::LoadRewards() { m_achievementRewards.clear(); // need for reload case // 0 1 2 3 4 5 6 QueryResult *result = WorldDatabase.Query("SELECT entry, title_A, title_H, item, sender, subject, text FROM achievement_reward"); if(!result) { barGoLink bar(1); bar.step(); sLog.outString(""); sLog.outString(">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty."); return; } barGoLink bar(result->GetRowCount()); do { Field *fields = result->Fetch(); bar.step(); uint32 entry = fields[0].GetUInt32(); if (!sAchievementStore.LookupEntry(entry)) { sLog.outErrorDb( "Table `achievement_reward` has wrong achievement (Entry: %u), ignore", entry); continue; } AchievementReward reward; reward.titleId[0] = fields[1].GetUInt32(); reward.titleId[1] = fields[2].GetUInt32(); reward.itemId = fields[3].GetUInt32(); reward.sender = fields[4].GetUInt32(); reward.subject = fields[5].GetCppString(); reward.text = fields[6].GetCppString(); if ((reward.titleId[0]==0)!=(reward.titleId[1]==0)) 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]); // must be title or mail at least if (!reward.titleId[0] && !reward.titleId[1] && !reward.sender) { sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have title or item reward data, ignore.", entry); continue; } if (reward.titleId[0]) { CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.titleId[0]); if (!titleEntry) { sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry, reward.titleId[0]); reward.titleId[0] = 0; } } if (reward.titleId[1]) { CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(reward.titleId[1]); if (!titleEntry) { sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid title id (%u) in `title_A`, set to 0", entry, reward.titleId[1]); reward.titleId[1] = 0; } } //check mail data before item for report including wrong item case if (reward.sender) { if (!objmgr.GetCreatureTemplate(reward.sender)) { sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid creature entry %u as sender, mail reward skipped.", entry, reward.sender); reward.sender = 0; } } else { if (reward.itemId) sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have item reward, item will not rewarded", entry); if (!reward.subject.empty()) sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail subject.", entry); if (!reward.text.empty()) sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) not have sender data but have mail text.", entry); } if (reward.itemId) { if (!objmgr.GetItemPrototype(reward.itemId)) { sLog.outErrorDb( "Table `achievement_reward` (Entry: %u) has invalid item id %u, reward mail will be without item.", entry, reward.itemId); reward.itemId = 0; } } m_achievementRewards[entry] = reward; } while (result->NextRow()); delete result; sLog.outString(); sLog.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales.size() ); } void AchievementGlobalMgr::LoadRewardLocales() { m_achievementRewardLocales.clear(); // need for reload case 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"); if(!result) { barGoLink bar(1); bar.step(); sLog.outString(""); sLog.outString(">> Loaded 0 achievement reward locale strings. DB table `locales_achievement_reward` is empty."); return; } barGoLink bar(result->GetRowCount()); do { Field *fields = result->Fetch(); bar.step(); uint32 entry = fields[0].GetUInt32(); if(m_achievementRewards.find(entry)==m_achievementRewards.end()) { sLog.outErrorDb( "Table `locales_achievement_reward` (Entry: %u) has locale strings for not existed achievement reward .", entry); continue; } AchievementRewardLocale& data = m_achievementRewardLocales[entry]; for(int i = 1; i < MAX_LOCALE; ++i) { std::string str = fields[1+2*(i-1)].GetCppString(); if(!str.empty()) { int idx = objmgr.GetOrNewIndexForLocale(LocaleConstant(i)); if(idx >= 0) { if(data.subject.size() <= idx) data.subject.resize(idx+1); data.subject[idx] = str; } } str = fields[1+2*(i-1)+1].GetCppString(); if(!str.empty()) { int idx = objmgr.GetOrNewIndexForLocale(LocaleConstant(i)); if(idx >= 0) { if(data.text.size() <= idx) data.text.resize(idx+1); data.text[idx] = str; } } } } while (result->NextRow()); delete result; sLog.outString(); sLog.outString( ">> Loaded %u achievement reward locale strings", m_achievementRewardLocales.size() ); }