diff options
author | xinef1 <w.szyszko2@gmail.com> | 2017-04-24 05:46:06 +0200 |
---|---|---|
committer | ariel- <ariel-@users.noreply.github.com> | 2017-04-24 00:46:06 -0300 |
commit | cbbb74524623ea22fc5375697d6ec2ec16a1755f (patch) | |
tree | 534ee5d53e523dd193e9f06f63a6cbaf5d1e6611 /src | |
parent | 522f537048189b40a12d68583485d1de7fcbf1d2 (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
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 101 | ||||
-rw-r--r-- | src/server/game/Globals/ObjectMgr.cpp | 20 | ||||
-rw-r--r-- | src/server/game/Quests/QuestDef.h | 3 | ||||
-rw-r--r-- | src/server/game/Spells/SpellInfo.cpp | 8 | ||||
-rw-r--r-- | src/server/game/Spells/SpellInfo.h | 1 |
5 files changed, 95 insertions, 38 deletions
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 6f5043e6ae1..e73d83af94c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -62,6 +62,7 @@ #include "OutdoorPvP.h" #include "OutdoorPvPMgr.h" #include "Pet.h" +#include "PoolMgr.h" #include "QueryCallback.h" #include "QuestDef.h" #include "ReputationMgr.h" @@ -14811,7 +14812,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 @@ -15202,7 +15203,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, SendNewItem(item, quest->RewardItemIdCount[i], true, false); } else if (quest->IsDFQuest()) - SendItemRetrievalMail(quest->RewardItemId[i], quest->RewardItemIdCount[i]); + SendItemRetrievalMail(itemId, quest->RewardItemIdCount[i]); } } } @@ -15213,10 +15214,10 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (log_slot < MAX_QUEST_LOG_SIZE) SetQuestSlot(log_slot, 0); - bool rewarded = (m_RewardedQuests.find(quest_id) != m_RewardedQuests.end()); + bool rewarded = IsQuestRewarded(quest_id) && !quest->IsDFQuest(); // Not give XP in case already completed once repeatable quest - uint32 XP = rewarded && !quest->IsDFQuest() ? 0 : uint32(quest->XPValue(this)*sWorld->getRate(RATE_XP_QUEST)); + uint32 XP = rewarded ? 0 : uint32(quest->XPValue(this)*sWorld->getRate(RATE_XP_QUEST)); // handle SPELL_AURA_MOD_XP_QUEST_PCT auras XP *= GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_QUEST_PCT); @@ -15291,12 +15292,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) - SQLTransaction trans = SQLTransaction(nullptr); - _SaveQuestStatus(trans); - if (announce) SendQuestReward(quest, XP); @@ -15304,7 +15299,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (quest->GetRewSpellCast() > 0) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpellCast()); - if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM)) + if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) { if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) creature->CastSpell(this, quest->GetRewSpellCast(), true); @@ -15315,7 +15310,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, else if (quest->GetRewSpell() > 0) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpell()); - if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM)) + if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) { if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) creature->CastSpell(this, quest->GetRewSpell(), true); @@ -15329,6 +15324,9 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, quest->GetQuestId()); + // make full db save + SaveToDB(false); + if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP)) { pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest(); @@ -15355,13 +15353,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); @@ -15740,7 +15740,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) { @@ -15921,10 +15921,9 @@ bool Player::GetQuestRewardStatus(uint32 quest_id) const { if (qInfo->IsSeasonal() && !qInfo->IsRepeatable()) return !SatisfyQuestSeasonal(qInfo, false); - // 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; } @@ -15939,14 +15938,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; } @@ -15954,7 +15947,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*/) @@ -15995,6 +16003,18 @@ void Player::RemoveRewardedQuest(uint32 questId, bool update /*= true*/) m_RewardedQuestsSave[questId] = QUEST_FORCE_DELETE_SAVE_TYPE; } + // 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); } @@ -16231,12 +16251,15 @@ void Player::AreaExploredOrEventHappens(uint32 questId) { QuestStatusData& q_status = m_QuestStatus[questId]; - if (!q_status.Explored) + // Dont complete failed quest + if (!q_status.Explored && q_status.Status != QUEST_STATUS_FAILED) { 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)) @@ -16434,7 +16457,8 @@ void Player::KilledPlayerCredit() QuestStatusData& q_status = m_QuestStatus[questid]; if (q_status.Status == QUEST_STATUS_INCOMPLETE && (!GetGroup() || !GetGroup()->isRaidGroup() || qInfo->IsAllowedInRaid(GetMap()->GetDifficulty()))) { - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL)) + // PvP Killing quest require player to be in same zone as quest zone (only 2 quests so no doubt) + if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL) && GetZoneId() == static_cast<uint32>(qInfo->GetZoneOrSort())) { uint32 reqkill = qInfo->GetPlayersSlain(); uint16 curkill = q_status.PlayerCount; @@ -20017,7 +20041,7 @@ void Player::_SaveWeeklyQuestStatus(SQLTransaction& trans) void Player::_SaveSeasonalQuestStatus(SQLTransaction& trans) { - if (!m_SeasonalQuestChanged || m_seasonalquests.empty()) + if (!m_SeasonalQuestChanged) return; // we don't need transactions here. @@ -20025,6 +20049,11 @@ void Player::_SaveSeasonalQuestStatus(SQLTransaction& trans) stmt->setUInt32(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; @@ -20040,8 +20069,6 @@ void Player::_SaveSeasonalQuestStatus(SQLTransaction& trans) trans->Append(stmt); } } - - m_SeasonalQuestChanged = false; } void Player::_SaveMonthlyQuestStatus(SQLTransaction& trans) diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 1ad73db9c15..2882cef344c 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -4612,6 +4612,26 @@ void ObjectMgr::LoadQuests() qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED); if (qinfo->_requiredPlayerKills) qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL); + + // 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 (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j) + { + if (qinfo->RequiredItemId[j] != 0 && (qinfo->RequiredItemId[j] != qinfo->GetSrcItemId() || qinfo->RequiredItemCount[j] > 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 24925b064c3..c92b548f7b4 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -171,7 +171,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 }; struct QuestLocale diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 525f207aab4..99c0ceee3de 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1108,6 +1108,14 @@ bool SpellInfo::NeedsToBeTriggeredByCaster(SpellInfo const* triggeringSpell) con return false; } +bool SpellInfo::IsSelfCast() const +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (Effects[i].Effect && Effects[i].TargetA.GetTarget() != TARGET_UNIT_CASTER) + return false; + return true; +} + bool SpellInfo::IsPassive() const { return HasAttribute(SPELL_ATTR0_PASSIVE); diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 4550719b691..5336fffffeb 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -433,6 +433,7 @@ class TC_GAME_API SpellInfo bool IsTargetingArea() const; bool NeedsExplicitUnitTarget() const; bool NeedsToBeTriggeredByCaster(SpellInfo const* triggeringSpell) const; + bool IsSelfCast() const; bool IsPassive() const; bool IsAutocastable() const; |