diff options
author | Trazom62 <none@none> | 2010-04-27 22:22:13 +0200 |
---|---|---|
committer | Trazom62 <none@none> | 2010-04-27 22:22:13 +0200 |
commit | 36ebd58b37aa70e3c68c00ac7126810da39f2de1 (patch) | |
tree | b076936f48feffab7102bd18e4d09329ba7b930f | |
parent | 90f38341f05de2d517bc72d97672165dc3f5eb54 (diff) |
Implement support of Timed Achievement. Thanks Shauren.
Fixes issue #1431.
--HG--
branch : trunk
-rw-r--r-- | src/game/AchievementMgr.cpp | 135 | ||||
-rw-r--r-- | src/game/AchievementMgr.h | 11 | ||||
-rw-r--r-- | src/game/DBCEnums.h | 14 | ||||
-rw-r--r-- | src/game/DBCStructure.h | 4 | ||||
-rw-r--r-- | src/game/DBCfmt.h | 2 | ||||
-rw-r--r-- | src/game/Player.cpp | 6 | ||||
-rw-r--r-- | src/game/QuestHandler.cpp | 1 | ||||
-rw-r--r-- | src/game/Spell.cpp | 5 |
8 files changed, 152 insertions, 26 deletions
diff --git a/src/game/AchievementMgr.cpp b/src/game/AchievementMgr.cpp index 91567587231..cb72e2b9848 100644 --- a/src/game/AchievementMgr.cpp +++ b/src/game/AchievementMgr.cpp @@ -611,6 +611,10 @@ void AchievementMgr::SendAchievementEarned(AchievementEntry const* achievement) if (GetPlayer()->GetSession()->PlayerLoading()) return; + // Don't send for achievements with ACHIEVEMENT_FLAG_TRACKING + if (achievement->flags & ACHIEVEMENT_FLAG_TRACKING) + return; + #ifdef TRINITY_DEBUG if ((sLog.getLogFilter() & LOG_FILTER_ACHIEVEMENT_UPDATES) == 0) sLog.outDebug("AchievementMgr::SendAchievementEarned(%u)", achievement->ID); @@ -657,19 +661,22 @@ void AchievementMgr::SendAchievementEarned(AchievementEntry const* achievement) GetPlayer()->SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY), true); } -void AchievementMgr::SendCriteriaUpdate(uint32 id, CriteriaProgress const* progress) +void AchievementMgr::SendCriteriaUpdate(AchievementCriteriaEntry const* entry, CriteriaProgress const* progress, uint32 timeElapsed, bool timedCompleted) { WorldPacket data(SMSG_CRITERIA_UPDATE, 8+4+8); - data << uint32(id); + data << uint32(entry->ID); // the counter is packed like a packed Guid data.appendPackGUID(progress->counter); data.append(GetPlayer()->GetPackGUID()); - data << uint32(0); + if (!entry->timeLimit) + data << uint32(0); + else + data << uint32(timedCompleted ? 0 : 1); // this are some flags, 1 is for keeping the counter at 0 in client data << uint32(secsToTimeBitFields(progress->date)); - data << uint32(0); // timer 1 - data << uint32(0); // timer 2 + data << uint32(timeElapsed); // time elapsed in seconds + data << uint32(0); // unk GetPlayer()->SendDirectMessage(&data); } @@ -711,9 +718,6 @@ void AchievementMgr::UpdateAchievementCriteria(AchievementCriteriaTypes type, ui { AchievementCriteriaEntry const *achievementCriteria = (*i); - if (achievementCriteria->groupFlag & ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP && GetPlayer()->GetGroup()) - continue; - AchievementEntry const *achievement = sAchievementStore.LookupEntry(achievementCriteria->referredAchievement); if (!achievement) continue; @@ -1695,6 +1699,11 @@ bool AchievementMgr::IsCompletedAchievement(AchievementEntry const* entry) void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 changeValue, ProgressType ptype) { + // Don't allow to cheat - doing timed achievements without timer active + TimedAchievementMap::iterator timedIter = m_timedAchievements.find(entry->ID); + if (entry->timeLimit && timedIter == m_timedAchievements.end()) + return; + if ((sLog.getLogFilter() & LOG_FILTER_ACHIEVEMENT_UPDATES) == 0) sLog.outDetail("AchievementMgr::SetCriteriaProgress(%u, %u) for (GUID:%u)", entry->ID, changeValue, m_player->GetGUIDLow()); @@ -1704,8 +1713,9 @@ void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, if (iter == m_criteriaProgress.end()) { - // not create record for 0 counter - if (changeValue == 0) + // not create record for 0 counter but allow it for timed achievements + // we will need to send 0 progress to client to start the timer + if (changeValue == 0 && !entry->timeLimit) return; progress = &m_criteriaProgress[entry->ID]; @@ -1735,7 +1745,7 @@ void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, } // not update (not mark as changed) if counter will have same value - if (progress->counter == newValue) + if (progress->counter == newValue && !entry->timeLimit) return; progress->counter = newValue; @@ -1743,16 +1753,85 @@ void AchievementMgr::SetCriteriaProgress(AchievementCriteriaEntry const* entry, progress->changed = true; + uint32 timeElapsed = 0; + bool timedCompleted = false; + if (entry->timeLimit) { - time_t now = time(NULL); - if (time_t(progress->date + entry->timeLimit) < now) - progress->counter = 1; + //has to exist else we wouldn't be here + timedCompleted = IsCompletedCriteria(entry, sAchievementStore.LookupEntry(entry->referredAchievement)); + // Client expects this in packet + timeElapsed = entry->timeLimit - (timedIter->second/IN_MILISECONDS); + + // Remove the timer, we wont need it anymore + if (timedCompleted) + m_timedAchievements.erase(timedIter); + } - // also it seems illogical, the timeframe will be extended at every criteria update - progress->date = now; + SendCriteriaUpdate(entry, progress, timeElapsed, timedCompleted); +} + +void AchievementMgr::UpdateTimedAchievements(uint32 timeDiff) +{ + if (!m_timedAchievements.empty()) + { + for (TimedAchievementMap::iterator itr = m_timedAchievements.begin(); itr != m_timedAchievements.end();) + { + // Time is up, remove timer and reset progress + if (itr->second <= timeDiff) + { + AchievementCriteriaEntry const *entry = sAchievementCriteriaStore.LookupEntry(itr->first); + SetCriteriaProgress(entry, 0, PROGRESS_SET); + m_timedAchievements.erase(itr++); + } + else + { + itr->second -= timeDiff; + ++itr; + } + } + } +} + +void AchievementMgr::StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry) +{ + AchievementCriteriaEntryList const& achievementCriteriaList = achievementmgr.GetTimedAchievementCriteriaByType(type); + for (AchievementCriteriaEntryList::const_iterator i = achievementCriteriaList.begin(); i != achievementCriteriaList.end(); ++i) + { + if ((*i)->timerStartEvent != entry) + continue; + + AchievementEntry const *achievement = sAchievementStore.LookupEntry((*i)->referredAchievement); + if (m_timedAchievements.find((*i)->ID) == m_timedAchievements.end() && !IsCompletedCriteria(*i, achievement)) + { + // Start the timer + m_timedAchievements[(*i)->ID] = (*i)->timeLimit * IN_MILISECONDS; + + // and at client too + SetCriteriaProgress(*i, 0, PROGRESS_SET); + } + } +} + +void AchievementMgr::RemoveTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry) +{ + AchievementCriteriaEntryList const& achievementCriteriaList = achievementmgr.GetTimedAchievementCriteriaByType(type); + for (AchievementCriteriaEntryList::const_iterator i = achievementCriteriaList.begin(); i!=achievementCriteriaList.end(); ++i) + { + if ((*i)->timerStartEvent != entry) + continue; + + TimedAchievementMap::iterator timedIter = m_timedAchievements.find((*i)->ID); + // We don't have timer for this achievement + if (timedIter == m_timedAchievements.end()) + continue; + + // 0 the progress to avoid saving to db + SetCriteriaProgress(*i, 0, PROGRESS_SET); + + // Remove the timer + m_timedAchievements.erase(timedIter); } - SendCriteriaUpdate(entry->ID,progress); } void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) @@ -1847,8 +1926,14 @@ void AchievementMgr::SendRespondInspectAchievements(Player* player) */ void AchievementMgr::BuildAllDataPacket(WorldPacket *data) { + AchievementEntry const *achievement; for (CompletedAchievementMap::const_iterator iter = m_completedAchievements.begin(); iter != m_completedAchievements.end(); ++iter) { + // Skip tracking - they bug client UI + achievement = sAchievementStore.LookupEntry(iter->first); + if (achievement->flags & ACHIEVEMENT_FLAG_TRACKING) + continue; + *data << uint32(iter->first); *data << uint32(secsToTimeBitFields(iter->second.date)); } @@ -1879,6 +1964,11 @@ AchievementCriteriaEntryList const& AchievementGlobalMgr::GetAchievementCriteria return m_AchievementCriteriasByType[type]; } +AchievementCriteriaEntryList const& AchievementGlobalMgr::GetTimedAchievementCriteriaByType(AchievementCriteriaTimedTypes type) +{ + return m_AchievementCriteriasByTimedType[type]; +} + void AchievementGlobalMgr::LoadAchievementCriteriaList() { if (sAchievementCriteriaStore.GetNumRows() == 0) @@ -1902,6 +1992,9 @@ void AchievementGlobalMgr::LoadAchievementCriteriaList() m_AchievementCriteriasByType[criteria->requiredType].push_back(criteria); m_AchievementCriteriaListByAchievement[criteria->referredAchievement].push_back(criteria); + + if(criteria->timeLimit) + m_AchievementCriteriasByTimedType[criteria->timedType].push_back(criteria); } sLog.outString(); @@ -2030,6 +2123,8 @@ void AchievementGlobalMgr::LoadAchievementCriteriaData() } case ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING: break; // any cases + case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET: // any cases + break; case ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL: // any cases break; case ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA: // need skip generic cases @@ -2038,10 +2133,16 @@ void AchievementGlobalMgr::LoadAchievementCriteriaData() break; case ACHIEVEMENT_CRITERIA_TYPE_EQUIP_EPIC_ITEM: // any cases break; + case ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT: + break; // any cases + case ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT: + break; // any cases case ACHIEVEMENT_CRITERIA_TYPE_DO_EMOTE: // need skip generic cases if (criteria->do_emote.count == 0) continue; break; + case ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2: + break; // any cases case ACHIEVEMENT_CRITERIA_TYPE_WIN_DUEL: // skip statistics if (criteria->win_duel.duelCount == 0) continue; diff --git a/src/game/AchievementMgr.h b/src/game/AchievementMgr.h index 1e810a5160d..ea9e5e95142 100644 --- a/src/game/AchievementMgr.h +++ b/src/game/AchievementMgr.h @@ -249,12 +249,15 @@ class AchievementMgr void SendAllAchievementData(); void SendRespondInspectAchievements(Player* player); bool HasAchieved(AchievementEntry const* achievement) const; - Player* GetPlayer() { return m_player;} + Player* GetPlayer() { return m_player; } + void UpdateTimedAchievements(uint32 timeDiff); + void StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry); + void RemoveTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry); // used for quest and scripted timed achievements private: enum ProgressType { PROGRESS_SET, PROGRESS_ACCUMULATE, PROGRESS_HIGHEST }; void SendAchievementEarned(AchievementEntry const* achievement); - void SendCriteriaUpdate(uint32 id, CriteriaProgress const* progress); + void SendCriteriaUpdate(AchievementCriteriaEntry const* entry, CriteriaProgress const* progress, uint32 timeElapsed, bool timedCompleted); void SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 changeValue, ProgressType ptype = PROGRESS_SET); void CompletedCriteriaFor(AchievementEntry const* achievement); bool IsCompletedCriteria(AchievementCriteriaEntry const* criteria, AchievementEntry const* achievement); @@ -265,12 +268,15 @@ class AchievementMgr Player* m_player; CriteriaProgressMap m_criteriaProgress; CompletedAchievementMap m_completedAchievements; + typedef std::map<uint32, uint32> TimedAchievementMap; + TimedAchievementMap m_timedAchievements; // Criteria id/time left in MS }; class AchievementGlobalMgr { public: AchievementCriteriaEntryList const& GetAchievementCriteriaByType(AchievementCriteriaTypes type); + AchievementCriteriaEntryList const& GetTimedAchievementCriteriaByType(AchievementCriteriaTimedTypes type); AchievementCriteriaEntryList const* GetAchievementCriteriaByAchievement(uint32 id) { AchievementCriteriaListByAchievement::const_iterator itr = m_AchievementCriteriaListByAchievement.find(id); @@ -322,6 +328,7 @@ class AchievementGlobalMgr // store achievement criterias by type to speed up lookup AchievementCriteriaEntryList m_AchievementCriteriasByType[ACHIEVEMENT_CRITERIA_TYPE_TOTAL]; + AchievementCriteriaEntryList m_AchievementCriteriasByTimedType[ACHIEVEMENT_TIMED_TYPE_MAX]; // store achievement criterias by achievement to speed up lookup AchievementCriteriaListByAchievement m_AchievementCriteriaListByAchievement; // store achievements by referenced achievement id to speed up lookup diff --git a/src/game/DBCEnums.h b/src/game/DBCEnums.h index 346aa886c44..3969578cd02 100644 --- a/src/game/DBCEnums.h +++ b/src/game/DBCEnums.h @@ -58,7 +58,7 @@ enum AchievementFaction enum AchievementFlags { ACHIEVEMENT_FLAG_COUNTER = 0x00000001, // Just count statistic (never stop and complete) - ACHIEVEMENT_FLAG_UNK2 = 0x00000002, // not used + ACHIEVEMENT_FLAG_TRACKING = 0x00000002, // Not sent to client - internal use only ACHIEVEMENT_FLAG_STORE_MAX_VALUE = 0x00000004, // Store only max value? used only in "Reach level xx" ACHIEVEMENT_FLAG_SUMM = 0x00000008, // Use summ criteria value from all reqirements (and calculate max value) ACHIEVEMENT_FLAG_MAX_USED = 0x00000010, // Show max criteria (and calculate max value ??) @@ -90,10 +90,16 @@ enum AchievementCriteriaCompletionFlags ACHIEVEMENT_CRITERIA_FLAG_MONEY_COUNTER = 0x00000020, // Displays counter as money }; -enum AchievementCriteriaGroupFlags +enum AchievementCriteriaTimedTypes { - // you mustn't be in a group while fulfilling this achievement - ACHIEVEMENT_CRITERIA_GROUP_NOT_IN_GROUP = 2, + ACHIEVEMENT_TIMED_TYPE_EVENT = 1, // Timer is started by internal event with id in timerStartEvent + ACHIEVEMENT_TIMED_TYPE_QUEST = 2, // Timer is started by acceting quest with entry in timerStartEvent + ACHIEVEMENT_TIMED_TYPE_SPELL_CASTER = 5, // Timer is started by casting a spell with entry in timerStartEvent + ACHIEVEMENT_TIMED_TYPE_SPELL_TARGET = 6, // Timer is started by being target of spell with entry in timerStartEvent + ACHIEVEMENT_TIMED_TYPE_CREATURE = 7, // Timer is started by killing creature with entry in timerStartEvent + ACHIEVEMENT_TIMED_TYPE_ITEM = 9, // Timer is started by using item with entry in timerStartEvent + + ACHIEVEMENT_TIMED_TYPE_MAX, }; enum AchievementCriteriaTypes diff --git a/src/game/DBCStructure.h b/src/game/DBCStructure.h index 35fe22242b2..77393f2c4b4 100644 --- a/src/game/DBCStructure.h +++ b/src/game/DBCStructure.h @@ -492,8 +492,8 @@ struct AchievementCriteriaEntry //char* name[16]; // 9-24 //uint32 name_flags; // 25 uint32 completionFlag; // 26 - uint32 groupFlag; // 27 - //uint32 unk1; // 28 Alway appears with timed events + uint32 timedType; // 27 + uint32 timerStartEvent; // 28 Alway appears with timed events // for timed spells it is spell id for // timed kills it is creature id uint32 timeLimit; // 29 time limit in seconds diff --git a/src/game/DBCfmt.h b/src/game/DBCfmt.h index b2507bd1ff1..e192f3594b5 100644 --- a/src/game/DBCfmt.h +++ b/src/game/DBCfmt.h @@ -22,7 +22,7 @@ #define TRINITY_DBCSFRM_H const char Achievementfmt[]="niixssssssssssssssssxxxxxxxxxxxxxxxxxxiixixxxxxxxxxxxxxxxxxxii"; -const char AchievementCriteriafmt[]="niiiiiiiixxxxxxxxxxxxxxxxxiixix"; +const char AchievementCriteriafmt[]="niiiiiiiixxxxxxxxxxxxxxxxxiiiix"; const char AreaTableEntryfmt[]="iiinixxxxxissssssssssssssssxixxxxxxx"; const char AreaGroupEntryfmt[]="niiiiiii"; const char AreaPOIEntryfmt[]="niiiiiiiiiiifffixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxix"; diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 540c3c6b1aa..2715ea91a26 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -1218,6 +1218,8 @@ void Player::Update(uint32 p_time) } } + GetAchievementMgr().UpdateTimedAchievements(p_time); + if (hasUnitState(UNIT_STAT_MELEE_ATTACKING) && !hasUnitState(UNIT_STAT_CASTING)) { if (Unit *pVictim = getVictim()) @@ -13975,6 +13977,8 @@ void Player::AddQuest(Quest const *pQuest, Object *questGiver) if (questStatusData.uState != QUEST_NEW) questStatusData.uState = QUEST_CHANGED; + GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_QUEST, quest_id); + //starting initial quest script if (questGiver && pQuest->GetQuestStartScript() != 0) GetMap()->ScriptsStart(sQuestStartScripts, pQuest->GetQuestStartScript(), questGiver, this); @@ -14899,7 +14903,9 @@ void Player::KilledMonsterCredit(uint32 entry, uint64 guid) real_entry = killed->GetEntry(); } + GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_CREATURE, real_entry); // MUST BE CALLED FIRST GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, real_entry, addkillcount); + for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) { uint32 questid = GetQuestSlotQuestId(i); diff --git a/src/game/QuestHandler.cpp b/src/game/QuestHandler.cpp index 1da286509dd..9b03f68efa8 100644 --- a/src/game/QuestHandler.cpp +++ b/src/game/QuestHandler.cpp @@ -392,6 +392,7 @@ void WorldSession::HandleQuestLogRemoveQuest(WorldPacket& recv_data) _player->TakeQuestSourceItem(quest, true); // remove quest src item from player _player->SetQuestStatus(quest, QUEST_STATUS_NONE); + _player->GetAchievementMgr().RemoveTimedAchievement(ACHIEVEMENT_TIMED_TYPE_QUEST, quest); } _player->SetQuestSlot(slot, 0); diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index cea7d10735c..fb871a789e9 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -1266,12 +1266,14 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit *unit, const uint32 effectMask, bool if (unit->GetTypeId() == TYPEID_PLAYER) { + unit->ToPlayer()->GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_SPELL_TARGET, m_spellInfo->Id); unit->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, m_spellInfo->Id); unit->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, m_spellInfo->Id); } if (m_caster->GetTypeId() == TYPEID_PLAYER) { + m_caster->ToPlayer()->GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_SPELL_CASTER, m_spellInfo->Id); m_caster->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2, m_spellInfo->Id, 0, unit); } @@ -2929,7 +2931,10 @@ void Spell::cast(bool skipCheck) if (m_caster->GetTypeId() == TYPEID_PLAYER) { if (!m_IsTriggeredSpell && m_CastItem) + { + m_caster->ToPlayer()->GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_ITEM, m_CastItem->GetEntry()); m_caster->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM, m_CastItem->GetEntry()); + } m_caster->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, m_spellInfo->Id); } |