Implement support of Timed Achievement. Thanks Shauren.

Fixes issue #1431.

--HG--
branch : trunk
This commit is contained in:
Trazom62
2010-04-27 22:22:13 +02:00
parent 90f38341f0
commit 36ebd58b37
8 changed files with 152 additions and 26 deletions
+117 -16
View File
@@ -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());
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);
// also it seems illogical, the timeframe will be extended at every criteria update
progress->date = now;
// Remove the timer, we wont need it anymore
if (timedCompleted)
m_timedAchievements.erase(timedIter);
}
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;
+9 -2
View File
@@ -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
+10 -4
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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";
+6
View File
@@ -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);
+1
View File
@@ -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);
+5
View File
@@ -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);
}