mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-23 18:36:31 +01:00
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 cbbb745246)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user