diff options
author | Shauren <shauren.trinity@gmail.com> | 2021-05-09 20:21:32 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2021-05-09 20:21:32 +0200 |
commit | 91b91b502f30f781c8dea77dec4ce2f469b89744 (patch) | |
tree | ec4b30e134e7390c833d5a9612eea036263beec6 /src | |
parent | dd8aed421a48225b0e60c677969d1fc133954279 (diff) |
Core/Quests: Quest improvements
* Add quest log slot argument to functions dealing with objective progress
* Implemented QUEST_OBJECTIVE_FLAG_SEQUENCED (prevent progressing hidden objectives)
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartAI.cpp | 3 | ||||
-rw-r--r-- | src/server/game/Achievements/CriteriaHandler.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Conditions/ConditionMgr.cpp | 15 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 742 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 13 | ||||
-rw-r--r-- | src/server/game/Globals/ObjectMgr.cpp | 14 | ||||
-rw-r--r-- | src/server/game/Handlers/MiscHandler.cpp | 11 | ||||
-rw-r--r-- | src/server/game/Quests/QuestDef.h | 3 | ||||
-rw-r--r-- | src/server/game/Quests/QuestObjectiveCriteriaMgr.cpp | 8 | ||||
-rw-r--r-- | src/server/game/Reputation/ReputationMgr.cpp | 7 | ||||
-rw-r--r-- | src/server/scripts/Northrend/zone_sholazar_basin.cpp | 2 |
11 files changed, 537 insertions, 287 deletions
diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index ef6bb5943b8..30138c5cb10 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -1199,7 +1199,8 @@ public: // Called when a quest objective data change void OnQuestObjectiveChange(Player* player, Quest const* quest, QuestObjective const& objective, int32 /*oldAmount*/, int32 /*newAmount*/) override { - if (player->IsQuestObjectiveComplete(objective)) + uint16 slot = player->FindQuestSlot(quest->GetQuestId()); + if (slot < MAX_QUEST_LOG_SIZE && player->IsQuestObjectiveComplete(slot, quest, objective)) { SmartScript smartScript; smartScript.OnInitialize(nullptr, nullptr, nullptr, quest); diff --git a/src/server/game/Achievements/CriteriaHandler.cpp b/src/server/game/Achievements/CriteriaHandler.cpp index 57a45421017..63a38494ab0 100644 --- a/src/server/game/Achievements/CriteriaHandler.cpp +++ b/src/server/game/Achievements/CriteriaHandler.cpp @@ -2080,7 +2080,11 @@ bool CriteriaHandler::ModifierSatisfied(ModifierTreeEntry const* modifier, uint6 QuestObjective const* objective = sObjectMgr->GetQuestObjective(reqValue); if (!objective) return false; - if (referencePlayer->GetQuestRewardStatus(objective->QuestID) || !referencePlayer->IsQuestObjectiveComplete(*objective)) + Quest const* quest = sObjectMgr->GetQuestTemplate(objective->QuestID); + if (!quest) + return false; + uint16 slot = referencePlayer->FindQuestSlot(objective->QuestID); + if (slot >= MAX_QUEST_LOG_SIZE || referencePlayer->GetQuestRewardStatus(objective->QuestID) || !referencePlayer->IsQuestObjectiveComplete(slot, quest, *objective)) return false; break; } diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 3580a6903ec..0a328e20fa6 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -524,7 +524,15 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const if (!obj) break; - condMeets = (!player->GetQuestRewardStatus(obj->QuestID) && player->IsQuestObjectiveComplete(*obj)); + Quest const* quest = sObjectMgr->GetQuestTemplate(obj->QuestID); + if (!quest) + break; + + uint16 slot = player->FindQuestSlot(obj->QuestID); + if (slot >= MAX_QUEST_LOG_SIZE) + break; + + condMeets = (!player->GetQuestRewardStatus(obj->QuestID) && player->IsQuestObjectiveComplete(slot, quest, *obj)); } break; } @@ -2929,7 +2937,8 @@ bool ConditionMgr::IsPlayerMeetingCondition(Player const* player, PlayerConditio if (condition->QuestKillID) { Quest const* quest = sObjectMgr->GetQuestTemplate(condition->QuestKillID); - if (quest && player->GetQuestStatus(condition->QuestKillID) != QUEST_STATUS_COMPLETE) + uint16 questSlot = player->FindQuestSlot(condition->QuestKillID); + if (quest && player->GetQuestStatus(condition->QuestKillID) != QUEST_STATUS_COMPLETE && questSlot < MAX_QUEST_LOG_SIZE) { using QuestKillCount = std::extent<decltype(condition->QuestKillMonster)>; @@ -2944,7 +2953,7 @@ bool ConditionMgr::IsPlayerMeetingCondition(Player const* player, PlayerConditio return objective.Type == QUEST_OBJECTIVE_MONSTER && uint32(objective.ObjectID) == condition->QuestKillMonster[i]; }); if (objectiveItr != quest->GetObjectives().end()) - results[i] = player->GetQuestObjectiveData(quest, objectiveItr->StorageIndex) >= objectiveItr->Amount; + results[i] = player->GetQuestSlotObjectiveData(questSlot, *objectiveItr) >= objectiveItr->Amount; } } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 1443d0d72d0..54375f7136e 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -6877,19 +6877,6 @@ bool Player::HasCurrency(uint32 id, uint32 count) const return itr != _currencyStorage.end() && itr->second.Quantity >= count; } -bool Player::IsQuestObjectiveProgressComplete(Quest const* quest) const -{ - float progress = 0; - for (QuestObjective const& obj : quest->GetObjectives()) - if (obj.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR) - { - progress += GetQuestObjectiveData(quest, obj.StorageIndex) * obj.ProgressBarWeight; - if (progress >= 100) - return true; - } - return false; -} - void Player::ModifyCurrency(uint32 id, int32 count, bool printLog/* = true*/, bool ignoreMultipliers/* = false*/) { if (!count) @@ -6983,6 +6970,8 @@ void Player::ModifyCurrency(uint32 id, int32 count, bool printLog/* = true*/, bo if (itr->second.state != PLAYERCURRENCY_NEW) itr->second.state = PLAYERCURRENCY_CHANGED; + CurrencyChanged(id, newTotalCount - itr->second.Quantity); + itr->second.Quantity = newTotalCount; itr->second.WeeklyQuantity = newWeekCount; itr->second.TrackedQuantity = newTrackedCount; @@ -6990,8 +6979,6 @@ void Player::ModifyCurrency(uint32 id, int32 count, bool printLog/* = true*/, bo if (count > 0) UpdateCriteria(CRITERIA_TYPE_CURRENCY, id, count); - CurrencyChanged(id, count); - WorldPackets::Misc::SetCurrency packet; packet.Type = id; packet.Quantity = newTotalCount; @@ -15064,7 +15051,7 @@ bool Player::CanCompleteQuest(uint32 quest_id) { if (!(obj.Flags & QUEST_OBJECTIVE_FLAG_OPTIONAL) && !(obj.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR)) { - if (!IsQuestObjectiveComplete(obj)) + if (!IsQuestObjectiveComplete(q_status.Slot, qInfo, obj)) return false; } } @@ -15317,15 +15304,8 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) // check for repeatable quests status reset questStatusData.Status = QUEST_STATUS_INCOMPLETE; - int32 maxStorageIndex = 0; - for (QuestObjective const& obj : quest->GetObjectives()) - if (obj.StorageIndex > maxStorageIndex) - maxStorageIndex = obj.StorageIndex; - - questStatusData.ObjectiveData.resize(maxStorageIndex+1, 0); - GiveQuestSourceItem(quest); - AdjustQuestReqItemCount(quest); + AdjustQuestObjectiveProgress(quest); for (QuestObjective const& obj : quest->GetObjectives()) { @@ -15375,6 +15355,7 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty)); } + questStatusData.Slot = log_slot; SetQuestSlot(log_slot, quest_id, qtime); m_QuestStatusSave[quest_id] = QUEST_DEFAULT_SAVE_TYPE; @@ -16539,14 +16520,18 @@ uint16 Player::GetReqKillOrCastCurrentCount(uint32 quest_id, int32 entry) const if (!qInfo) return 0; + uint16 slot = FindQuestSlot(quest_id); + if (slot >= MAX_QUEST_LOG_SIZE) + return 0; + for (QuestObjective const& obj : qInfo->GetObjectives()) if (obj.ObjectID == entry) - return GetQuestObjectiveData(qInfo, obj.StorageIndex); + return GetQuestSlotObjectiveData(slot, obj); return 0; } -void Player::AdjustQuestReqItemCount(Quest const* quest) +void Player::AdjustQuestObjectiveProgress(Quest const* quest) { // adjust progress of quest objectives that rely on external counters, like items if (quest->HasQuestObjectiveType(QUEST_OBJECTIVE_ITEM)) @@ -16559,6 +16544,12 @@ void Player::AdjustQuestReqItemCount(Quest const* quest) uint32 curItemCount = GetItemCount(obj.ObjectID, true); SetQuestObjectiveData(obj, std::min(curItemCount, reqItemCount)); } + else if (obj.Type == QUEST_OBJECTIVE_HAVE_CURRENCY) + { + uint32 reqCurrencyCount = obj.Amount; + uint32 curCurrencyCount = GetCurrency(obj.ObjectID); + SetQuestObjectiveData(obj, std::min(reqCurrencyCount, curCurrencyCount)); + } } } } @@ -16594,6 +16585,35 @@ uint32 Player::GetQuestSlotTime(uint16 slot) const return m_playerData->QuestLog[slot].EndTime; } +bool Player::GetQuestSlotObjectiveFlag(uint16 slot, int8 objectiveIndex) const +{ + if (objectiveIndex < MAX_QUEST_COUNTS) + return (*m_playerData->QuestLog[slot].ObjectiveFlags) & (1 << objectiveIndex); + return false; +} + +int32 Player::GetQuestSlotObjectiveData(uint16 slot, QuestObjective const& objective) const +{ + if (objective.StorageIndex < 0) + { + TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Called for quest %u with invalid StorageIndex %d (objective data is not tracked)", + objective.QuestID, int32(objective.StorageIndex)); + return 0; + } + + if (objective.StorageIndex >= MAX_QUEST_COUNTS) + { + TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Player '%s' (%s) quest %u out of range StorageIndex %u", + GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID, uint32(objective.StorageIndex)); + return 0; + } + + if (!objective.IsStoringFlag()) + return GetQuestSlotCounter(slot, objective.StorageIndex); + + return GetQuestSlotObjectiveFlag(slot, objective.StorageIndex) ? 1 : 0; +} + void Player::SetQuestSlot(uint16 slot, uint32 quest_id, uint32 timer /*= 0*/) { auto questLogField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::QuestLog, slot); @@ -16729,30 +16749,27 @@ void Player::ItemAddedQuestCheck(uint32 entry, uint32 count) for (QuestObjective const& obj : qInfo->GetObjectives()) { - if (obj.Type != QUEST_OBJECTIVE_ITEM) + if (obj.Type != QUEST_OBJECTIVE_ITEM || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - uint32 reqItem = obj.ObjectID; - if (reqItem == entry) - { - uint32 reqItemCount = obj.Amount; - uint32 curItemCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curItemCount < reqItemCount) - { - uint32 newItemCount = std::min<uint32>(curItemCount + count, reqItemCount); - SetQuestObjectiveData(obj, newItemCount); + if (uint32(obj.ObjectID) != entry) + continue; - //SendQuestUpdateAddItem(qInfo, j, additemcount); - // FIXME: verify if there's any packet sent updating item - } + 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); + if (CanCompleteQuest(questid)) + CompleteQuest(questid); - return; - } + break; } } + UpdateForQuestWorldObjects(); } @@ -16770,29 +16787,30 @@ void Player::ItemRemovedQuestCheck(uint32 entry, uint32 count) for (QuestObjective const& obj : qInfo->GetObjectives()) { - if (obj.Type != QUEST_OBJECTIVE_ITEM) + if (obj.Type != QUEST_OBJECTIVE_ITEM || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - uint32 reqItem = obj.ObjectID; - if (reqItem == entry) - { - uint32 reqItemCount = obj.Amount; - uint16 curItemCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (uint32(obj.ObjectID) != entry) + continue; - if (curItemCount >= reqItemCount) // we may have more than what the status shows - curItemCount = GetItemCount(entry, false); + uint32 reqItemCount = obj.Amount; + uint16 curItemCount = GetQuestSlotObjectiveData(i, obj); - uint16 newItemCount = (count > curItemCount) ? 0 : curItemCount - count; + if (curItemCount >= reqItemCount) // we may have more than what the status shows + curItemCount = GetItemCount(entry, false); - if (newItemCount < reqItemCount) - { - SetQuestObjectiveData(obj, newItemCount); - IncompleteQuest(questid); - } - return; + uint16 newItemCount = (count > curItemCount) ? 0 : curItemCount - count; + + if (newItemCount < reqItemCount) + { + SetQuestObjectiveData(obj, newItemCount); + IncompleteQuest(questid); } + + break; } } + UpdateForQuestWorldObjects(); } @@ -16823,6 +16841,7 @@ 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); @@ -16833,42 +16852,47 @@ void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid /*= ObjectGuid::E if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_MONSTER)) 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_MONSTER) - continue; - uint32 reqkill = obj.ObjectID; + if (m_QuestStatus[questid].Status != QUEST_STATUS_INCOMPLETE) + continue; - if (reqkill == real_entry) - { - uint32 reqKillCount = obj.Amount; - uint16 curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curKillCount < reqKillCount) - { - SetQuestObjectiveData(obj, curKillCount + addKillCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, curKillCount + addKillCount); - } + if (GetGroup() && GetGroup()->isRaidGroup() && qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) + continue; - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + for (QuestObjective const& obj : qInfo->GetObjectives()) + { + if (obj.Type != QUEST_OBJECTIVE_MONSTER || !IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; - } + 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(); } void Player::KilledPlayerCredit() { uint16 addKillCount = 1; - + bool completedObjectiveInSequencedQuest = false; for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) { uint32 questid = GetQuestSlotQuestId(i); @@ -16885,14 +16909,16 @@ void Player::KilledPlayerCredit() { for (QuestObjective const& obj : qInfo->GetObjectives()) { - if (obj.Type != QUEST_OBJECTIVE_PLAYERKILLS) + if (obj.Type != QUEST_OBJECTIVE_PLAYERKILLS || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - uint32 curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + 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)) @@ -16903,11 +16929,15 @@ void Player::KilledPlayerCredit() } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } 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); @@ -16915,45 +16945,50 @@ void Player::KillCreditGO(uint32 entry, ObjectGuid guid) continue; Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo) + if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_GAMEOBJECT)) continue; - QuestStatusData& q_status = m_QuestStatus[questid]; + if (m_QuestStatus[questid].Status != QUEST_STATUS_INCOMPLETE) + continue; - if (q_status.Status == QUEST_STATUS_INCOMPLETE) - { - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_GAMEOBJECT) - continue; + if (GetGroup() && GetGroup()->isRaidGroup() && qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) + continue; - uint32 reqTarget = obj.ObjectID; + 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 (reqTarget != entry) - continue; + // other not this creature/GO related objectives + if (uint32(obj.ObjectID) != entry) + continue; - uint32 reqCastCount = obj.Amount; - uint32 curCastCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curCastCount < reqCastCount) - { - SetQuestObjectiveData(obj, curCastCount + addCastCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, curCastCount + addCastCount); - } + 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); + 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; - } + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } 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); @@ -16961,39 +16996,43 @@ void Player::TalkedToCreature(uint32 entry, ObjectGuid guid) continue; Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid); - if (!qInfo) + if (!qInfo || !qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_TALKTO)) continue; - QuestStatusData& q_status = m_QuestStatus[questid]; + if (m_QuestStatus[questid].Status != QUEST_STATUS_INCOMPLETE) + continue; - if (q_status.Status == QUEST_STATUS_INCOMPLETE) + if (GetGroup() && GetGroup()->isRaidGroup() && qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID())) + continue; + + for (QuestObjective const& obj : qInfo->GetObjectives()) { - for (QuestObjective const& obj : qInfo->GetObjectives()) - { - if (obj.Type != QUEST_OBJECTIVE_TALKTO) - continue; + if (obj.Type != QUEST_OBJECTIVE_TALKTO || !IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; - uint32 reqTarget = obj.ObjectID; + if (uint32(obj.ObjectID) != entry) + continue; - if (reqTarget == entry) - { - uint32 reqTalkCount = obj.Amount; - uint32 curTalkCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curTalkCount < reqTalkCount) - { - SetQuestObjectiveData(obj, curTalkCount + addTalkCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, curTalkCount + addTalkCount); - } + 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); + if (CanCompleteQuest(questid)) + CompleteQuest(questid); - // Quest can't have more than one objective for the same creature (code optimisation) - break; - } - } + // Quest can't have more than one objective for the same creature (code optimisation) + break; } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } void Player::KillCreditCriteriaTreeObjective(QuestObjective const& questObjective) @@ -17001,18 +17040,23 @@ void Player::KillCreditCriteriaTreeObjective(QuestObjective const& questObjectiv if (questObjective.Type != QUEST_OBJECTIVE_CRITERIA_TREE) return; - if (GetQuestStatus(questObjective.QuestID) == QUEST_STATUS_INCOMPLETE) + Quest const* quest = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(questObjective.QuestID)); + if (!IsQuestObjectiveComplete(FindQuestSlot(questObjective.QuestID), quest, questObjective)) { SetQuestObjectiveData(questObjective, 1); SendQuestUpdateAddCreditSimple(questObjective); if (CanCompleteQuest(questObjective.QuestID)) CompleteQuest(questObjective.QuestID); + + if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) + UpdateForQuestWorldObjects(); } } void Player::MoneyChanged(uint64 value) { + bool completedObjectiveInSequencedQuest = false; for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) { uint32 questid = GetQuestSlotQuestId(i); @@ -17023,19 +17067,25 @@ void Player::MoneyChanged(uint64 value) if (!qInfo) continue; + if (!qInfo->HasQuestObjectiveType(QUEST_OBJECTIVE_MONEY)) + continue; + + QuestStatusData& q_status = m_QuestStatus[questid]; + for (QuestObjective const& obj : qInfo->GetObjectives()) { - if (obj.Type != QUEST_OBJECTIVE_MONEY) + if (obj.Type != QUEST_OBJECTIVE_MONEY || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - QuestStatusData& q_status = m_QuestStatus[questid]; - - if (q_status.Status == QUEST_STATUS_INCOMPLETE) + if (!IsQuestObjectiveComplete(i, qInfo, obj)) { - if (int64(value) >= obj.Amount) + if (int64(value) >= int64(obj.Amount)) { if (CanCompleteQuest(questid)) CompleteQuest(questid); + + if (!completedObjectiveInSequencedQuest && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES)) + completedObjectiveInSequencedQuest = true; } } else if (q_status.Status == QUEST_STATUS_COMPLETE) @@ -17045,10 +17095,14 @@ void Player::MoneyChanged(uint64 value) } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } -void Player::ReputationChanged(FactionEntry const* factionEntry) +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)) @@ -17057,47 +17111,85 @@ void Player::ReputationChanged(FactionEntry const* factionEntry) { 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()) { - if (uint32(obj.ObjectID) != factionEntry->ID) + if (uint32(obj.ObjectID) != factionEntry->ID || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; - if (obj.Type == QUEST_OBJECTIVE_MIN_REPUTATION) + switch (obj.Type) { - if (q_status.Status == QUEST_STATUS_INCOMPLETE) - { - if (GetReputationMgr().GetReputation(factionEntry) >= obj.Amount) - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - } - else if (q_status.Status == QUEST_STATUS_COMPLETE) - { - if (GetReputationMgr().GetReputation(factionEntry) < obj.Amount) - IncompleteQuest(questid); - } - } - else if (obj.Type == QUEST_OBJECTIVE_MAX_REPUTATION) - { - if (q_status.Status == QUEST_STATUS_INCOMPLETE) + 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: { - if (GetReputationMgr().GetReputation(factionEntry) <= obj.Amount) + 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; } - else if (q_status.Status == QUEST_STATUS_COMPLETE) - { - if (GetReputationMgr().GetReputation(factionEntry) > obj.Amount) - IncompleteQuest(questid); - } + default: + 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); @@ -17108,41 +17200,87 @@ void Player::CurrencyChanged(uint32 currencyId, int32 change) 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) + if (uint32(obj.ObjectID) != currencyId || !IsQuestObjectiveCompletable(i, qInfo, obj)) continue; QuestStatusData& q_status = m_QuestStatus[questid]; - if (obj.Type == QUEST_OBJECTIVE_CURRENCY || obj.Type == QUEST_OBJECTIVE_HAVE_CURRENCY) + switch (obj.Type) { - int64 value = GetCurrency(currencyId); - if (obj.Type == QUEST_OBJECTIVE_HAVE_CURRENCY) - SetQuestObjectiveData(obj, int32(std::min<int64>(value, obj.Amount))); + case QUEST_OBJECTIVE_CURRENCY: + { + if (!IsQuestObjectiveComplete(i, qInfo, obj)) + { + if (int64(GetCurrency(currencyId)) + change >= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); - if (q_status.Status == QUEST_STATUS_INCOMPLETE) + 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: { - if (value >= obj.Amount) + int32 newProgress = int32(std::min<int64>(int64(GetCurrency(currencyId)) + change, obj.Amount)); + SetQuestObjectiveData(obj, newProgress); + if (!IsQuestObjectiveComplete(i, qInfo, obj)) { - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + 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; } - else if (q_status.Status == QUEST_STATUS_COMPLETE) + case QUEST_OBJECTIVE_OBTAIN_CURRENCY: { - if (value < obj.Amount) - IncompleteQuest(questid); + 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; } - } - else if (obj.Type == QUEST_OBJECTIVE_OBTAIN_CURRENCY && change > 0) // currency losses are not accounted for in this objective type - { - int64 currentProgress = GetQuestObjectiveData(qInfo, obj.StorageIndex); - SetQuestObjectiveData(obj, int32(std::max(std::min<int64>(currentProgress + change, obj.Amount), SI64LIT(0)))); - if (CanCompleteQuest(questid)) - CompleteQuest(questid); + default: + break; } } } + + if (completedObjectiveInSequencedQuest) + UpdateForQuestWorldObjects(); } bool Player::HasQuestForItem(uint32 itemid) const @@ -17174,7 +17312,7 @@ bool Player::HasQuestForItem(uint32 itemid) const // This part for ReqItem drop for (QuestObjective const& obj : qInfo->GetObjectives()) { - if (obj.Type == QUEST_OBJECTIVE_ITEM && itemid == uint32(obj.ObjectID) && GetQuestObjectiveData(qInfo, obj.StorageIndex) < obj.Amount) + 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 @@ -17203,44 +17341,136 @@ bool Player::HasQuestForItem(uint32 itemid) const return false; } -int32 Player::GetQuestObjectiveData(Quest const* quest, int8 storageIndex) const +int32 Player::GetQuestObjectiveData(QuestObjective const& objective) const { - if (storageIndex < 0) - TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Called for quest %u with invalid StorageIndex %d (objective data is not tracked)", - quest->GetQuestId(), storageIndex); + uint16 slot = FindQuestSlot(objective.QuestID); + if (slot >= MAX_QUEST_LOG_SIZE) + return 0; + + return GetQuestSlotObjectiveData(slot, objective); +} + +void Player::SetQuestObjectiveData(QuestObjective const& objective, int32 data) +{ + if (objective.StorageIndex < 0) + { + TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: called for quest %u with invalid StorageIndex %d (objective data is not tracked)", + objective.QuestID, objective.StorageIndex); + return; + } - auto itr = m_QuestStatus.find(quest->GetQuestId()); + auto itr = m_QuestStatus.find(objective.QuestID); if (itr == m_QuestStatus.end()) - return 0; + { + TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '%s' (%s) doesn't have quest status data (QuestID: %u)", + GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID); + return; + } - QuestStatusData const& status = itr->second; + QuestStatusData& status = itr->second; - if (uint8(storageIndex) >= status.ObjectiveData.size()) + if (uint8(objective.StorageIndex) >= MAX_QUEST_COUNTS) { - TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Player '%s' (%s) quest %u out of range StorageIndex %u", - GetName().c_str(), GetGUID().ToString().c_str(), quest->GetQuestId(), storageIndex); - return 0; + TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '%s' (%s) quest %u out of range StorageIndex %u", + GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID, objective.StorageIndex); + return; } - return status.ObjectiveData[storageIndex]; + if (status.Slot >= MAX_QUEST_LOG_SIZE) + return; + + // No change + int32 oldData = GetQuestSlotObjectiveData(status.Slot, objective); + if (oldData == data) + return; + + if (Quest const* quest = sObjectMgr->GetQuestTemplate(objective.QuestID)) + sScriptMgr->OnQuestObjectiveChange(this, quest, objective, oldData, data); + + // Add to save + m_QuestStatusSave[objective.QuestID] = QUEST_DEFAULT_SAVE_TYPE; + + // Update quest fields + if (!objective.IsStoringFlag()) + SetQuestSlotCounter(status.Slot, objective.StorageIndex, data); + else if (data) + SetQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex); + else + RemoveQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex); } -bool Player::IsQuestObjectiveComplete(QuestObjective const& objective) const +bool Player::IsQuestObjectiveCompletable(uint16 slot, Quest const* quest, QuestObjective const& objective) const { - Quest const* quest = sObjectMgr->GetQuestTemplate(objective.QuestID); - ASSERT(quest); + ASSERT(objective.QuestID == quest->GetQuestId()); + if (objective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR) + { + // delegate check to actual progress bar objective + auto progressBarObjectiveItr = std::find_if(quest->GetObjectives().begin(), quest->GetObjectives().end(), [](QuestObjective const& otherObjective) + { + return otherObjective.Type == QUEST_OBJECTIVE_PROGRESS_BAR && !(otherObjective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR); + }); + if (progressBarObjectiveItr == quest->GetObjectives().end()) + return false; + + return IsQuestObjectiveCompletable(slot, quest, *progressBarObjectiveItr) && !IsQuestObjectiveComplete(slot, quest, *progressBarObjectiveItr); + } + + int32 objectiveIndex = int32(std::distance(&quest->GetObjectives()[0], &objective)); + if (!objectiveIndex) + return true; + + // check sequenced objectives + int32 previousIndex = objectiveIndex - 1; + bool objectiveSequenceSatisfied = true; + bool previousSequencedObjectiveComplete = false; + int32 previousSequencedObjectiveIndex = -1; + do + { + QuestObjective const& previousObjective = quest->GetObjectives()[previousIndex]; + if (previousObjective.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED) + { + previousSequencedObjectiveIndex = previousIndex; + previousSequencedObjectiveComplete = IsQuestObjectiveComplete(slot, quest, previousObjective); + break; + } + + if (objectiveSequenceSatisfied) + objectiveSequenceSatisfied = IsQuestObjectiveComplete(slot, quest, previousObjective) || (previousObjective.Flags & (QUEST_OBJECTIVE_FLAG_OPTIONAL | QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR)); + + --previousIndex; + } while (previousIndex >= 0); + + if (objective.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED) + { + if (previousSequencedObjectiveIndex == -1) + return objectiveSequenceSatisfied; + if (!previousSequencedObjectiveComplete || !objectiveSequenceSatisfied) + return false; + } + else if (!previousSequencedObjectiveComplete && previousSequencedObjectiveIndex != -1) + { + if (!IsQuestObjectiveCompletable(slot, quest, quest->GetObjectives()[previousSequencedObjectiveIndex])) + return false; + } + + return true; +} + +bool Player::IsQuestObjectiveComplete(uint16 slot, Quest const* quest, QuestObjective const& objective) const +{ switch (objective.Type) { case QUEST_OBJECTIVE_MONSTER: case QUEST_OBJECTIVE_ITEM: case QUEST_OBJECTIVE_GAMEOBJECT: - case QUEST_OBJECTIVE_PLAYERKILLS: case QUEST_OBJECTIVE_TALKTO: + case QUEST_OBJECTIVE_PLAYERKILLS: case QUEST_OBJECTIVE_WINPVPPETBATTLES: case QUEST_OBJECTIVE_HAVE_CURRENCY: case QUEST_OBJECTIVE_OBTAIN_CURRENCY: - if (GetQuestObjectiveData(quest, objective.StorageIndex) < objective.Amount) + case QUEST_OBJECTIVE_INCREASE_REPUTATION: + if (GetQuestSlotObjectiveData(slot, objective) < objective.Amount) return false; break; case QUEST_OBJECTIVE_MIN_REPUTATION: @@ -17256,8 +17486,12 @@ bool Player::IsQuestObjectiveComplete(QuestObjective const& objective) const return false; break; case QUEST_OBJECTIVE_AREATRIGGER: + case QUEST_OBJECTIVE_WINPETBATTLEAGAINSTNPC: + case QUEST_OBJECTIVE_DEFEATBATTLEPET: case QUEST_OBJECTIVE_CRITERIA_TREE: - if (!GetQuestObjectiveData(quest, objective.StorageIndex)) + case QUEST_OBJECTIVE_AREA_TRIGGER_ENTER: + case QUEST_OBJECTIVE_AREA_TRIGGER_EXIT: + if (!GetQuestSlotObjectiveData(slot, objective)) return false; break; case QUEST_OBJECTIVE_LEARNSPELL: @@ -17269,7 +17503,7 @@ bool Player::IsQuestObjectiveComplete(QuestObjective const& objective) const return false; break; case QUEST_OBJECTIVE_PROGRESS_BAR: - if (!IsQuestObjectiveProgressComplete(quest)) + if (!IsQuestObjectiveProgressBarComplete(slot, quest)) return false; break; default: @@ -17281,58 +17515,19 @@ bool Player::IsQuestObjectiveComplete(QuestObjective const& objective) const return true; } -void Player::SetQuestObjectiveData(QuestObjective const& objective, int32 data) +bool Player::IsQuestObjectiveProgressBarComplete(uint16 slot, Quest const* quest) const { - if (objective.StorageIndex < 0) - { - TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: called for quest %u with invalid StorageIndex %d (objective data is not tracked)", - objective.QuestID, objective.StorageIndex); - return; - } - - auto itr = m_QuestStatus.find(objective.QuestID); - - if (itr == m_QuestStatus.end()) - { - TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '%s' (%s) doesn't have quest status data (QuestID: %u)", - GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID); - return; - } - - QuestStatusData& status = itr->second; - - if (uint8(objective.StorageIndex) >= status.ObjectiveData.size()) - { - TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '%s' (%s) quest %u out of range StorageIndex %u", - GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID, objective.StorageIndex); - return; - } - - // No change - int32 oldData = status.ObjectiveData[objective.StorageIndex]; - if (oldData == data) - return; - - if (Quest const* quest = sObjectMgr->GetQuestTemplate(objective.QuestID)) - sScriptMgr->OnQuestObjectiveChange(this, quest, objective, oldData, data); - - // Set data - status.ObjectiveData[objective.StorageIndex] = data; - - // Add to save - m_QuestStatusSave[objective.QuestID] = QUEST_DEFAULT_SAVE_TYPE; - - // Update quest fields - uint16 log_slot = FindQuestSlot(objective.QuestID); - if (log_slot < MAX_QUEST_LOG_SIZE) + float progress = 0.0f; + for (QuestObjective const& obj : quest->GetObjectives()) { - if (!objective.IsStoringFlag()) - SetQuestSlotCounter(log_slot, objective.StorageIndex, status.ObjectiveData[objective.StorageIndex]); - else if (data) - SetQuestSlotObjectiveFlag(log_slot, objective.StorageIndex); - else - RemoveQuestSlotObjectiveFlag(log_slot, objective.StorageIndex); + if (obj.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR) + { + progress += GetQuestSlotObjectiveData(slot, obj) * obj.ProgressBarWeight; + if (progress >= 100.0f) + return true; + } } + return false; } void Player::SendQuestComplete(Quest const* quest) const @@ -19487,6 +19682,7 @@ void Player::_LoadQuestStatus(PreparedQueryResult result) // add to quest log if (slot < MAX_QUEST_LOG_SIZE && questStatusData.Status != QUEST_STATUS_NONE) { + questStatusData.Slot = slot; SetQuestSlot(slot, quest_id, uint32(quest_time)); // cast can't be helped if (questStatusData.Status == QUEST_STATUS_COMPLETE) @@ -19497,14 +19693,6 @@ void Player::_LoadQuestStatus(PreparedQueryResult result) ++slot; } - // Resize quest objective data to proper size - int32 maxStorageIndex = 0; - for (QuestObjective const& obj : quest->GetObjectives()) - if (obj.StorageIndex > maxStorageIndex) - maxStorageIndex = obj.StorageIndex; - - questStatusData.ObjectiveData.resize(maxStorageIndex+1); - TC_LOG_DEBUG("entities.player.loading", "Player::_LoadQuestStatus: Quest status is {%u} for quest {%u} for player (%s)", questStatusData.Status, quest_id, GetGUID().ToString().c_str()); } } @@ -19530,24 +19718,22 @@ void Player::_LoadQuestStatusObjectives(PreparedQueryResult result) uint32 questID = fields[0].GetUInt32(); Quest const* quest = sObjectMgr->GetQuestTemplate(questID); - uint16 slot = FindQuestSlot(questID); auto itr = m_QuestStatus.find(questID); - if (itr != m_QuestStatus.end() && slot < MAX_QUEST_LOG_SIZE && quest) + if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE && quest) { QuestStatusData& questStatusData = itr->second; - uint8 objectiveIndex = fields[1].GetUInt8(); - auto objectiveItr = std::find_if(quest->Objectives.begin(), quest->Objectives.end(), [=](QuestObjective const& objective) { return uint8(objective.StorageIndex) == objectiveIndex; }); - if (objectiveIndex < questStatusData.ObjectiveData.size() && objectiveItr != quest->Objectives.end()) + uint8 storageIndex = fields[1].GetUInt8(); + auto objectiveItr = std::find_if(quest->Objectives.begin(), quest->Objectives.end(), [=](QuestObjective const& objective) { return uint8(objective.StorageIndex) == storageIndex; }); + if (objectiveItr != quest->Objectives.end()) { int32 data = fields[2].GetInt32(); - questStatusData.ObjectiveData[objectiveIndex] = data; if (!objectiveItr->IsStoringFlag()) - SetQuestSlotCounter(slot, objectiveIndex, data); + SetQuestSlotCounter(questStatusData.Slot, storageIndex, data); else if (data) - SetQuestSlotObjectiveFlag(slot, objectiveIndex); + SetQuestSlotObjectiveFlag(questStatusData.Slot, storageIndex); } else - TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player '%s' (%s) has quest %d out of range objective index %u.", GetName().c_str(), GetGUID().ToString().c_str(), questID, objectiveIndex); + TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player '%s' (%s) has quest %d out of range objective index %u.", GetName().c_str(), GetGUID().ToString().c_str(), questID, storageIndex); } else TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player %s (%s) does not have quest %d but has objective data for it.", GetName().c_str(), GetGUID().ToString().c_str(), questID); @@ -21184,13 +21370,24 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction& trans) trans->Append(stmt); // Save objectives - for (uint32 i = 0; i < qData.ObjectiveData.size(); ++i) + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setUInt32(1, saveItr->first); + trans->Append(stmt); + + Quest const* quest = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(saveItr->first)); + + for (QuestObjective const& obj : quest->GetObjectives()) { + int32 count = GetQuestSlotObjectiveData(qData.Slot, obj); + if (!count) + continue; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS_OBJECTIVES); stmt->setUInt64(0, GetGUID().GetCounter()); stmt->setUInt32(1, statusItr->first); - stmt->setUInt8(2, i); - stmt->setInt32(3, qData.ObjectiveData[i]); + stmt->setUInt8(2, obj.StorageIndex); + stmt->setInt32(3, count); trans->Append(stmt); } } @@ -24107,8 +24304,8 @@ bool Player::HasEnoughMoney(int64 amount) const void Player::SetMoney(uint64 value) { - SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Coinage), value); MoneyChanged(value); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Coinage), value); UpdateCriteria(CRITERIA_TYPE_HIGHEST_GOLD_VALUE_OWNED); } @@ -25114,7 +25311,10 @@ bool Player::HasQuestForGO(int32 GOId) const if (obj.Type != QUEST_OBJECTIVE_GAMEOBJECT) //skip non GO case continue; - if (GOId == obj.ObjectID && GetQuestObjectiveData(qInfo, obj.StorageIndex) < obj.Amount) + if (!IsQuestObjectiveCompletable(i, qInfo, obj)) + continue; + + if (GOId == obj.ObjectID && GetQuestSlotObjectiveData(i, obj) < obj.Amount) return true; } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 335c60074a6..e74a37c7bf9 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1504,6 +1504,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> uint32 GetQuestSlotState(uint16 slot) const; uint16 GetQuestSlotCounter(uint16 slot, uint8 counter) const; uint32 GetQuestSlotTime(uint16 slot) const; + bool GetQuestSlotObjectiveFlag(uint16 slot, int8 objectiveIndex) const; + int32 GetQuestSlotObjectiveData(uint16 slot, QuestObjective const& objective) const; void SetQuestSlot(uint16 slot, uint32 quest_id, uint32 timer = 0); void SetQuestSlotCounter(uint16 slot, uint8 counter, uint16 count); void SetQuestSlotState(uint16 slot, uint32 state); @@ -1525,17 +1527,18 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void TalkedToCreature(uint32 entry, ObjectGuid guid); void KillCreditCriteriaTreeObjective(QuestObjective const& questObjective); void MoneyChanged(uint64 value); - void ReputationChanged(FactionEntry const* factionEntry); + void ReputationChanged(FactionEntry const* factionEntry, int32 change); void CurrencyChanged(uint32 currencyId, int32 change); bool HasQuestForItem(uint32 itemId) const; bool HasQuestForGO(int32 goId) const; void UpdateForQuestWorldObjects(); bool CanShareQuest(uint32 questId) const; - int32 GetQuestObjectiveData(Quest const* quest, int8 storageIndex) const; - bool IsQuestObjectiveComplete(QuestObjective const& objective) const; + int32 GetQuestObjectiveData(QuestObjective const& objective) const; void SetQuestObjectiveData(QuestObjective const& objective, int32 data); - bool IsQuestObjectiveProgressComplete(Quest const* quest) const; + bool IsQuestObjectiveCompletable(uint16 slot, Quest const* quest, QuestObjective const& objective) const; + bool IsQuestObjectiveComplete(uint16 slot, Quest const* quest, QuestObjective const& objective) const; + bool IsQuestObjectiveProgressBarComplete(uint16 slot, Quest const* quest) const; void SendQuestComplete(Quest const* quest) const; void SendQuestReward(Quest const* quest, Creature const* questGiver, uint32 xp, bool hideChatMessage) const; void SendQuestFailed(uint32 questID, InventoryResult reason = EQUIP_ERR_OK) const; @@ -2944,7 +2947,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void RefundItem(Item* item); void SendItemRefundResult(Item* item, ItemExtendedCostEntry const* iece, uint8 error) const; - void AdjustQuestReqItemCount(Quest const* quest); + void AdjustQuestObjectiveProgress(Quest const* quest); bool IsCanDelayTeleport() const { return m_bCanDelayTeleport; } void SetCanDelayTeleport(bool setting) { m_bCanDelayTeleport = setting; } diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 241ffcc3535..ad23bd14251 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -17,6 +17,8 @@ #include "ObjectMgr.h" #include "ArenaTeamMgr.h" +#include "AreaTriggerDataStore.h" +#include "AreaTriggerTemplate.h" #include "AzeriteEmpoweredItem.h" #include "AzeriteItem.h" #include "Chat.h" @@ -4577,9 +4579,13 @@ void ObjectMgr::LoadQuests() qinfo->GetQuestId(), obj.ID, uint32(obj.ObjectID)); break; case QUEST_OBJECTIVE_TALKTO: + if (!sObjectMgr->GetCreatureTemplate(obj.ObjectID)) + TC_LOG_ERROR("sql.sql", "Quest %u objective %u has non existing creature entry %u, quest can't be done.", + qinfo->GetQuestId(), obj.ID, uint32(obj.ObjectID)); break; case QUEST_OBJECTIVE_MIN_REPUTATION: case QUEST_OBJECTIVE_MAX_REPUTATION: + case QUEST_OBJECTIVE_INCREASE_REPUTATION: if (!sFactionStore.LookupEntry(obj.ObjectID)) TC_LOG_ERROR("sql.sql", "Quest %u objective %u has non existing faction id %d", qinfo->GetQuestId(), obj.ID, obj.ObjectID); break; @@ -4614,6 +4620,11 @@ void ObjectMgr::LoadQuests() break; case QUEST_OBJECTIVE_AREATRIGGER: if (!sAreaTriggerStore.LookupEntry(uint32(obj.ObjectID)) && obj.ObjectID != -1) + TC_LOG_ERROR("sql.sql", "Quest %u objective %u has non existing AreaTrigger.db2 id %d", qinfo->GetQuestId(), obj.ID, obj.ObjectID); + break; + case QUEST_OBJECTIVE_AREA_TRIGGER_ENTER: + case QUEST_OBJECTIVE_AREA_TRIGGER_EXIT: + if (!sAreaTriggerDataStore->GetAreaTriggerTemplate({ uint32(obj.ObjectID), false }) && !sAreaTriggerDataStore->GetAreaTriggerTemplate({ uint32(obj.ObjectID), true })) TC_LOG_ERROR("sql.sql", "Quest %u objective %u has non existing areatrigger id %d", qinfo->GetQuestId(), obj.ID, obj.ObjectID); break; case QUEST_OBJECTIVE_MONEY: @@ -4623,6 +4634,9 @@ void ObjectMgr::LoadQuests() TC_LOG_ERROR("sql.sql", "Quest %u objective %u has unhandled type %u", qinfo->GetQuestId(), obj.ID, obj.Type); break; } + + if (obj.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED) + qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES); } for (uint8 j = 0; j < QUEST_ITEM_DROP_COUNT; ++j) diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index 0c72ec7aad4..2ffacd75ff9 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -491,17 +491,21 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge { if (std::unordered_set<uint32> const* quests = sObjectMgr->GetQuestsForAreaTrigger(packet.AreaTriggerID)) { + bool completedObjectiveInSequencedQuest = false; for (uint32 questId : *quests) { Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); - if (qInfo && player->GetQuestStatus(questId) == QUEST_STATUS_INCOMPLETE) + uint16 slot = player->FindQuestSlot(questId); + if (qInfo && slot < MAX_QUEST_LOG_SIZE && player->GetQuestStatus(questId) == QUEST_STATUS_INCOMPLETE) { for (QuestObjective const& obj : qInfo->Objectives) { - if (obj.Type == QUEST_OBJECTIVE_AREATRIGGER && !player->IsQuestObjectiveComplete(obj)) + 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; } } @@ -510,6 +514,9 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPackets::AreaTrigger::AreaTrigge player->CompleteQuest(questId); } } + + if (completedObjectiveInSequencedQuest) + player->UpdateForQuestWorldObjects(); } } diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index c497f332b05..bb2ab57fcc3 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -253,6 +253,7 @@ enum QuestSpecialFlags QUEST_SPECIAL_FLAGS_DB_ALLOWED = QUEST_SPECIAL_FLAGS_REPEATABLE | QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT | QUEST_SPECIAL_FLAGS_AUTO_ACCEPT | QUEST_SPECIAL_FLAGS_DF_QUEST | QUEST_SPECIAL_FLAGS_MONTHLY, + QUEST_SPECIAL_FLAGS_SEQUENCED_OBJECTIVES = 0x020, // Internal flag computed only }; enum class QuestTagType @@ -670,9 +671,9 @@ class TC_GAME_API Quest struct QuestStatusData { + uint16 Slot = MAX_QUEST_LOG_SIZE; QuestStatus Status = QUEST_STATUS_NONE; uint32 Timer = 0; - std::vector<int32> ObjectiveData; }; #endif diff --git a/src/server/game/Quests/QuestObjectiveCriteriaMgr.cpp b/src/server/game/Quests/QuestObjectiveCriteriaMgr.cpp index 07b1e1f582a..d9072f58659 100644 --- a/src/server/game/Quests/QuestObjectiveCriteriaMgr.cpp +++ b/src/server/game/Quests/QuestObjectiveCriteriaMgr.cpp @@ -296,6 +296,14 @@ bool QuestObjectiveCriteriaMgr::CanUpdateCriteriaTree(Criteria const* criteria, return false; } + uint16 slot = _owner->FindQuestSlot(objective->QuestID); + if (slot >= MAX_QUEST_LOG_SIZE || !_owner->IsQuestObjectiveCompletable(slot, quest, *objective)) + { + TC_LOG_TRACE("criteria.quest", "QuestObjectiveCriteriaMgr::CanUpdateCriteriaTree: (Id: %u Type %s Quest Objective %u) Objective not completable", + criteria->ID, CriteriaMgr::GetCriteriaTypeString(criteria->Entry->Type), objective->ID); + return false; + } + return CriteriaHandler::CanUpdateCriteriaTree(criteria, tree, referencePlayer); } diff --git a/src/server/game/Reputation/ReputationMgr.cpp b/src/server/game/Reputation/ReputationMgr.cpp index ba7870c1bc0..ebad1e1b632 100644 --- a/src/server/game/Reputation/ReputationMgr.cpp +++ b/src/server/game/Reputation/ReputationMgr.cpp @@ -361,7 +361,11 @@ bool ReputationMgr::SetOneFactionReputation(FactionEntry const* factionEntry, in ReputationRank old_rank = ReputationToRank(itr->second.Standing + BaseRep); ReputationRank new_rank = ReputationToRank(standing); - itr->second.Standing = standing - BaseRep; + int32 newStanding = standing - BaseRep; + + _player->ReputationChanged(factionEntry, newStanding - itr->second.Standing); + + itr->second.Standing = newStanding; itr->second.needSend = true; itr->second.needSave = true; @@ -375,7 +379,6 @@ bool ReputationMgr::SetOneFactionReputation(FactionEntry const* factionEntry, in UpdateRankCounters(old_rank, new_rank); - _player->ReputationChanged(factionEntry); _player->UpdateCriteria(CRITERIA_TYPE_KNOWN_FACTIONS, factionEntry->ID); _player->UpdateCriteria(CRITERIA_TYPE_GAIN_REPUTATION, factionEntry->ID); _player->UpdateCriteria(CRITERIA_TYPE_GAIN_EXALTED_REPUTATION, factionEntry->ID); diff --git a/src/server/scripts/Northrend/zone_sholazar_basin.cpp b/src/server/scripts/Northrend/zone_sholazar_basin.cpp index 1e8a13dbc33..f83c55b201c 100644 --- a/src/server/scripts/Northrend/zone_sholazar_basin.cpp +++ b/src/server/scripts/Northrend/zone_sholazar_basin.cpp @@ -342,7 +342,7 @@ public: if (uint32(quest->Objectives[i].ObjectID) != me->GetEntry()) continue; - if (player->GetQuestObjectiveData(quest, i) != 0) + if (player->GetQuestObjectiveData(quest->Objectives[i]) != 0) continue; player->KilledMonsterCredit(me->GetEntry()); |