diff options
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 653 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 14 | ||||
-rw-r--r-- | src/server/game/Handlers/MiscHandler.cpp | 29 | ||||
-rw-r--r-- | src/server/game/Quests/QuestDef.h | 39 |
4 files changed, 229 insertions, 506 deletions
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 54375f7136e..b01611ff9e3 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -15024,7 +15024,7 @@ bool Player::CanAddQuest(Quest const* quest, bool msg) const return true; } -bool Player::CanCompleteQuest(uint32 quest_id) +bool Player::CanCompleteQuest(uint32 quest_id, uint32 ignoredQuestObjectiveId /*= 0*/) { if (quest_id) { @@ -15049,6 +15049,9 @@ bool Player::CanCompleteQuest(uint32 quest_id) { for (QuestObjective const& obj : qInfo->GetObjectives()) { + if (ignoredQuestObjectiveId && obj.ID == ignoredQuestObjectiveId) + continue; + if (!(obj.Flags & QUEST_OBJECTIVE_FLAG_OPTIONAL) && !(obj.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR)) { if (!IsQuestObjectiveComplete(q_status.Slot, qInfo, obj)) @@ -15298,7 +15301,8 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) uint32 quest_id = quest->GetQuestId(); // if not exist then created with set uState == NEW and rewarded=false - QuestStatusData& questStatusData = m_QuestStatus[quest_id]; + auto questStatusItr = m_QuestStatus.emplace(quest_id, QuestStatusData{}).first; + QuestStatusData& questStatusData = questStatusItr->second; QuestStatus oldStatus = questStatusData.Status; // check for repeatable quests status reset @@ -15309,6 +15313,7 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) for (QuestObjective const& obj : quest->GetObjectives()) { + m_questObjectiveStatus.emplace(std::make_pair(QuestObjectiveType(obj.Type), obj.ObjectID), QuestObjectiveStatusData { questStatusItr, &obj }); switch (obj.Type) { case QUEST_OBJECTIVE_MIN_REPUTATION: @@ -16344,6 +16349,13 @@ void Player::RemoveActiveQuest(uint32 questId, bool update /*= true*/) QuestStatusMap::iterator itr = m_QuestStatus.find(questId); if (itr != m_QuestStatus.end()) { + for (auto objectiveItr = m_questObjectiveStatus.begin(); objectiveItr != m_questObjectiveStatus.end(); ) + { + if (objectiveItr->second.QuestStatusItr == itr) + objectiveItr = m_questObjectiveStatus.erase(objectiveItr); + else + ++objectiveItr; + } m_QuestStatus.erase(itr); m_QuestStatusSave[questId] = QUEST_DELETE_SAVE_TYPE; } @@ -16732,82 +16744,31 @@ void Player::GroupEventHappens(uint32 questId, WorldObject const* pEventObject) void Player::ItemAddedQuestCheck(uint32 entry, uint32 count) { - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - uint32 questid = GetQuestSlotQuestId(i); - if (questid == 0) - continue; - - QuestStatusData& q_status = m_QuestStatus[questid]; - - if (q_status.Status != QUEST_STATUS_INCOMPLETE) - continue; - - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_ITEM)) - continue; - - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_ITEM || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - if (uint32(obj.ObjectID) != entry) - continue; - - uint32 reqItemCount = obj.Amount; - uint32 curItemCount = GetQuestSlotObjectiveData(i, obj); - if (curItemCount < reqItemCount) - { - uint32 newItemCount = std::min<uint32>(curItemCount + count, reqItemCount); - SetQuestObjectiveData(obj, newItemCount); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - break; - } - } - - UpdateForQuestWorldObjects(); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_ITEM, entry, count); } void Player::ItemRemovedQuestCheck(uint32 entry, uint32 count) { - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_ITEM, entry })) { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; + uint32 questId = objectiveItr.second.QuestStatusItr->first; + Quest const* quest = sObjectMgr->GetQuestTemplate(questId); + uint16 logSlot = objectiveItr.second.QuestStatusItr->second.Slot; + QuestObjective const& objective = *objectiveItr.second.Objective; - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_ITEM)) + if (!IsQuestObjectiveCompletable(logSlot, quest, objective)) continue; - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_ITEM || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - if (uint32(obj.ObjectID) != entry) - continue; - - uint32 reqItemCount = obj.Amount; - uint16 curItemCount = GetQuestSlotObjectiveData(i, obj); - - if (curItemCount >= reqItemCount) // we may have more than what the status shows - curItemCount = GetItemCount(entry, false); + int32 curItemCount = GetQuestSlotObjectiveData(logSlot, objective); + if (curItemCount >= objective.Amount) // we may have more than what the status shows + curItemCount = GetItemCount(entry, false); - uint16 newItemCount = (count > curItemCount) ? 0 : curItemCount - count; - - if (newItemCount < reqItemCount) - { - SetQuestObjectiveData(obj, newItemCount); - IncompleteQuest(questid); - } + int32 newItemCount = (int32(count) > curItemCount) ? 0 : curItemCount - count; - break; + if (newItemCount < objective.Amount) + { + SetQuestObjectiveData(objective, newItemCount); + IncompleteQuest(questId); } } @@ -16841,503 +16802,205 @@ void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid /*= ObjectGuid::E StartCriteriaTimer(CRITERIA_TIMED_TYPE_CREATURE, real_entry); // MUST BE CALLED FIRST UpdateCriteria(CRITERIA_TYPE_KILL_CREATURE, real_entry, addKillCount, 0, killed); - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; - - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_MONSTER)) - continue; - - - if (m_QuestStatus[questid].Status != QUEST_STATUS_INCOMPLETE) - continue; - - if (GetGroup() && GetGroup()->isRaidGroup() && qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) - continue; - - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_MONSTER || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - if (uint32(obj.ObjectID) != entry) - continue; - - uint32 reqKillCount = obj.Amount; - uint16 curKillCount = GetQuestSlotObjectiveData(i, obj); - if (curKillCount < reqKillCount) - { - SetQuestObjectiveData(obj, curKillCount + addKillCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, curKillCount + addKillCount); - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES) && IsQuestObjectiveComplete(i, qInfo, obj)) - completedObjectiveInSequencedQuest = true; - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; - } - } - - if (completedObjectiveInSequencedQuest) - UpdateForQuestWorldObjects(); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MONSTER, entry, 1, guid); } void Player::KilledPlayerCredit() { - uint16 addKillCount = 1; - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; - - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_PLAYERKILLS)) - continue; - - // just if !ingroup || !noraidgroup || raidgroup - QuestStatusData& q_status = m_QuestStatus[questid]; - if (q_status.Status == QUEST_STATUS_INCOMPLETE && (!GetGroup() || !GetGroup()->isRaidGroup() || qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))) - { - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_PLAYERKILLS || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - uint32 curKillCount = GetQuestSlotObjectiveData(i, obj); - if (curKillCount < uint32(obj.Amount)) - { - SetQuestObjectiveData(obj, curKillCount + addKillCount); - SendQuestUpdateAddPlayer(qInfo, curKillCount + addKillCount); - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES) && IsQuestObjectiveComplete(i, qInfo, obj)) - completedObjectiveInSequencedQuest = true; - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // Quest can't have more than one player kill objective (code optimisation) - break; - } - } - } - - if (completedObjectiveInSequencedQuest) - UpdateForQuestWorldObjects(); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_PLAYERKILLS, 0, 1); } void Player::KillCreditGO(uint32 entry, ObjectGuid guid) { - uint16 addCastCount = 1; - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; - - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_GAMEOBJECT)) - continue; - - if (m_QuestStatus[questid].Status != QUEST_STATUS_INCOMPLETE) - continue; - - if (GetGroup() && GetGroup()->isRaidGroup() && qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) - continue; - - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_GAMEOBJECT || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - // other not this creature/GO related objectives - if (uint32(obj.ObjectID) != entry) - continue; - - uint32 reqCastCount = obj.Amount; - uint32 curCastCount = GetQuestSlotObjectiveData(i, obj); - if (curCastCount < reqCastCount) - { - SetQuestObjectiveData(obj, curCastCount + addCastCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, curCastCount + addCastCount); - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES) && IsQuestObjectiveComplete(i, qInfo, obj)) - completedObjectiveInSequencedQuest = true; - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; - } - } - - if (completedObjectiveInSequencedQuest) - UpdateForQuestWorldObjects(); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_GAMEOBJECT, entry, 1, guid); } void Player::TalkedToCreature(uint32 entry, ObjectGuid guid) { - uint16 addTalkCount = 1; - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; - - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_TALKTO)) - continue; - - if (m_QuestStatus[questid].Status != QUEST_STATUS_INCOMPLETE) - continue; - - if (GetGroup() && GetGroup()->isRaidGroup() && qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) - continue; - - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_TALKTO || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - if (uint32(obj.ObjectID) != entry) - continue; - - uint32 reqTalkCount = obj.Amount; - uint32 curTalkCount = GetQuestSlotObjectiveData(i, obj); - if (curTalkCount < reqTalkCount) - { - SetQuestObjectiveData(obj, curTalkCount + addTalkCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, curTalkCount + addTalkCount); - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES) && IsQuestObjectiveComplete(i, qInfo, obj)) - completedObjectiveInSequencedQuest = true; - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // Quest can't have more than one objective for the same creature (code optimisation) - break; - } - } - - if (completedObjectiveInSequencedQuest) - UpdateForQuestWorldObjects(); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_TALKTO, entry, 1, guid); } void Player::KillCreditCriteriaTreeObjective(QuestObjective const& questObjective) { - if (questObjective.Type != QUEST_OBJECTIVE_CRITERIA_TREE) - return; + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_CRITERIA_TREE, questObjective.ObjectID, 1); +} - Quest const* quest = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(questObjective.QuestID)); - if (!IsQuestObjectiveComplete(FindQuestSlot(questObjective.QuestID), quest, questObjective)) - { - SetQuestObjectiveData(questObjective, 1); - SendQuestUpdateAddCreditSimple(questObjective); +void Player::MoneyChanged(uint64 value) +{ + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MONEY, 0, int64(value) - int64(GetMoney())); +} - if (CanCompleteQuest(questObjective.QuestID)) - CompleteQuest(questObjective.QuestID); +void Player::ReputationChanged(FactionEntry const* factionEntry, int32 change) +{ + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MIN_REPUTATION, factionEntry->ID, change); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MAX_REPUTATION, factionEntry->ID, change); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_INCREASE_REPUTATION, factionEntry->ID, change); +} - if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - UpdateForQuestWorldObjects(); - } +void Player::CurrencyChanged(uint32 currencyId, int32 change) +{ + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_CURRENCY, currencyId, change); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_HAVE_CURRENCY, currencyId, change); + UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_OBTAIN_CURRENCY, currencyId, change); } -void Player::MoneyChanged(uint64 value) +void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int32 objectId, int64 addCount, ObjectGuid victimGuid) { - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + bool anyObjectiveChangedCompletionState = false; + + for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { objectiveType, objectId })) { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; + uint32 questId = objectiveItr.second.QuestStatusItr->first; + Quest const* quest = sObjectMgr->GetQuestTemplate(questId); - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo) - continue; + if (!QuestObjective::CanAlwaysBeProgressedInRaid(objectiveType)) + if (GetGroup() && GetGroup()->isRaidGroup() && quest->IsAllowedInRaid(GetMap()->GetDifficultyID())) + continue; - if (!qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_MONEY)) + uint16 logSlot = objectiveItr.second.QuestStatusItr->second.Slot; + QuestObjective const& objective = *objectiveItr.second.Objective; + if (!IsQuestObjectiveCompletable(logSlot, quest, objective)) continue; - QuestStatusData& q_status = m_QuestStatus[questid]; - - for (QuestObjective const& obj : qInfo->GetObjectives()) + bool objectiveWasComplete = IsQuestObjectiveComplete(logSlot, quest, objective); + if (!objectiveWasComplete || addCount < 0) { - if (obj.Type != QUEST_OBJECTIVE_MONEY || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - if (!IsQuestObjectiveComplete(i, qInfo, obj)) + bool objectiveIsNowComplete = false; + if (objective.IsStoringValue()) { - if (int64(value) >= int64(obj.Amount)) + int32 currentProgress = GetQuestSlotObjectiveData(logSlot, objective); + if (addCount > 0 ? (currentProgress < objective.Amount) : (currentProgress > 0)) { - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + int32 newProgress = advstd::clamp<int32>(currentProgress + addCount, 0, objective.Amount); + SetQuestObjectiveData(objective, newProgress); + if (addCount > 0 && !(objective.Flags & QUEST_OBJECTIVE_FLAG_HIDE_CREDIT_MSG)) + { + if (objectiveType != QUEST_OBJECTIVE_PLAYERKILLS) + SendQuestUpdateAddCredit(quest, victimGuid, objective, newProgress); + else + SendQuestUpdateAddPlayer(quest, newProgress); + } - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - completedObjectiveInSequencedQuest = true; + objectiveIsNowComplete = IsQuestObjectiveComplete(logSlot, quest, objective); } } - else if (q_status.Status == QUEST_STATUS_COMPLETE) + else if (objective.IsStoringFlag()) { - if (int64(value) < obj.Amount) - IncompleteQuest(questid); - } - } - } + SetQuestObjectiveData(objective, addCount > 0); - if (completedObjectiveInSequencedQuest) - UpdateForQuestWorldObjects(); -} + if (addCount > 0 && !(objective.Flags & QUEST_OBJECTIVE_FLAG_HIDE_CREDIT_MSG)) + SendQuestUpdateAddCreditSimple(objective); -void Player::ReputationChanged(FactionEntry const* factionEntry, int32 change) -{ - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - if (uint32 questid = GetQuestSlotQuestId(i)) - { - if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid)) + objectiveIsNowComplete = IsQuestObjectiveComplete(logSlot, quest, objective); + } + else { - QuestStatusData& q_status = m_QuestStatus[questid]; - - if (!qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_MIN_REPUTATION) - && !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_MAX_REPUTATION) - && (!qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_INCREASE_REPUTATION) || q_status.Status != QUEST_STATUS_INCOMPLETE)) - continue; - - for (QuestObjective const& obj : qInfo->GetObjectives()) + switch (objectiveType) { - if (uint32(obj.ObjectID) != factionEntry->ID || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - switch (obj.Type) - { - case QUEST_OBJECTIVE_MIN_REPUTATION: - if (!IsQuestObjectiveComplete(i, qInfo, obj)) - { - if (GetReputationMgr().GetReputation(factionEntry) + change >= obj.Amount) - { - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - completedObjectiveInSequencedQuest = true; - } - } - else if (q_status.Status == QUEST_STATUS_COMPLETE) - { - if (GetReputationMgr().GetReputation(factionEntry) + change < obj.Amount) - IncompleteQuest(questid); - } - break; - case QUEST_OBJECTIVE_MAX_REPUTATION: - if (!IsQuestObjectiveComplete(i, qInfo, obj)) - { - if (GetReputationMgr().GetReputation(factionEntry) + change <= obj.Amount) - { - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - completedObjectiveInSequencedQuest = true; - } - } - else if (q_status.Status == QUEST_STATUS_COMPLETE) - { - if (GetReputationMgr().GetReputation(factionEntry) + change > obj.Amount) - IncompleteQuest(questid); - } - break; - case QUEST_OBJECTIVE_INCREASE_REPUTATION: - { - int32 currentProgress = GetQuestSlotObjectiveData(i, obj); - if (change > 0 && currentProgress < obj.Amount) - { - SetQuestObjectiveData(obj, currentProgress + change); - SendQuestUpdateAddCredit(qInfo, ObjectGuid::Empty, obj, currentProgress + change); - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES) && IsQuestObjectiveComplete(i, qInfo, obj)) - completedObjectiveInSequencedQuest = true; - } - break; - } - default: - break; - } + case QUEST_OBJECTIVE_CURRENCY: + objectiveIsNowComplete = GetCurrency(objectId) + addCount >= objective.Amount; + break; + case QUEST_OBJECTIVE_LEARNSPELL: + objectiveIsNowComplete = addCount != 0; + break; + case QUEST_OBJECTIVE_MIN_REPUTATION: + objectiveIsNowComplete = GetReputationMgr().GetReputation(objectId) + addCount >= objective.Amount; + break; + case QUEST_OBJECTIVE_MAX_REPUTATION: + objectiveIsNowComplete = GetReputationMgr().GetReputation(objectId) + addCount <= objective.Amount; + break; + case QUEST_OBJECTIVE_MONEY: + objectiveIsNowComplete = int64(GetMoney()) + addCount >= objective.Amount; + break; + case QUEST_OBJECTIVE_PROGRESS_BAR: + objectiveIsNowComplete = IsQuestObjectiveProgressBarComplete(logSlot, quest); + break; + default: + ASSERT(false, "Unhandled quest objective type %u", uint32(objectiveType)); + break; } } - } - } - if (completedObjectiveInSequencedQuest) - UpdateForQuestWorldObjects(); -} - -void Player::CurrencyChanged(uint32 currencyId, int32 change) -{ - bool completedObjectiveInSequencedQuest = false; - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) - { - uint32 questid = GetQuestSlotQuestId(i); - if (!questid) - continue; - - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo) - continue; - - QuestStatusData& q_status = m_QuestStatus[questid]; - - if (!qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_CURRENCY) - && !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_HAVE_CURRENCY) - && (!qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_OBTAIN_CURRENCY) || q_status.Status != QUEST_STATUS_INCOMPLETE)) - continue; - - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (uint32(obj.ObjectID) != currencyId || !IsQuestObjectiveCompletable(i, qInfo, obj)) - continue; - - QuestStatusData& q_status = m_QuestStatus[questid]; - switch (obj.Type) + if (objective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR) { - case QUEST_OBJECTIVE_CURRENCY: + if (IsQuestObjectiveProgressBarComplete(logSlot, quest)) { - if (!IsQuestObjectiveComplete(i, qInfo, obj)) + auto progressBarObjectiveItr = std::find_if(quest->GetObjectives().begin(), quest->GetObjectives().end(), [](QuestObjective const& otherObjective) { - if (int64(GetCurrency(currencyId)) + change >= obj.Amount) - { - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + return otherObjective.Type == QUEST_OBJECTIVE_PROGRESS_BAR && !(otherObjective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR); + }); + if (progressBarObjectiveItr != quest->GetObjectives().end()) + SendQuestUpdateAddCreditSimple(*progressBarObjectiveItr); - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - completedObjectiveInSequencedQuest = true; - } - } - else if (q_status.Status == QUEST_STATUS_COMPLETE) - { - if (int64(GetCurrency(currencyId)) + change < obj.Amount) - IncompleteQuest(questid); - } - break; - } - case QUEST_OBJECTIVE_HAVE_CURRENCY: - { - int32 newProgress = int32(std::min<int64>(int64(GetCurrency(currencyId)) + change, obj.Amount)); - SetQuestObjectiveData(obj, newProgress); - if (!IsQuestObjectiveComplete(i, qInfo, obj)) - { - if (newProgress >= obj.Amount) - { - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - completedObjectiveInSequencedQuest = true; - } - } - else if (q_status.Status == QUEST_STATUS_COMPLETE) - { - if (newProgress < obj.Amount) - IncompleteQuest(questid); - } - break; + objectiveIsNowComplete = true; } - case QUEST_OBJECTIVE_OBTAIN_CURRENCY: - { - int32 currentProgress = GetQuestSlotObjectiveData(i, obj); - if (change > 0 && currentProgress < obj.Amount) - { - SetQuestObjectiveData(obj, currentProgress + change); - SendQuestUpdateAddCredit(qInfo, ObjectGuid::Empty, obj, currentProgress + change); + } - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + if (objectiveWasComplete != objectiveIsNowComplete) + anyObjectiveChangedCompletionState = true; - if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES) && IsQuestObjectiveComplete(i, qInfo, obj)) - completedObjectiveInSequencedQuest = true; - } - break; - } - default: - break; - } + if (objectiveIsNowComplete && CanCompleteQuest(questId, objective.ID)) + CompleteQuest(questId); + else if (objectiveItr.second.QuestStatusItr->second.Status == QUEST_STATUS_COMPLETE) + IncompleteQuest(questId); } } - if (completedObjectiveInSequencedQuest) + if (anyObjectiveChangedCompletionState) UpdateForQuestWorldObjects(); } bool Player::HasQuestForItem(uint32 itemid) const { - for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + // Search incomplete objective first + for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_ITEM, itemid })) { - uint32 questid = GetQuestSlotQuestId(i); - if (questid == 0) + Quest const* qInfo = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(objectiveItr.second.QuestStatusItr->first)); + QuestObjective const& objective = *objectiveItr.second.Objective; + if (!IsQuestObjectiveCompletable(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, objective)) continue; - QuestStatusMap::const_iterator qs_itr = m_QuestStatus.find(questid); - if (qs_itr == m_QuestStatus.end()) + // hide quest if player is in raid-group and quest is no raid quest + if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) + if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later + continue; + + if (!IsQuestObjectiveComplete(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, objective)) + return true; + } + + // This part - for ItemDrop + for (std::pair<uint32 const, QuestStatusData> const& questStatus : m_QuestStatus) + { + if (questStatus.second.Status != QUEST_STATUS_INCOMPLETE) continue; - QuestStatusData const& q_status = qs_itr->second; + Quest const* qInfo = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(questStatus.first)); + // hide quest if player is in raid-group and quest is no raid quest + if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) + if (!InBattleground()) + continue; - if (q_status.Status == QUEST_STATUS_INCOMPLETE) + for (uint8 j = 0; j < QUEST_ITEM_DROP_COUNT; ++j) { - Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo) + // examined item is a source item + if (qInfo->ItemDrop[j] != itemid) continue; - // hide quest if player is in raid-group and quest is no raid quest - if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) - if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later - continue; + ItemTemplate const* pProto = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(itemid)); - // There should be no mixed ReqItem/ReqSource drop - // This part for ReqItem drop - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type == QUEST_OBJECTIVE_ITEM && IsQuestObjectiveCompletable(i, qInfo, obj) && itemid == uint32(obj.ObjectID) && GetQuestSlotObjectiveData(i, obj) < obj.Amount) - return true; - } - // This part - for ReqSource - for (uint8 j = 0; j < QUEST_ITEM_DROP_COUNT; ++j) - { - // examined item is a source item - if (qInfo->ItemDrop[j] == itemid) - { - ItemTemplate const* pProto = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(itemid)); + // allows custom amount drop when not 0 + uint32 maxAllowedCount = qInfo->ItemDropQuantity[j] ? qInfo->ItemDropQuantity[j] : pProto->GetMaxStackSize(); - // 'unique' item - if (pProto->GetMaxCount() && GetItemCount(itemid, true) < pProto->GetMaxCount()) - return true; + // 'unique' item + if (pProto->GetMaxCount() && pProto->GetMaxCount() < maxAllowedCount) + maxAllowedCount = pProto->GetMaxCount(); - // allows custom amount drop when not 0 - if (qInfo->ItemDropQuantity[j]) - { - if (GetItemCount(itemid, true) < qInfo->ItemDropQuantity[j]) - return true; - } else if (GetItemCount(itemid, true) < pProto->GetMaxStackSize()) - return true; - } - } + if (GetItemCount(itemid, true) < maxAllowedCount) + return true; } } + return false; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e74a37c7bf9..ceaf1fc8b5a 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -24,6 +24,7 @@ #include "DBCEnums.h" #include "EquipmentSet.h" #include "GroupReference.h" +#include "Hash.h" #include "ItemDefines.h" #include "ItemEnchantmentMgr.h" #include "MapReference.h" @@ -529,6 +530,15 @@ enum AtLoginFlags }; typedef std::map<uint32, QuestStatusData> QuestStatusMap; + +struct QuestObjectiveStatusData +{ + QuestStatusMap::iterator QuestStatusItr; + QuestObjective const* Objective; +}; + +using QuestObjectiveStatusMap = std::unordered_multimap<std::pair<QuestObjectiveType, int32>, QuestObjectiveStatusData>; + typedef std::set<uint32> RewardedQuestSet; enum QuestSaveType @@ -1446,7 +1456,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> bool CanSeeStartQuest(Quest const* quest); bool CanTakeQuest(Quest const* quest, bool msg); bool CanAddQuest(Quest const* quest, bool msg) const; - bool CanCompleteQuest(uint32 quest_id); + bool CanCompleteQuest(uint32 quest_id, uint32 ignoredQuestObjectiveId = 0); bool CanCompleteRepeatableQuest(Quest const* quest); bool CanRewardQuest(Quest const* quest, bool msg) const; bool CanRewardQuest(Quest const* quest, LootItemType rewardType, uint32 rewardId, bool msg) const; @@ -1529,6 +1539,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void MoneyChanged(uint64 value); void ReputationChanged(FactionEntry const* factionEntry, int32 change); void CurrencyChanged(uint32 currencyId, int32 change); + void UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int32 objectId, int64 addCount, ObjectGuid victimGuid = ObjectGuid::Empty); bool HasQuestForItem(uint32 itemId) const; bool HasQuestForGO(int32 goId) const; void UpdateForQuestWorldObjects(); @@ -2825,6 +2836,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> uint32 m_ExtraFlags; QuestStatusMap m_QuestStatus; + QuestObjectiveStatusMap m_questObjectiveStatus; QuestStatusSaveMap m_QuestStatusSave; RewardedQuestSet m_RewardedQuests; diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index 2ffacd75ff9..0f8ad12a769 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -489,9 +489,10 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge if (player->IsAlive()) { + // not using Player::UpdateQuestObjectiveProgress, ObjectID in quest_objectives can be set to -1, areatrigger_involvedrelation then holds correct id if (std::unordered_set<uint32> const* quests = sObjectMgr->GetQuestsForAreaTrigger(packet.AreaTriggerID)) { - bool completedObjectiveInSequencedQuest = false; + bool anyObjectiveChangedCompletionState = false; for (uint32 questId : *quests) { Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); @@ -500,14 +501,22 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge { for (QuestObjective const& obj : qInfo->Objectives) { - if (obj.Type == QUEST_OBJECTIVE_AREATRIGGER && !player->IsQuestObjectiveComplete(slot, qInfo, obj)) - { - player->SetQuestObjectiveData(obj, 1); - player->SendQuestUpdateAddCreditSimple(obj); - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) - completedObjectiveInSequencedQuest = true; - break; - } + if (obj.Type != QUEST_OBJECTIVE_AREATRIGGER) + continue; + + if (!player->IsQuestObjectiveCompletable(slot, qInfo, obj)) + continue; + + if (player->IsQuestObjectiveComplete(slot, qInfo, obj)) + continue; + + if (obj.ObjectID != -1 && obj.ObjectID != packet.AreaTriggerID) + continue; + + player->SetQuestObjectiveData(obj, 1); + player->SendQuestUpdateAddCreditSimple(obj); + anyObjectiveChangedCompletionState = true; + break; } if (player->CanCompleteQuest(questId)) @@ -515,7 +524,7 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge } } - if (completedObjectiveInSequencedQuest) + if (anyObjectiveChangedCompletionState) player->UpdateForQuestWorldObjects(); } } diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index bb2ab57fcc3..9b74cbb978e 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -373,6 +373,26 @@ struct QuestObjective std::string Description; std::vector<int32> VisualEffects; + bool IsStoringValue() const + { + switch (Type) + { + case QUEST_OBJECTIVE_MONSTER: + case QUEST_OBJECTIVE_ITEM: + case QUEST_OBJECTIVE_GAMEOBJECT: + case QUEST_OBJECTIVE_TALKTO: + case QUEST_OBJECTIVE_PLAYERKILLS: + case QUEST_OBJECTIVE_WINPVPPETBATTLES: + case QUEST_OBJECTIVE_HAVE_CURRENCY: + case QUEST_OBJECTIVE_OBTAIN_CURRENCY: + case QUEST_OBJECTIVE_INCREASE_REPUTATION: + return true; + default: + break; + } + return false; + } + bool IsStoringFlag() const { switch (Type) @@ -389,6 +409,25 @@ struct QuestObjective } return false; } + + static constexpr bool CanAlwaysBeProgressedInRaid(QuestObjectiveType type) + { + switch (type) + { + case QUEST_OBJECTIVE_ITEM: + case QUEST_OBJECTIVE_CURRENCY: + case QUEST_OBJECTIVE_LEARNSPELL: + case QUEST_OBJECTIVE_MIN_REPUTATION: + case QUEST_OBJECTIVE_MAX_REPUTATION: + case QUEST_OBJECTIVE_MONEY: + case QUEST_OBJECTIVE_HAVE_CURRENCY: + case QUEST_OBJECTIVE_INCREASE_REPUTATION: + return true; + default: + break; + } + return false; + } }; using QuestObjectives = std::vector<QuestObjective>; |