diff options
author | xinef1 <w.szyszko2@gmail.com> | 2017-04-24 05:46:06 +0200 |
---|---|---|
committer | funjoker <funjoker109@gmail.com> | 2020-04-28 12:56:03 +0200 |
commit | 16cf95654f35f6bb563e82608476d8f53837bd06 (patch) | |
tree | 549b44c3ebed03b2d2b5ad5d002ef6f9bd4af9a6 | |
parent | 9ef39fa5d2f76459e3e529a0877655b0f69b98d6 (diff) |
Various quest system fixes (seasonal quests, timed quests and more) (#18940)
- Unify quest status checking function, use dedicated function instead of direct map checks
- Fixed seasonal quest chains and ability to complete the same quests rewarded in past
- Update area dependent auras on quest status change (they often requires specific quest status)
- Send all not stored quest rewards by mail
- When casting quest reward spell, check if it is not self casted, if so - use player to cast this spell
- Perform full db save on quest reward to prevent data desynchronization
- Don't allow to fail completed timed quests, except for quests which are completed right from the start
- Don't allow to share pooled quests, if they are not available in the current pool (eg sharing easy dalaran weeklies, stored at alt character)
- Remove seasonal quest if rewarded quest is removed
- Don't complete whole quest on AreaExplore event, check if there are no more requirements that should be fulfilled
- Quests with flag QUEST_SPECIAL_FLAGS_PLAYER_KILL can be only credited in quest zone
Closes #18913
Closes #11187
Closes #15279
(cherry picked from commit cbbb74524623ea22fc5375697d6ec2ec16a1755f)
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 90 | ||||
-rw-r--r-- | src/server/game/Globals/ObjectMgr.cpp | 21 | ||||
-rw-r--r-- | src/server/game/Quests/QuestDef.h | 3 |
3 files changed, 81 insertions, 33 deletions
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d007bf3e7ff..5442360a165 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -91,6 +91,7 @@ #include "OutdoorPvPMgr.h" #include "Pet.h" #include "PetPackets.h" +#include "PoolMgr.h" #include "PhasingHandler.h" #include "QueryCallback.h" #include "QueryHolder.h" @@ -15271,7 +15272,7 @@ bool Player::CanCompleteQuest(uint32 quest_id) if (!qInfo) return false; - if (!qInfo->IsRepeatable() && m_RewardedQuests.find(quest_id) != m_RewardedQuests.end()) + if (!qInfo->IsRepeatable() && GetQuestRewardStatus(quest_id)) return false; // not allow re-complete quest // auto complete quest @@ -15664,10 +15665,10 @@ uint32 Player::GetQuestMoneyReward(Quest const* quest) const uint32 Player::GetQuestXPReward(Quest const* quest) { - bool rewarded = (m_RewardedQuests.find(quest->GetQuestId()) != m_RewardedQuests.end()); + bool rewarded = IsQuestRewarded(quest->GetQuestId()) && !quest->IsDFQuest(); // Not give XP in case already completed once repeatable quest - if (rewarded && !quest->IsDFQuest()) + if (rewarded) return 0; uint32 XP = quest->XPValue(this) * sWorld->getRate(RATE_XP_QUEST); @@ -15819,7 +15820,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, SendNewItem(item, quest->RewardItemCount[i], true, false); } else if (quest->IsDFQuest()) - SendItemRetrievalMail(quest->RewardItemId[i], quest->RewardItemCount[i], ItemContext::Quest_Reward); + SendItemRetrievalMail(itemId, quest->RewardItemCount[i], ItemContext::Quest_Reward); } } } @@ -15898,12 +15899,6 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (quest->CanIncreaseRewardedQuestCounters()) SetRewardedQuest(quest_id); - // StoreNewItem, mail reward, etc. save data directly to the database - // to prevent exploitable data desynchronisation we save the quest status to the database too - // (to prevent rewarding this quest another time while rewards were already given out) - CharacterDatabaseTransaction trans = CharacterDatabaseTransaction(nullptr); - _SaveQuestStatus(trans); - SendQuestReward(quest, questGiver ? questGiver->ToCreature() : nullptr, XP, !announce); // cast spells after mark quest complete (some spells have quest completed state requirements in spell_area data) @@ -15939,6 +15934,9 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, UpdateCriteria(CRITERIA_TYPE_COMPLETE_QUEST_COUNT); UpdateCriteria(CRITERIA_TYPE_COMPLETE_QUEST, quest->GetQuestId()); + // make full db save + SaveToDB(false); + if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id)) SetQuestCompletedBit(questBit, true); @@ -15969,13 +15967,15 @@ void Player::FailQuest(uint32 questId) { if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId)) { - // Already complete quests shouldn't turn failed. - if (GetQuestStatus(questId) == QUEST_STATUS_COMPLETE && !quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED)) - return; + QuestStatus qStatus = GetQuestStatus(questId); - // You can't fail a quest if you don't have it, or if it's already rewarded. - if (GetQuestStatus(questId) == QUEST_STATUS_NONE || GetQuestStatus(questId) == QUEST_STATUS_REWARDED) - return; + // we can only fail incomplete quest or... + if (qStatus != QUEST_STATUS_INCOMPLETE) + { + // completed timed quest with no requirements + if (qStatus != QUEST_STATUS_COMPLETE || !quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED) || !quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_COMPLETED_AT_START)) + return; + } SetQuestStatus(questId, QUEST_STATUS_FAILED); @@ -16356,7 +16356,7 @@ bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const } // alternative quest already started or completed - but don't check rewarded states if both are repeatable - if (GetQuestStatus(exclude_Id) != QUEST_STATUS_NONE || (!(qInfo->IsRepeatable() && Nquest->IsRepeatable()) && (m_RewardedQuests.find(exclude_Id) != m_RewardedQuests.end()))) + if (GetQuestStatus(exclude_Id) != QUEST_STATUS_NONE || (!(qInfo->IsRepeatable() && Nquest->IsRepeatable()) && GetQuestRewardStatus(exclude_Id))) { if (msg) { @@ -16521,7 +16521,7 @@ bool Player::GetQuestRewardStatus(uint32 quest_id) const // for repeatable quests: rewarded field is set after first reward only to prevent getting XP more than once if (!qInfo->IsRepeatable()) - return m_RewardedQuests.find(quest_id) != m_RewardedQuests.end(); + return IsQuestRewarded(quest_id); return false; } @@ -16536,14 +16536,8 @@ QuestStatus Player::GetQuestStatus(uint32 quest_id) const if (itr != m_QuestStatus.end()) return itr->second.Status; - if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id)) - { - if (qInfo->IsSeasonal() && !qInfo->IsRepeatable()) - return SatisfyQuestSeasonal(qInfo, false) ? QUEST_STATUS_NONE : QUEST_STATUS_REWARDED; - - if (!qInfo->IsRepeatable() && IsQuestRewarded(quest_id)) - return QUEST_STATUS_REWARDED; - } + if (GetQuestRewardStatus(quest_id)) + return QUEST_STATUS_REWARDED; } return QUEST_STATUS_NONE; } @@ -16551,7 +16545,22 @@ QuestStatus Player::GetQuestStatus(uint32 quest_id) const bool Player::CanShareQuest(uint32 quest_id) const { Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id); - return qInfo && qInfo->HasFlag(QUEST_FLAGS_SHARABLE) && IsActiveQuest(quest_id); + if (qInfo && qInfo->HasFlag(QUEST_FLAGS_SHARABLE)) + { + QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id); + if (itr != m_QuestStatus.end()) + { + if (itr->second.Status != QUEST_STATUS_INCOMPLETE) + return false; + + // in pool and not currently available (wintergrasp weekly, dalaran weekly) - can't share + if (sPoolMgr->IsPartOfAPool<Quest>(quest_id) && !sPoolMgr->IsSpawnedObject<Quest>(quest_id)) + return false; + + return true; + } + } + return false; } void Player::SetQuestStatus(uint32 questId, QuestStatus status, bool update /*= true*/) @@ -16597,6 +16606,18 @@ void Player::RemoveRewardedQuest(uint32 questId, bool update /*= true*/) if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId)) SetQuestCompletedBit(questBit, false); + // Remove seasonal quest also + Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); + if (qInfo->IsSeasonal()) + { + uint16 eventId = qInfo->GetEventIdForQuest(); + if (m_seasonalquests.find(eventId) != m_seasonalquests.end()) + { + m_seasonalquests[eventId].erase(questId); + m_SeasonalQuestChanged = true; + } + } + if (update) SendQuestUpdate(questId); } @@ -16855,8 +16876,10 @@ void Player::AreaExploredOrEventHappens(uint32 questId) { q_status.Explored = true; m_QuestStatusSave[questId] = QUEST_DEFAULT_SAVE_TYPE; - SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE); - SendQuestComplete(questId); + + // if we cannot complete quest send exploration succeded (to mark exploration on client) + if (!CanCompleteQuest(questId)) + SendQuestComplete(questId); }**/ } if (CanCompleteQuest(questId)) @@ -21269,7 +21292,7 @@ void Player::_SaveWeeklyQuestStatus(CharacterDatabaseTransaction& trans) void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction& trans) { - if (!m_SeasonalQuestChanged || m_seasonalquests.empty()) + if (!m_SeasonalQuestChanged) return; // we don't need transactions here. @@ -21277,6 +21300,11 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction& trans) stmt->setUInt64(0, GetGUID().GetCounter()); trans->Append(stmt); + m_SeasonalQuestChanged = false; + + if (m_seasonalquests.empty()) + return; + for (SeasonalEventQuestMap::const_iterator iter = m_seasonalquests.begin(); iter != m_seasonalquests.end(); ++iter) { uint16 eventId = iter->first; @@ -21292,8 +21320,6 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction& trans) trans->Append(stmt); } } - - m_SeasonalQuestChanged = false; } void Player::_SaveMonthlyQuestStatus(CharacterDatabaseTransaction& trans) diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 57922dd84eb..f519c69948f 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -4626,6 +4626,27 @@ void ObjectMgr::LoadQuests() _exclusiveQuestGroups.insert(std::pair<int32, uint32>(qinfo->_exclusiveGroup, qinfo->GetQuestId())); if (qinfo->_limitTime) qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED); + + // Special flag to determine if quest is completed from the start, used to determine if we can fail timed quest if it is completed + if (!qinfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO | QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT)) + { + bool addFlag = true; + if (qinfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER)) + { + for (QuestObjective const& obj : qinfo->GetObjectives()) + { + if (obj.Type == QUEST_OBJECTIVE_ITEM) + if (static_cast<uint32>(obj.ObjectID) != qinfo->GetSrcItemId() || static_cast<uint32>(obj.Amount) > qinfo->GetSrcItemCount()) + { + addFlag = false; + break; + } + } + } + + if (addFlag) + qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_COMPLETED_AT_START); + } } // check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index 44347222711..da9d80dad63 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -224,7 +224,8 @@ enum QuestSpecialFlags QUEST_SPECIAL_FLAGS_SPEAKTO = 0x100, // Internal flag computed only QUEST_SPECIAL_FLAGS_KILL = 0x200, // Internal flag computed only QUEST_SPECIAL_FLAGS_TIMED = 0x400, // Internal flag computed only - QUEST_SPECIAL_FLAGS_PLAYER_KILL = 0x800 // Internal flag computed only + QUEST_SPECIAL_FLAGS_PLAYER_KILL = 0x800, // Internal flag computed only + QUEST_SPECIAL_FLAGS_COMPLETED_AT_START = 0x1000 // Internal flag computed only }; enum QuestObjectiveType |