aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/Entities/Player/Player.cpp101
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp20
-rw-r--r--src/server/game/Quests/QuestDef.h3
-rw-r--r--src/server/game/Spells/SpellInfo.cpp8
-rw-r--r--src/server/game/Spells/SpellInfo.h1
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;