/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "CreatureAI.h"
#include "DisableMgr.h"
#include "GameEventMgr.h"
#include "GameObjectAI.h"
#include "GameTime.h"
#include "GitRevision.h"
#include "GossipDef.h"
#include "Group.h"
#include "MapMgr.h"
#include "Player.h"
#include "PoolMgr.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
#include "SpellAuraEffects.h"
#include "SpellMgr.h"
#include "WorldSession.h"
/*********************************************************/
/*** QUEST SYSTEM ***/
/*********************************************************/
void Player::PrepareQuestMenu(ObjectGuid guid)
{
QuestRelationBounds objectQR;
QuestRelationBounds objectQIR;
// pets also can have quests
Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
if (creature)
{
objectQR = sObjectMgr->GetCreatureQuestRelationBounds(creature->GetEntry());
objectQIR = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(creature->GetEntry());
}
else
{
//we should obtain map pointer from GetMap() in 99% of cases. Special case
//only for quests which cast teleport spells on player
Map* _map = IsInWorld() ? GetMap() : sMapMgr->FindMap(GetMapId(), GetInstanceId());
ASSERT(_map);
GameObject* pGameObject = _map->GetGameObject(guid);
if (pGameObject)
{
objectQR = sObjectMgr->GetGOQuestRelationBounds(pGameObject->GetEntry());
objectQIR = sObjectMgr->GetGOQuestInvolvedRelationBounds(pGameObject->GetEntry());
}
else
return;
}
QuestMenu& qm = PlayerTalkClass->GetQuestMenu();
qm.ClearMenu();
for (QuestRelations::const_iterator i = objectQIR.first; i != objectQIR.second; ++i)
{
uint32 quest_id = i->second;
QuestStatus status = GetQuestStatus(quest_id);
if (status == QUEST_STATUS_COMPLETE)
qm.AddMenuItem(quest_id, 4);
else if (status == QUEST_STATUS_INCOMPLETE)
qm.AddMenuItem(quest_id, 4);
//else if (status == QUEST_STATUS_AVAILABLE)
// qm.AddMenuItem(quest_id, 2);
}
for (QuestRelations::const_iterator i = objectQR.first; i != objectQR.second; ++i)
{
uint32 quest_id = i->second;
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
if (!CanTakeQuest(quest, false))
continue;
if (quest->IsAutoComplete() && (!quest->IsRepeatable() || quest->IsDaily() || quest->IsWeekly() || quest->IsMonthly()))
qm.AddMenuItem(quest_id, 0);
else if (quest->IsAutoComplete())
qm.AddMenuItem(quest_id, 4);
else if (GetQuestStatus(quest_id) == QUEST_STATUS_NONE)
qm.AddMenuItem(quest_id, 2);
}
}
bool Player::HasQuest(uint32 questId) const
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (questid == questId)
{
return true;
}
}
return false;
}
void Player::SendPreparedQuest(ObjectGuid guid)
{
QuestMenu& questMenu = PlayerTalkClass->GetQuestMenu();
if (questMenu.Empty())
return;
// single element case
if (questMenu.GetMenuItemCount() == 1)
{
QuestMenuItem const& qmi0 = questMenu.GetItem(0);
uint32 questId = qmi0.QuestId;
// Auto open -- maybe also should verify there is no greeting
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
if (qmi0.QuestIcon == 4)
PlayerTalkClass->SendQuestGiverRequestItems(quest, guid, CanRewardQuest(quest, false), true);
// Send completable on repeatable and autoCompletable quest if player don't have quest
/// @todo verify if check for !quest->IsDaily() is really correct (possibly not)
else
{
Object* object = ObjectAccessor::GetObjectByTypeMask(*this, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM);
if (!object || (!object->hasQuest(questId) && !object->hasInvolvedQuest(questId)))
{
PlayerTalkClass->SendCloseGossip();
return;
}
if (quest->IsAutoAccept() && CanAddQuest(quest, true) && CanTakeQuest(quest, true))
AddQuestAndCheckCompletion(quest, object);
if (quest->IsAutoComplete() || !quest->GetQuestMethod())
PlayerTalkClass->SendQuestGiverRequestItems(quest, guid, CanCompleteRepeatableQuest(quest), true);
else
PlayerTalkClass->SendQuestGiverQuestDetails(quest, guid, true);
}
}
}
// multiple entries
else
{
QEmote qe;
qe._Delay = 0;
qe._Emote = 0;
std::string title = "";
// need pet case for some quests
Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
if (creature)
{
uint32 textid = GetGossipTextId(creature);
GossipText const* gossiptext = sObjectMgr->GetGossipText(textid);
if (!gossiptext)
{
qe._Delay = 0; //TEXTEMOTE_MESSAGE; //zyg: player emote
qe._Emote = 0; //TEXTEMOTE_HELLO; //zyg: NPC emote
title = "";
}
else
{
qe = gossiptext->Options[0].Emotes[0];
if (!gossiptext->Options[0].Text_0.empty())
{
title = gossiptext->Options[0].Text_0;
int loc_idx = GetSession()->GetSessionDbLocaleIndex();
if (loc_idx >= 0)
if (NpcTextLocale const* npcTextLocale = sObjectMgr->GetNpcTextLocale(textid))
ObjectMgr::GetLocaleString(npcTextLocale->Text_0[0], loc_idx, title);
}
else
{
title = gossiptext->Options[0].Text_1;
int loc_idx = GetSession()->GetSessionDbLocaleIndex();
if (loc_idx >= 0)
if (NpcTextLocale const* npcTextLocale = sObjectMgr->GetNpcTextLocale(textid))
ObjectMgr::GetLocaleString(npcTextLocale->Text_1[0], loc_idx, title);
}
}
}
PlayerTalkClass->SendQuestGiverQuestList(qe, title, guid);
}
}
bool Player::IsActiveQuest(uint32 quest_id) const
{
return m_QuestStatus.find(quest_id) != m_QuestStatus.end();
}
Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* quest)
{
QuestRelationBounds objectQR;
Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
if (creature)
objectQR = sObjectMgr->GetCreatureQuestRelationBounds(creature->GetEntry());
else
{
//we should obtain map pointer from GetMap() in 99% of cases. Special case
//only for quests which cast teleport spells on player
Map* _map = IsInWorld() ? GetMap() : sMapMgr->FindMap(GetMapId(), GetInstanceId());
ASSERT(_map);
GameObject* pGameObject = _map->GetGameObject(guid);
if (pGameObject)
objectQR = sObjectMgr->GetGOQuestRelationBounds(pGameObject->GetEntry());
else
return nullptr;
}
uint32 nextQuestID = quest->GetNextQuestInChain();
for (QuestRelations::const_iterator itr = objectQR.first; itr != objectQR.second; ++itr)
{
if (itr->second == nextQuestID)
return sObjectMgr->GetQuestTemplate(nextQuestID);
}
return nullptr;
}
bool Player::CanSeeStartQuest(Quest const* quest)
{
if (!sDisableMgr->IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this) && SatisfyQuestClass(quest, false) && SatisfyQuestRace(quest, false) &&
SatisfyQuestSkill(quest, false) && SatisfyQuestExclusiveGroup(quest, false) && SatisfyQuestReputation(quest, false) &&
SatisfyQuestPreviousQuest(quest, false) && SatisfyQuestNextChain(quest, false) &&
SatisfyQuestPrevChain(quest, false) && SatisfyQuestDay(quest, false) && SatisfyQuestWeek(quest, false) &&
SatisfyQuestMonth(quest, false) && SatisfyQuestSeasonal(quest, false))
{
return GetLevel() + sWorld->getIntConfig(CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF) >= quest->GetMinLevel();
}
return false;
}
bool Player::CanTakeQuest(Quest const* quest, bool msg)
{
return !sDisableMgr->IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this)
&& SatisfyQuestStatus(quest, msg) && SatisfyQuestExclusiveGroup(quest, msg)
&& SatisfyQuestClass(quest, msg) && SatisfyQuestRace(quest, msg) && SatisfyQuestLevel(quest, msg)
&& SatisfyQuestSkill(quest, msg) && SatisfyQuestReputation(quest, msg)
&& SatisfyQuestPreviousQuest(quest, msg) && SatisfyQuestTimed(quest, msg)
&& SatisfyQuestNextChain(quest, msg) && SatisfyQuestPrevChain(quest, msg)
&& SatisfyQuestDay(quest, msg) && SatisfyQuestWeek(quest, msg)
&& SatisfyQuestMonth(quest, msg) && SatisfyQuestSeasonal(quest, msg)
&& SatisfyQuestConditions(quest, msg);
}
bool Player::CanAddQuest(Quest const* quest, bool msg)
{
if (!SatisfyQuestLog(msg))
return false;
uint32 srcitem = quest->GetSrcItemId();
if (srcitem > 0)
{
uint32 count = quest->GetSrcItemCount();
ItemPosCountVec dest;
InventoryResult msg2 = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, srcitem, count);
// player already have max number (in most case 1) source item, no additional item needed and quest can be added.
if (msg2 == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
return true;
else if (msg2 != EQUIP_ERR_OK)
{
SendEquipError(msg2, nullptr, nullptr, srcitem);
PlayDirectSound(QUEST_SOUND_FAILURE, this); // Play failure sound
return false;
}
}
return true;
}
bool Player::CanCompleteQuest(uint32 quest_id, const QuestStatusData* q_savedStatus)
{
if (quest_id)
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (!qInfo)
return false;
// Xinef: take seasonals into account
if (!qInfo->IsRepeatable() && !qInfo->IsSeasonal() && IsQuestRewarded(quest_id))
return false; // not allow re-complete quest
// auto complete quest
if ((qInfo->IsAutoComplete() || !qInfo->GetQuestMethod()) && CanTakeQuest(qInfo, false))
return true;
QuestStatusData q_status;
if (q_savedStatus)
q_status = *q_savedStatus;
else
{
QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
if (itr == m_QuestStatus.end())
return false;
q_status = itr->second;
}
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
{
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
{
if (qInfo->RequiredItemCount[i] != 0 && q_status.ItemCount[i] < qInfo->RequiredItemCount[i])
return false;
}
}
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO))
{
for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
{
if (qInfo->RequiredNpcOrGo[i] == 0)
continue;
if (qInfo->RequiredNpcOrGoCount[i] != 0 && q_status.CreatureOrGOCount[i] < qInfo->RequiredNpcOrGoCount[i])
return false;
}
}
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL))
if (qInfo->GetPlayersSlain() != 0 && q_status.PlayerCount < qInfo->GetPlayersSlain())
return false;
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT) && !q_status.Explored)
return false;
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED) && q_status.Timer == 0)
return false;
if (qInfo->GetRewOrReqMoney() < 0)
{
if (!HasEnoughMoney(-qInfo->GetRewOrReqMoney()))
return false;
}
uint32 repFacId = qInfo->GetRepObjectiveFaction();
if (repFacId && GetReputationMgr().GetReputation(repFacId) < qInfo->GetRepObjectiveValue())
return false;
return true;
}
}
return false;
}
bool Player::CanCompleteRepeatableQuest(Quest const* quest)
{
// Solve problem that player don't have the quest and try complete it.
// if repeatable she must be able to complete event if player don't have it.
// Seem that all repeatable quest are DELIVER Flag so, no need to add more.
if (!CanTakeQuest(quest, false))
return false;
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
if (quest->RequiredItemId[i] && quest->RequiredItemCount[i] && !HasItemCount(quest->RequiredItemId[i], quest->RequiredItemCount[i]))
return false;
if (!CanRewardQuest(quest, false))
return false;
return true;
}
bool Player::CanRewardQuest(Quest const* quest, bool msg)
{
// not auto complete quest and not completed quest (only cheating case, then ignore without message)
if (!quest->IsDFQuest() && !quest->IsAutoComplete() && quest->GetQuestMethod() && GetQuestStatus(quest->GetQuestId()) != QUEST_STATUS_COMPLETE)
return false;
// daily quest can't be rewarded (25 daily quest already completed)
if (!SatisfyQuestDay(quest, true) || !SatisfyQuestWeek(quest, true) || !SatisfyQuestMonth(quest, true) || !SatisfyQuestSeasonal(quest, true))
return false;
// rewarded and not repeatable quest (only cheating case, then ignore without message)
if (GetQuestRewardStatus(quest->GetQuestId()))
return false;
// prevent receive reward with quest items in bank
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
{
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
{
if (quest->RequiredItemCount[i] != 0 &&
GetItemCount(quest->RequiredItemId[i]) < quest->RequiredItemCount[i])
{
if (msg)
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr, quest->RequiredItemId[i]);
return false;
}
}
}
// prevent receive reward with low money and GetRewOrReqMoney() < 0
if (quest->GetRewOrReqMoney() < 0 && !HasEnoughMoney(-quest->GetRewOrReqMoney()))
return false;
return true;
}
void Player::AddQuestAndCheckCompletion(Quest const* quest, Object* questGiver)
{
AddQuest(quest, questGiver);
if (CanCompleteQuest(quest->GetQuestId()))
CompleteQuest(quest->GetQuestId());
if (!questGiver)
return;
switch (questGiver->GetTypeId())
{
case TYPEID_UNIT:
sScriptMgr->OnQuestAccept(this, questGiver->ToCreature(), quest);
questGiver->ToCreature()->AI()->sQuestAccept(this, quest);
break;
case TYPEID_ITEM:
case TYPEID_CONTAINER:
{
Item* item = (Item*)questGiver;
sScriptMgr->OnQuestAccept(this, item, quest);
// destroy not required for quest finish quest starting item
bool destroyItem = true;
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
{
if (quest->RequiredItemId[i] == item->GetEntry() && item->GetTemplate()->MaxCount > 0)
{
destroyItem = false;
break;
}
}
if (destroyItem)
DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
break;
}
case TYPEID_GAMEOBJECT:
sScriptMgr->OnQuestAccept(this, questGiver->ToGameObject(), quest);
questGiver->ToGameObject()->AI()->QuestAccept(this, quest);
break;
default:
break;
}
}
bool Player::CanRewardQuest(Quest const* quest, uint32 reward, bool msg)
{
// prevent receive reward with quest items in bank or for not completed quest
if (!CanRewardQuest(quest, msg))
return false;
ItemPosCountVec dest;
if (quest->GetRewChoiceItemsCount() > 0)
{
if (quest->RewardChoiceItemId[reward])
{
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, quest->RewardChoiceItemId[reward], quest->RewardChoiceItemCount[reward]);
if (res != EQUIP_ERR_OK)
{
SendEquipError(res, nullptr, nullptr, quest->RewardChoiceItemId[reward]);
return false;
}
}
}
if (quest->GetRewItemsCount() > 0)
{
for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
{
if (quest->RewardItemId[i])
{
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, quest->RewardItemId[i], quest->RewardItemIdCount[i]);
if (res != EQUIP_ERR_OK)
{
SendEquipError(res, nullptr, nullptr, quest->RewardItemId[i]);
return false;
}
}
}
}
return true;
}
void Player::AddQuest(Quest const* quest, Object* questGiver)
{
uint16 log_slot = FindQuestSlot(0);
if (log_slot >= MAX_QUEST_LOG_SIZE) // Player does not have any free slot in the quest log
return;
uint32 quest_id = quest->GetQuestId();
// if not exist then created with set uState == NEW and rewarded=false
QuestStatusData& questStatusData = m_QuestStatus[quest_id];
// check for repeatable quests status reset
questStatusData.Status = QUEST_STATUS_INCOMPLETE;
questStatusData.Explored = false;
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
{
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
questStatusData.ItemCount[i] = 0;
}
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO))
{
for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; ++i)
questStatusData.CreatureOrGOCount[i] = 0;
}
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL))
questStatusData.PlayerCount = 0;
GiveQuestSourceItem(quest);
AdjustQuestReqItemCount(quest, questStatusData);
if (quest->GetRepObjectiveFaction())
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(quest->GetRepObjectiveFaction()))
GetReputationMgr().SetVisible(factionEntry);
if (quest->GetRepObjectiveFaction2())
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(quest->GetRepObjectiveFaction2()))
GetReputationMgr().SetVisible(factionEntry);
uint32 qtime = 0;
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED))
{
uint32 timeAllowed = quest->GetTimeAllowed();
// shared timed quest
if (questGiver && questGiver->IsPlayer())
timeAllowed = questGiver->ToPlayer()->getQuestStatusMap()[quest_id].Timer / IN_MILLISECONDS;
AddTimedQuest(quest_id);
questStatusData.Timer = timeAllowed * IN_MILLISECONDS;
qtime = static_cast(GameTime::GetGameTime().count()) + timeAllowed;
}
else
questStatusData.Timer = 0;
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
{
pvpInfo.IsHostile = true;
UpdatePvPState();
}
SetQuestSlot(log_slot, quest_id, qtime);
m_QuestStatusSave[quest_id] = true;
StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_QUEST, quest_id);
SendQuestUpdate(quest_id);
// check if Quest Tracker is enabled
if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER))
{
// prepare Quest Tracker datas
auto stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_QUEST_TRACK);
stmt->SetData(0, quest_id);
stmt->SetData(1, GetGUID().GetCounter());
stmt->SetData(2, GitRevision::GetHash());
stmt->SetData(3, GitRevision::GetDate());
// add to Quest Tracker
CharacterDatabase.Execute(stmt);
}
// Xinef: area auras may change on quest accept!
UpdateZoneDependentAuras(GetZoneId());
UpdateAreaDependentAuras(GetAreaId());
}
void Player::CompleteQuest(uint32 quest_id)
{
if (!quest_id)
{
return;
}
if (!sScriptMgr->OnPlayerBeforeQuestComplete(this, quest_id))
{
return;
}
SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE);
auto log_slot = FindQuestSlot(quest_id);
if (log_slot < MAX_QUEST_LOG_SIZE)
{
SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE);
}
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (qInfo && qInfo->HasFlag(QUEST_FLAGS_TRACKING))
{
RewardQuest(qInfo, 0, this, false);
}
// Xinef: area auras may change on quest completion!
UpdateZoneDependentAuras(GetZoneId());
UpdateAreaDependentAuras(GetAreaId());
AdditionalSavingAddMask(ADDITIONAL_SAVING_INVENTORY_AND_GOLD | ADDITIONAL_SAVING_QUEST_STATUS);
// check if Quest Tracker is enabled
if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER))
{
// prepare Quest Tracker datas
auto stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_QUEST_TRACK_COMPLETE_TIME);
stmt->SetData(0, quest_id);
stmt->SetData(1, GetGUID().GetCounter());
// add to Quest Tracker
CharacterDatabase.Execute(stmt);
}
}
void Player::IncompleteQuest(uint32 quest_id)
{
if (quest_id)
{
SetQuestStatus(quest_id, QUEST_STATUS_INCOMPLETE);
uint16 log_slot = FindQuestSlot(quest_id);
if (log_slot < MAX_QUEST_LOG_SIZE)
RemoveQuestSlotState(log_slot, QUEST_STATE_COMPLETE);
// Xinef: area auras may change on quest completion!
UpdateZoneDependentAuras(GetZoneId());
UpdateAreaDependentAuras(GetAreaId());
AdditionalSavingAddMask(ADDITIONAL_SAVING_QUEST_STATUS);
}
}
void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, bool announce, bool isLFGReward)
{
//this THING should be here to protect code from quest, which cast on player far teleport as a reward
//should work fine, cause far teleport will be executed in Player::Update()
SetMustDelayTeleport(true);
uint32 quest_id = quest->GetQuestId();
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
{
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->RequiredItemId[i]))
{
if (quest->RequiredItemCount[i] > 0 && itemTemplate->Bonding == BIND_QUEST_ITEM && !quest->IsRepeatable() && !HasQuestForItem(quest->RequiredItemId[i], quest_id, true))
DestroyItemCount(quest->RequiredItemId[i], 9999, true);
else
DestroyItemCount(quest->RequiredItemId[i], quest->RequiredItemCount[i], true);
}
}
for (uint8 i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i)
{
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
{
if (quest->ItemDropQuantity[i] > 0 && itemTemplate->Bonding == BIND_QUEST_ITEM && !quest->IsRepeatable() && !HasQuestForItem(quest->ItemDrop[i], quest_id))
DestroyItemCount(quest->ItemDrop[i], 9999, true);
else
DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true);
}
}
RemoveTimedQuest(quest_id);
std::vector> problematicItems;
if (quest->GetRewChoiceItemsCount())
{
if (uint32 itemId = quest->RewardChoiceItemId[reward])
{
ItemPosCountVec dest;
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, quest->RewardChoiceItemCount[reward]) == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, itemId, true);
SendNewItem(item, quest->RewardChoiceItemCount[reward], true, false, false, false);
sScriptMgr->OnPlayerQuestRewardItem(this, item, quest->RewardChoiceItemCount[reward]);
}
else
{
problematicItems.emplace_back(itemId, quest->RewardChoiceItemCount[reward]);
}
}
}
if (quest->GetRewItemsCount())
{
for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
{
if (uint32 itemId = quest->RewardItemId[i])
{
ItemPosCountVec dest;
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, quest->RewardItemIdCount[i]) == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, itemId, true);
SendNewItem(item, quest->RewardItemIdCount[i], true, false, false, false);
sScriptMgr->OnPlayerQuestRewardItem(this, item, quest->RewardItemIdCount[i]);
}
else
problematicItems.emplace_back(itemId, quest->RewardItemIdCount[i]);
}
}
}
// Xinef: send items that couldn't be added properly by mail
if (!problematicItems.empty())
{
SendItemRetrievalMail(problematicItems);
}
RewardReputation(quest);
uint16 log_slot = FindQuestSlot(quest_id);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlot(log_slot, 0);
bool const rewarded = IsQuestRewarded(quest_id) && !quest->IsDFQuest() && !(quest->IsDaily() || quest->IsWeekly() || quest->IsMonthly());
// Repeatable quests (not time-based reset ones) should not give XP on subsequent completions
uint32 XP = rewarded ? 0 : CalculateQuestRewardXP(quest);
sScriptMgr->OnPlayerQuestComputeXP(this, quest, XP);
int32 moneyRew = 0;
if (GetLevel() >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) || sScriptMgr->OnPlayerShouldBeRewardedWithMoneyInsteadOfExp(this))
{
moneyRew = quest->GetRewMoneyMaxLevel();
}
else
{
sScriptMgr->OnPlayerGiveXP(this, XP, nullptr, isLFGReward ? PlayerXPSource::XPSOURCE_QUEST_DF : PlayerXPSource::XPSOURCE_QUEST);
GiveXP(XP, nullptr, 1.0f, isLFGReward);
}
// Give player extra money if GetRewOrReqMoney > 0 and get ReqMoney if negative
if (int32 rewOrReqMoney = quest->GetRewOrReqMoney(GetLevel()))
{
moneyRew += rewOrReqMoney;
}
if (moneyRew)
{
ModifyMoney(moneyRew);
if (moneyRew > 0)
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_QUEST_REWARD, uint32(moneyRew));
}
// honor reward
if (uint32 honor = quest->CalculateHonorGain(GetLevel()))
RewardHonor(nullptr, 0, honor);
// title reward
if (quest->GetCharTitleId())
{
if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(quest->GetCharTitleId()))
SetTitle(titleEntry);
}
if (quest->GetBonusTalents())
{
m_questRewardTalentCount += quest->GetBonusTalents();
InitTalentForLevel();
}
if (quest->GetRewArenaPoints())
ModifyArenaPoints(quest->GetRewArenaPoints());
// Send reward mail
if (uint32 mail_template_id = quest->GetRewMailTemplateId())
{
//- TODO: Poor design of mail system
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (quest->GetRewMailSenderEntry() != 0)
MailDraft(mail_template_id).SendMailTo(trans, this, quest->GetRewMailSenderEntry(), MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs());
else
MailDraft(mail_template_id).SendMailTo(trans, this, questGiver, MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs());
CharacterDatabase.CommitTransaction(trans);
}
if (quest->IsDaily() || quest->IsDFQuest())
{
SetDailyQuestStatus(quest_id);
if (quest->IsDaily())
{
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST, quest_id);
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST_DAILY, quest_id);
}
}
else if (quest->IsWeekly())
SetWeeklyQuestStatus(quest_id);
else if (quest->IsMonthly())
SetMonthlyQuestStatus(quest_id);
else if (quest->IsSeasonal())
SetSeasonalQuestStatus(quest_id);
RemoveActiveQuest(quest_id, false);
SetRewardedQuest(quest_id);
if (announce)
SendQuestReward(quest, XP);
// cast spells after mark quest complete (some spells have quest completed state requirements in spell_area data)
if (quest->GetRewSpellCast() > 0)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpellCast());
if (questGiver->IsUnit() && !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);
}
else
CastSpell(this, quest->GetRewSpellCast(), true);
}
else if (quest->GetRewSpell() > 0)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpell());
if (questGiver->IsUnit() && !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);
}
else
CastSpell(this, quest->GetRewSpell(), true);
}
if (quest->GetZoneOrSort() > 0)
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE, quest->GetZoneOrSort());
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT);
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, quest->GetQuestId());
// pussywizard: replaced partial save with full save
SaveToDB(false, false);
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
{
pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest();
UpdatePvPState();
}
SendQuestUpdate(quest_id);
SendQuestGiverStatusMultiple();
//lets remove flag for delayed teleports
SetMustDelayTeleport(false);
// Xinef: area auras may change on quest completion!
UpdateZoneDependentAuras(GetZoneId());
UpdateAreaDependentAuras(GetAreaId());
sScriptMgr->OnPlayerCompleteQuest(this, quest);
}
void Player::SetRewardedQuest(uint32 quest_id)
{
m_RewardedQuests.insert(quest_id);
m_RewardedQuestsSave[quest_id] = true;
}
void Player::FailQuest(uint32 questId)
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
QuestStatus qStatus = GetQuestStatus(questId);
// xinef: if quest is marked as failed, dont do it again
if ((qStatus != QUEST_STATUS_INCOMPLETE) && (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_CAN_FAIL_IN_ANY_STATE)))
return;
SetQuestStatus(questId, QUEST_STATUS_FAILED);
uint16 log_slot = FindQuestSlot(questId);
if (log_slot < MAX_QUEST_LOG_SIZE)
{
SetQuestSlotTimer(log_slot, 1);
SetQuestSlotState(log_slot, QUEST_STATE_FAIL);
}
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED))
{
QuestStatusData& q_status = m_QuestStatus[questId];
RemoveTimedQuest(questId);
q_status.Timer = 0;
SendQuestTimerFailed(questId);
}
else
SendQuestFailed(questId);
// Destroy quest items on quest failure.
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->RequiredItemId[i]))
if (quest->RequiredItemCount[i] > 0 && itemTemplate->Bonding == BIND_QUEST_ITEM)
DestroyItemCount(quest->RequiredItemId[i], quest->RequiredItemCount[i], true);
for (uint8 i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
if (quest->ItemDropQuantity[i] > 0 && itemTemplate->Bonding == BIND_QUEST_ITEM)
DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true);
}
}
void Player::AbandonQuest(uint32 questId)
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
// It will Destroy quest items on quests abandons.
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->RequiredItemId[i]))
if (quest->RequiredItemCount[i] > 0 && itemTemplate->Bonding == BIND_QUEST_ITEM)
DestroyItemCount(quest->RequiredItemId[i], quest->RequiredItemCount[i], true);
for (uint8 i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
if (quest->ItemDropQuantity[i] > 0 && itemTemplate->Bonding == BIND_QUEST_ITEM)
DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true);
}
}
bool Player::SatisfyQuestSkill(Quest const* qInfo, bool msg) const
{
uint32 skill = qInfo->GetRequiredSkill();
// skip 0 case RequiredSkill
if (skill == 0)
return true;
// check skill value
if (GetBaseSkillValue(skill) < qInfo->GetRequiredSkillValue())
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
return true;
}
bool Player::SatisfyQuestLevel(Quest const* qInfo, bool msg) const
{
if (GetLevel() < qInfo->GetMinLevel())
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_LOW_LEVEL);
return false;
}
else if (qInfo->GetMaxLevel() > 0 && GetLevel() > qInfo->GetMaxLevel())
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); // There doesn't seem to be a specific response for too high player level
return false;
}
return true;
}
bool Player::SatisfyQuestLog(bool msg)
{
// exist free slot
if (FindQuestSlot(0) < MAX_QUEST_LOG_SIZE)
return true;
if (msg)
{
WorldPacket data(SMSG_QUESTLOG_FULL, 0);
SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTLOG_FULL");
}
return false;
}
bool Player::SatisfyQuestPreviousQuest(Quest const* qInfo, bool msg) const
{
// No previous quest (might be first quest in a series)
if (qInfo->prevQuests.empty())
return true;
for (Quest::PrevQuests::const_iterator iter = qInfo->prevQuests.begin(); iter != qInfo->prevQuests.end(); ++iter)
{
uint32 prevId = std::abs(*iter);
Quest const* qPrevInfo = sObjectMgr->GetQuestTemplate(prevId);
if (qPrevInfo)
{
// If any of the positive previous quests completed, return true
if (*iter > 0 && IsQuestRewarded(prevId) && (!qPrevInfo->IsSeasonal() || !SatisfyQuestSeasonal(qPrevInfo, false)))
{
// skip one-from-all exclusive group
if (qPrevInfo->GetExclusiveGroup() >= 0)
return true;
// each-from-all exclusive group (< 0)
// can be start if only all quests in prev quest exclusive group completed and rewarded
ObjectMgr::ExclusiveQuestGroupsBounds range(sObjectMgr->mExclusiveQuestGroups.equal_range(qPrevInfo->GetExclusiveGroup()));
for (; range.first != range.second; ++range.first)
{
uint32 exclude_Id = range.first->second;
// skip checked quest id, only state of other quests in group is interesting
if (exclude_Id == prevId)
continue;
// alternative quest from group also must be completed and rewarded(reported)
Quest const* qExcludeInfo = sObjectMgr->GetQuestTemplate(exclude_Id);
if (!IsQuestRewarded(exclude_Id) || (qExcludeInfo->IsSeasonal() && SatisfyQuestSeasonal(qExcludeInfo, false)))
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
}
return true;
}
// If any of the negative previous quests active, return true
if (*iter < 0 && GetQuestStatus(prevId) != QUEST_STATUS_NONE)
{
// skip one-from-all exclusive group
if (qPrevInfo->GetExclusiveGroup() >= 0)
return true;
// each-from-all exclusive group (< 0)
// can be start if only all quests in prev quest exclusive group active
ObjectMgr::ExclusiveQuestGroupsBounds range(sObjectMgr->mExclusiveQuestGroups.equal_range(qPrevInfo->GetExclusiveGroup()));
for (; range.first != range.second; ++range.first)
{
uint32 exclude_Id = range.first->second;
// skip checked quest id, only state of other quests in group is interesting
if (exclude_Id == prevId)
continue;
// alternative quest from group also must be active
if (GetQuestStatus(exclude_Id) != QUEST_STATUS_NONE)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
}
return true;
}
}
}
// Has only positive prev. quests in non-rewarded state
// and negative prev. quests in non-active state
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
bool Player::SatisfyQuestClass(Quest const* qInfo, bool msg) const
{
uint32 reqClass = qInfo->GetRequiredClasses();
if (reqClass == 0)
return true;
if ((reqClass & getClassMask()) == 0)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
return true;
}
bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const
{
uint32 reqraces = qInfo->GetAllowableRaces();
if (reqraces == 0)
return true;
if ((reqraces & getRaceMask()) == 0)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_WRONG_RACE);
return false;
}
return true;
}
bool Player::SatisfyQuestReputation(Quest const* qInfo, bool msg) const
{
uint32 fIdMin = qInfo->GetRequiredMinRepFaction(); //Min required rep
if (fIdMin && GetReputationMgr().GetReputation(fIdMin) < qInfo->GetRequiredMinRepValue())
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
uint32 fIdMax = qInfo->GetRequiredMaxRepFaction(); //Max required rep
if (fIdMax && GetReputationMgr().GetReputation(fIdMax) >= qInfo->GetRequiredMaxRepValue())
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
// ReputationObjective2 does not seem to be an objective requirement but a requirement
// to be able to accept the quest
uint32 fIdObj = qInfo->GetRepObjectiveFaction2();
if (fIdObj && GetReputationMgr().GetReputation(fIdObj) >= qInfo->GetRepObjectiveValue2())
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
return true;
}
bool Player::SatisfyQuestStatus(Quest const* qInfo, bool msg) const
{
if (GetQuestStatus(qInfo->GetQuestId()) != QUEST_STATUS_NONE)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_QUEST_ALREADY_ON);
return false;
}
return true;
}
bool Player::SatisfyQuestConditions(Quest const* qInfo, bool msg)
{
ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, qInfo->GetQuestId());
if (!sConditionMgr->IsObjectMeetToConditions(this, conditions))
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
LOG_DEBUG("condition", "Player::SatisfyQuestConditions: conditions not met for quest {}", qInfo->GetQuestId());
return false;
}
return true;
}
bool Player::SatisfyQuestTimed(Quest const* qInfo, bool msg) const
{
if (!m_timedquests.empty() && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED))
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_QUEST_ONLY_ONE_TIMED);
return false;
}
return true;
}
bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const
{
// non positive exclusive group, if > 0 then can be start if any other quest in exclusive group already started/completed
if (qInfo->GetExclusiveGroup() <= 0)
return true;
ObjectMgr::ExclusiveQuestGroupsBounds range(sObjectMgr->mExclusiveQuestGroups.equal_range(qInfo->GetExclusiveGroup()));
for (; range.first != range.second; ++range.first)
{
uint32 exclude_Id = range.first->second;
// skip checked quest id, only state of other quests in group is interesting
if (exclude_Id == qInfo->GetQuestId())
continue;
// not allow have daily quest if daily quest from exclusive group already recently completed
Quest const* Nquest = sObjectMgr->GetQuestTemplate(exclude_Id);
if (!SatisfyQuestDay(Nquest, false) || !SatisfyQuestWeek(Nquest, false) || !SatisfyQuestSeasonal(Nquest, false))
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
// 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()) && !Nquest->IsSeasonal() && IsQuestRewarded(exclude_Id))) // pussywizard: added !Nquest->IsSeasonal() because seasonal quests are considered rewarded only if finished this year, this is checked above in SatisfyQuestSeasonal
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
}
return true;
}
bool Player::SatisfyQuestNextChain(Quest const* qInfo, bool msg) const
{
uint32 nextQuest = qInfo->GetNextQuestInChain();
if (!nextQuest)
return true;
// next quest in chain already started or completed
if (GetQuestStatus(nextQuest) != QUEST_STATUS_NONE) // GetQuestStatus returns QUEST_STATUS_COMPLETED for rewarded quests
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
// check for all quests further up the chain
// only necessary if there are quest chains with more than one quest that can be skipped
//return SatisfyQuestNextChain(qInfo->GetNextQuestInChain(), msg);
return true;
}
bool Player::SatisfyQuestPrevChain(Quest const* qInfo, bool msg) const
{
// No previous quest in chain
if (qInfo->prevChainQuests.empty())
return true;
for (Quest::PrevChainQuests::const_iterator iter = qInfo->prevChainQuests.begin(); iter != qInfo->prevChainQuests.end(); ++iter)
{
QuestStatusMap::const_iterator itr = m_QuestStatus.find(*iter);
// If any of the previous quests in chain active, return false
if (itr != m_QuestStatus.end() && itr->second.Status != QUEST_STATUS_NONE)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
// check for all quests further down the chain
// only necessary if there are quest chains with more than one quest that can be skipped
//if (!SatisfyQuestPrevChain(prevId, msg))
// return false;
}
// No previous quest in chain active
return true;
}
bool Player::SatisfyQuestDay(Quest const* qInfo, bool msg) const
{
if (!qInfo->IsDaily() && !qInfo->IsDFQuest())
return true;
if (qInfo->IsDFQuest())
{
if (m_DFQuests.find(qInfo->GetQuestId()) != m_DFQuests.end())
return false;
return true;
}
bool have_slot = false;
for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
{
uint32 id = GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx);
if (qInfo->GetQuestId() == id)
return false;
if (!id)
have_slot = true;
}
if (!have_slot)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DAILY_QUESTS_REMAINING);
return false;
}
return true;
}
bool Player::SatisfyQuestWeek(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsWeekly() || m_weeklyquests.empty())
return true;
// if not found in cooldown list
return m_weeklyquests.find(qInfo->GetQuestId()) == m_weeklyquests.end();
}
bool Player::SatisfyQuestSeasonal(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsSeasonal() || m_seasonalquests.empty())
return true;
// cppcheck-suppress mismatchingContainers
Player::SeasonalEventQuestMap::iterator itr = ((Player*)this)->m_seasonalquests.find(qInfo->GetEventIdForQuest());
if (itr == m_seasonalquests.end() || itr->second.empty())
return true;
// if not found in cooldown list
return itr->second.find(qInfo->GetQuestId()) == itr->second.end();
}
bool Player::SatisfyQuestMonth(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsMonthly() || m_monthlyquests.empty())
return true;
// if not found in cooldown list
return m_monthlyquests.find(qInfo->GetQuestId()) == m_monthlyquests.end();
}
bool Player::GiveQuestSourceItem(Quest const* quest)
{
uint32 srcitem = quest->GetSrcItemId();
if (srcitem > 0)
{
uint32 count = quest->GetSrcItemCount();
if (count <= 0)
count = 1;
ItemPosCountVec dest;
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, srcitem, count);
if (msg == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, srcitem, true);
SendNewItem(item, count, true, false);
return true;
}
// player already have max amount required item, just report success
else if (msg == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
return true;
else
SendEquipError(msg, nullptr, nullptr, srcitem);
return false;
}
return true;
}
bool Player::TakeQuestSourceItem(uint32 questId, bool msg)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (quest)
{
uint32 srcItemId = quest->GetSrcItemId();
ItemTemplate const* item = sObjectMgr->GetItemTemplate(srcItemId);
if (srcItemId > 0)
{
uint32 count = quest->GetSrcItemCount();
if (count <= 0)
count = 1;
// exist two cases when destroy source quest item not possible:
// a) non un-equippable item (equipped non-empty bag, for example)
// b) when quest is started from an item and item also is needed in
// the end as RequiredItemId
InventoryResult res = CanUnequipItems(srcItemId, count);
if (res != EQUIP_ERR_OK)
{
if (msg)
SendEquipError(res, nullptr, nullptr, srcItemId);
return false;
}
bool destroyItem = true;
for (uint8 n = 0; n < QUEST_ITEM_OBJECTIVES_COUNT; ++n)
if (item->StartQuest == questId && srcItemId == quest->RequiredItemId[n])
destroyItem = false;
if (destroyItem)
DestroyItemCount(srcItemId, count, true, true);
}
}
return true;
}
uint32 Player::CalculateQuestRewardXP(Quest const* quest)
{
// apply world quest rate
uint32 xp = uint32(quest->XPValue(GetLevel()) * GetQuestRate(quest->IsDFQuest()));
// handle SPELL_AURA_MOD_XP_QUEST_PCT auras
xp *= GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_QUEST_PCT);
return xp;
}
bool Player::GetQuestRewardStatus(uint32 quest_id) const
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (qInfo)
{
if (qInfo->IsSeasonal())
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 IsQuestRewarded(quest_id);
}
return false;
}
QuestStatus Player::GetQuestStatus(uint32 quest_id) const
{
if (quest_id)
{
QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
if (itr != m_QuestStatus.end())
{
return itr->second.Status;
}
if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id))
{
if (qInfo->IsSeasonal())
{
return SatisfyQuestSeasonal(qInfo, false) ? QUEST_STATUS_NONE : QUEST_STATUS_REWARDED;
}
if (!qInfo->IsRepeatable() && IsQuestRewarded(quest_id))
{
return QUEST_STATUS_REWARDED;
}
}
}
return QUEST_STATUS_NONE;
}
bool Player::CanShareQuest(uint32 quest_id) const
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (qInfo && qInfo->HasFlag(QUEST_FLAGS_SHARABLE))
{
QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
if (itr != m_QuestStatus.end())
{
// in pool and not currently available (wintergrasp weekly, dalaran weekly) - can't share
if (sPoolMgr->IsPartOfAPool(quest_id) && !sPoolMgr->IsSpawnedObject(quest_id))
{
SendPushToPartyResponse(this, QUEST_PARTY_MSG_CANT_BE_SHARED_TODAY);
return false;
}
return true;
}
}
return false;
}
void Player::SetQuestStatus(uint32 questId, QuestStatus status, bool update /*= true*/)
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
m_QuestStatus[questId].Status = status;
if (quest->GetQuestMethod() && !quest->IsAutoComplete())
{
m_QuestStatusSave[questId] = true;
}
}
if (update)
SendQuestUpdate(questId);
}
void Player::RemoveActiveQuest(uint32 questId, bool update /*= true*/)
{
QuestStatusMap::iterator itr = m_QuestStatus.find(questId);
if (itr != m_QuestStatus.end())
{
m_QuestStatus.erase(itr);
m_QuestStatusSave[questId] = false;
}
if (update)
SendQuestUpdate(questId);
// Xinef: area auras may change on quest remove!
UpdateZoneDependentAuras(GetZoneId());
UpdateAreaDependentAuras(GetAreaId());
AdditionalSavingAddMask(ADDITIONAL_SAVING_QUEST_STATUS);
}
void Player::RemoveRewardedQuest(uint32 questId, bool update /*= true*/)
{
RewardedQuestSet::iterator rewItr = m_RewardedQuests.find(questId);
if (rewItr != m_RewardedQuests.end())
{
m_RewardedQuests.erase(rewItr);
m_RewardedQuestsSave[questId] = false;
}
if (update)
SendQuestUpdate(questId);
}
void Player::SendQuestUpdate(uint32 questId)
{
uint32 zone = 0, area = 0;
// xinef: fixup
uint32 oldSpellId = 0;
SpellAreaForQuestMapBounds saBounds = sSpellMgr->GetSpellAreaForQuestMapBounds(questId);
if (saBounds.first != saBounds.second)
{
GetZoneAndAreaId(zone, area);
for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
{
// xinef: spell was already casted, skip different areas with same spell
if (itr->second->spellId == oldSpellId)
continue;
if (itr->second->autocast && itr->second->IsFitToRequirements(this, zone, area))
if (!HasAura(itr->second->spellId))
{
CastSpell(this, itr->second->spellId, true);
oldSpellId = itr->second->spellId;
}
}
}
saBounds = sSpellMgr->GetSpellAreaForQuestEndMapBounds(questId);
// xinef: fixup
uint32 skipSpellId = 0;
oldSpellId = 0;
if (saBounds.first != saBounds.second)
{
if (!zone || !area)
GetZoneAndAreaId(zone, area);
for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
{
// xinef: skip spell for which condition is already fulfilled
if (itr->second->spellId == skipSpellId)
continue;
skipSpellId = 0;
// xinef: spells are sorted, if no condition is fulfilled remove aura
if (oldSpellId && oldSpellId != itr->second->spellId)
{
RemoveAurasDueToSpell(oldSpellId);
oldSpellId = 0;
}
if (!itr->second->IsFitToRequirements(this, zone, area))
{
//RemoveAurasDueToSpell(itr->second->spellId);
oldSpellId = itr->second->spellId;
}
else
{
skipSpellId = itr->second->spellId;
oldSpellId = 0;
}
}
// xinef: check if we have something to remove yet
if (oldSpellId)
RemoveAurasDueToSpell(oldSpellId);
}
UpdateForQuestWorldObjects();
}
QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver)
{
QuestRelationBounds qr;
QuestRelationBounds qir;
sScriptMgr->GetDialogStatus(this, questgiver);
switch (questgiver->GetTypeId())
{
case TYPEID_GAMEOBJECT:
{
QuestGiverStatus questStatus = QuestGiverStatus(sScriptMgr->GetDialogStatus(this, questgiver->ToGameObject()));
if (questStatus != DIALOG_STATUS_SCRIPTED_NO_STATUS)
return questStatus;
qr = sObjectMgr->GetGOQuestRelationBounds(questgiver->GetEntry());
qir = sObjectMgr->GetGOQuestInvolvedRelationBounds(questgiver->GetEntry());
break;
}
case TYPEID_UNIT:
{
QuestGiverStatus questStatus = QuestGiverStatus(sScriptMgr->GetDialogStatus(this, questgiver->ToCreature()));
if (questStatus != DIALOG_STATUS_SCRIPTED_NO_STATUS)
return questStatus;
qr = sObjectMgr->GetCreatureQuestRelationBounds(questgiver->GetEntry());
qir = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(questgiver->GetEntry());
break;
}
default:
// it's impossible, but check
//LOG_ERROR("entities.player.quest", "GetQuestDialogStatus called for unexpected type {}", questgiver->GetTypeId());
return DIALOG_STATUS_NONE;
}
QuestGiverStatus result = DIALOG_STATUS_NONE;
for (QuestRelations::const_iterator i = qir.first; i != qir.second; ++i)
{
QuestGiverStatus result2 = DIALOG_STATUS_NONE;
uint32 questId = i->second;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, quest->GetQuestId());
if (!sConditionMgr->IsObjectMeetToConditions(this, conditions))
continue;
QuestStatus status = GetQuestStatus(questId);
if (status == QUEST_STATUS_COMPLETE && !GetQuestRewardStatus(questId))
{
result2 = DIALOG_STATUS_REWARD;
}
else if (status == QUEST_STATUS_INCOMPLETE)
{
result2 = DIALOG_STATUS_INCOMPLETE;
}
if (quest->IsAutoComplete() && CanTakeQuest(quest, false) && quest->IsRepeatable() && !quest->IsDailyOrWeekly())
{
result2 = DIALOG_STATUS_REWARD_REP;
}
if (result2 > result)
{
result = result2;
}
}
for (QuestRelations::const_iterator i = qr.first; i != qr.second; ++i)
{
QuestGiverStatus result2 = DIALOG_STATUS_NONE;
uint32 questId = i->second;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, quest->GetQuestId());
if (!sConditionMgr->IsObjectMeetToConditions(this, conditions))
continue;
QuestStatus status = GetQuestStatus(questId);
if (status == QUEST_STATUS_NONE)
{
if (CanSeeStartQuest(quest))
{
if (SatisfyQuestLevel(quest, false))
{
bool isNotLowLevelQuest = GetLevel() <= (GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF));
if (quest->IsRepeatable())
{
if (quest->IsDaily())
{
if (isNotLowLevelQuest)
{
result2 = DIALOG_STATUS_AVAILABLE_REP;
}
else
{
result2 = DIALOG_STATUS_LOW_LEVEL_AVAILABLE_REP;
}
}
else if (quest->IsWeekly() || quest->IsMonthly())
{
if (isNotLowLevelQuest)
{
result2 = DIALOG_STATUS_AVAILABLE;
}
else
{
result2 = DIALOG_STATUS_LOW_LEVEL_AVAILABLE;
}
}
else if (quest->IsAutoComplete())
{
if (isNotLowLevelQuest)
{
result2 = DIALOG_STATUS_REWARD_REP;
}
else
{
result2 = DIALOG_STATUS_LOW_LEVEL_REWARD_REP;
}
}
else
{
if (isNotLowLevelQuest)
{
result2 = DIALOG_STATUS_REWARD_REP;
}
else
{
result2 = DIALOG_STATUS_LOW_LEVEL_REWARD_REP;
}
}
}
else
{
result2 = isNotLowLevelQuest ? DIALOG_STATUS_AVAILABLE : DIALOG_STATUS_LOW_LEVEL_AVAILABLE;
}
}
else
{
result2 = DIALOG_STATUS_UNAVAILABLE;
}
}
}
if (result2 > result)
result = result2;
}
return result;
}
// not used in Trinity, but used in scripting code
uint16 Player::GetReqKillOrCastCurrentCount(uint32 quest_id, int32 entry)
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (!qInfo)
return 0;
for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
if (qInfo->RequiredNpcOrGo[j] == entry)
return m_QuestStatus[quest_id].CreatureOrGOCount[j];
return 0;
}
void Player::AdjustQuestReqItemCount(Quest const* quest, QuestStatusData& questStatusData)
{
if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
{
for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
{
uint32 reqitemcount = quest->RequiredItemCount[i];
if (reqitemcount != 0)
{
uint32 curitemcount = GetItemCount(quest->RequiredItemId[i], true);
questStatusData.ItemCount[i] = std::min(curitemcount, reqitemcount);
m_QuestStatusSave[quest->GetQuestId()] = true;
}
}
}
}
uint16 Player::FindQuestSlot(uint32 quest_id) const
{
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
if (GetQuestSlotQuestId(i) == quest_id)
return i;
return MAX_QUEST_LOG_SIZE;
}
void Player::AreaExploredOrEventHappens(uint32 questId)
{
if (questId)
{
uint16 log_slot = FindQuestSlot(questId);
QuestStatusData* q_status = nullptr;
if (log_slot < MAX_QUEST_LOG_SIZE)
{
q_status = &m_QuestStatus[questId];
// xinef: added failed check
if (!q_status->Explored && q_status->Status != QUEST_STATUS_FAILED)
{
q_status->Explored = true;
m_QuestStatusSave[questId] = true;
SendQuestComplete(questId);
}
}
if (CanCompleteQuest(questId, q_status))
CompleteQuest(questId);
else
AdditionalSavingAddMask(ADDITIONAL_SAVING_QUEST_STATUS);
}
}
//not used in Trinityd, function for external script library
void Player::GroupEventHappens(uint32 questId, WorldObject const* pEventObject)
{
if (Group* group = GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* player = itr->GetSource();
// for any leave or dead (with not released body) group member at appropriate distance
if (player && player->IsAtGroupRewardDistance(pEventObject) && !player->GetCorpse())
player->AreaExploredOrEventHappens(questId);
}
}
else
AreaExploredOrEventHappens(questId);
}
void Player::ItemAddedQuestCheck(uint32 entry, uint32 count)
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (questid == 0)
continue;
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status != QUEST_STATUS_INCOMPLETE)
continue;
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid);
if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
continue;
for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
{
uint32 reqitem = qInfo->RequiredItemId[j];
if (reqitem == entry)
{
uint32 reqitemcount = qInfo->RequiredItemCount[j];
uint16 curitemcount = q_status.ItemCount[j];
if (curitemcount < reqitemcount)
{
q_status.ItemCount[j] = std::min(q_status.ItemCount[j] + count, reqitemcount);
m_QuestStatusSave[questid] = true;
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
else
AdditionalSavingAddMask(ADDITIONAL_SAVING_INVENTORY_AND_GOLD | ADDITIONAL_SAVING_QUEST_STATUS);
}
}
}
UpdateForQuestWorldObjects();
}
void Player::ItemRemovedQuestCheck(uint32 entry, uint32 count)
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (!questid)
continue;
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid);
if (!qInfo)
continue;
if (!qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
continue;
for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
{
uint32 reqitem = qInfo->RequiredItemId[j];
if (reqitem == entry)
{
QuestStatusData& q_status = m_QuestStatus[questid];
uint32 reqitemcount = qInfo->RequiredItemCount[j];
uint16 curitemcount = q_status.ItemCount[j];
if (q_status.ItemCount[j] >= reqitemcount) // we may have more than what the status shows
curitemcount = GetItemCount(entry, false);
uint16 newItemCount = (count > curitemcount) ? 0 : curitemcount - count;
newItemCount = std::min(newItemCount, reqitemcount);
if (newItemCount != q_status.ItemCount[j])
{
q_status.ItemCount[j] = newItemCount;
m_QuestStatusSave[questid] = true;
IncompleteQuest(questid);
}
}
}
}
UpdateForQuestWorldObjects();
}
void Player::KilledMonster(CreatureTemplate const* cInfo, ObjectGuid guid)
{
ASSERT(cInfo);
if (cInfo->Entry)
KilledMonsterCredit(cInfo->Entry, guid);
for (uint8 i = 0; i < MAX_KILL_CREDIT; ++i)
if (cInfo->KillCredit[i])
KilledMonsterCredit(cInfo->KillCredit[i]);
}
void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid)
{
uint16 addkillcount = 1;
uint32 real_entry = entry;
if (guid)
{
Creature* killed = GetMap()->GetCreature(guid);
if (killed && killed->GetEntry())
real_entry = killed->GetEntry();
}
StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_CREATURE, real_entry); // MUST BE CALLED FIRST
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, real_entry, addkillcount, guid ? GetMap()->GetCreature(guid) : nullptr);
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (!questid)
continue;
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid);
if (!qInfo)
continue;
// just if !ingroup || !noraidgroup || raidgroup
// xinef: or is pvp quest, and player in BG/BF group
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status == QUEST_STATUS_INCOMPLETE && (!GetGroup() || !GetGroup()->isRaidGroup() || qInfo->IsAllowedInRaid(GetMap()->GetDifficulty()) ||
(qInfo->IsPVPQuest() && (GetGroup()->isBFGroup() || GetGroup()->isBGGroup()))))
{
if (!sScriptMgr->OnPlayerPassedQuestKilledMonsterCredit(this, qInfo, entry, real_entry, guid))
continue;
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL) /*&& !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_CAST)*/)
{
for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
{
// skip GO activate objective or none
if (qInfo->RequiredNpcOrGo[j] <= 0)
continue;
uint32 reqkill = qInfo->RequiredNpcOrGo[j];
if (reqkill == real_entry)
{
uint32 reqkillcount = qInfo->RequiredNpcOrGoCount[j];
uint16 curkillcount = q_status.CreatureOrGOCount[j];
if (curkillcount < reqkillcount)
{
q_status.CreatureOrGOCount[j] = curkillcount + addkillcount;
m_QuestStatusSave[questid] = true;
SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, curkillcount, addkillcount);
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
else
AdditionalSavingAddMask(ADDITIONAL_SAVING_QUEST_STATUS);
// same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization).
break;
}
}
}
}
}
}
void Player::KilledPlayerCredit(uint16 count)
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (!questid)
{
continue;
}
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid);
if (!qInfo)
{
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()->GetDifficulty())))
{
// Xinef: PvP Killing quest require player to be in same zone as quest zone (only 2 quests so no doubt, can be extended to conditions in cata ;s)
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL) && (qInfo->GetZoneOrSort() >= 0 && GetZoneId() == uint32(qInfo->GetZoneOrSort())))
{
KilledPlayerCreditForQuest(count, qInfo);
break; // there is only one quest per zone
}
}
}
}
void Player::KilledPlayerCreditForQuest(uint16 count, Quest const* quest)
{
uint32 const questId = quest->GetQuestId();
auto it = m_QuestStatus.find(questId);
if (it == m_QuestStatus.end())
{
return;
}
QuestStatusData& questStatus = it->second;
uint16 curKill = questStatus.PlayerCount;
uint32 reqKill = quest->GetPlayersSlain();
if (curKill < reqKill)
{
count = std::min(reqKill - curKill, count);
questStatus.PlayerCount = curKill + count;
m_QuestStatusSave[quest->GetQuestId()] = true;
SendQuestUpdateAddPlayer(quest, curKill, count);
}
if (CanCompleteQuest(questId))
{
CompleteQuest(questId);
}
}
void Player::KillCreditGO(uint32 entry, ObjectGuid guid)
{
uint16 addCastCount = 1;
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (!questid)
continue;
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid);
if (!qInfo)
continue;
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_CAST) /*&& !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL)*/)
{
for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
{
uint32 reqTarget = 0;
// GO activate objective
if (qInfo->RequiredNpcOrGo[j] < 0)
// checked at quest_template loading
reqTarget = - qInfo->RequiredNpcOrGo[j];
// other not this creature/GO related objectives
if (reqTarget != entry)
continue;
uint32 reqCastCount = qInfo->RequiredNpcOrGoCount[j];
uint16 curCastCount = q_status.CreatureOrGOCount[j];
if (curCastCount < reqCastCount)
{
q_status.CreatureOrGOCount[j] = curCastCount + addCastCount;
m_QuestStatusSave[questid] = true;
SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, curCastCount, addCastCount);
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
else
AdditionalSavingAddMask(ADDITIONAL_SAVING_QUEST_STATUS);
// same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization).
break;
}
}
}
}
}
void Player::TalkedToCreature(uint32 entry, ObjectGuid guid)
{
uint16 addTalkCount = 1;
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (!questid)
continue;
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid);
if (!qInfo)
continue;
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO))
{
for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
{
// skip Gameobject objectives
if (qInfo->RequiredNpcOrGo[j] < 0)
continue;
uint32 reqTarget = 0;
if (qInfo->RequiredNpcOrGo[j] > 0) // creature activate objectives
// checked at quest_template loading
reqTarget = qInfo->RequiredNpcOrGo[j];
else
continue;
if (reqTarget == entry)
{
uint32 reqTalkCount = qInfo->RequiredNpcOrGoCount[j];
uint16 curTalkCount = q_status.CreatureOrGOCount[j];
if (curTalkCount < reqTalkCount)
{
q_status.CreatureOrGOCount[j] = curTalkCount + addTalkCount;
m_QuestStatusSave[questid] = true;
SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, curTalkCount, addTalkCount);
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
else
AdditionalSavingAddMask(ADDITIONAL_SAVING_QUEST_STATUS);
// same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization).
continue;
}
}
}
}
}
}
void Player::MoneyChanged(uint32 count)
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (!questid)
continue;
if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid))
{
int32 rewOrReqMoney = qInfo->GetRewOrReqMoney();
if (rewOrReqMoney < 0)
{
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
if (int32(count) >= -rewOrReqMoney)
{
if (CanCompleteQuest(questid))
{
CompleteQuest(questid);
}
}
}
else if (q_status.Status == QUEST_STATUS_COMPLETE)
{
if (int32(count) < -rewOrReqMoney)
{
IncompleteQuest(questid);
}
}
}
}
}
}
void Player::ReputationChanged(FactionEntry const* factionEntry)
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
if (uint32 questid = GetQuestSlotQuestId(i))
{
if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid))
{
if (qInfo->GetRepObjectiveFaction() == factionEntry->ID)
{
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
if (GetReputationMgr().GetReputation(factionEntry) >= qInfo->GetRepObjectiveValue())
if (CanCompleteQuest(questid))
CompleteQuest(questid);
}
else if (q_status.Status == QUEST_STATUS_COMPLETE)
{
if (GetReputationMgr().GetReputation(factionEntry) < qInfo->GetRepObjectiveValue())
IncompleteQuest(questid);
}
}
}
}
}
}
void Player::ReputationChanged2(FactionEntry const* factionEntry)
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
if (uint32 questid = GetQuestSlotQuestId(i))
{
if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid))
{
if (qInfo->GetRepObjectiveFaction2() == factionEntry->ID)
{
QuestStatusData& q_status = m_QuestStatus[questid];
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
if (GetReputationMgr().GetReputation(factionEntry) >= qInfo->GetRepObjectiveValue2())
if (CanCompleteQuest(questid))
CompleteQuest(questid);
}
else if (q_status.Status == QUEST_STATUS_COMPLETE)
{
if (GetReputationMgr().GetReputation(factionEntry) < qInfo->GetRepObjectiveValue2())
IncompleteQuest(questid);
}
}
}
}
}
}
bool Player::HasQuestForItem(uint32 itemid, uint32 excludeQuestId /* 0 */, bool turnIn /* false */, bool* showInLoot /*= nullptr*/) const
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questid = GetQuestSlotQuestId(i);
if (questid == 0 || questid == excludeQuestId)
continue;
QuestStatusMap::const_iterator qs_itr = m_QuestStatus.find(questid);
if (qs_itr == m_QuestStatus.end())
continue;
QuestStatusData const& q_status = qs_itr->second;
if ((q_status.Status == QUEST_STATUS_INCOMPLETE) || (turnIn && q_status.Status == QUEST_STATUS_COMPLETE))
{
Quest const* qinfo = sObjectMgr->GetQuestTemplate(questid);
if (!qinfo)
continue;
// hide quest if player is in raid-group and quest is no raid quest
if (GetGroup() && GetGroup()->isRaidGroup() && !qinfo->IsAllowedInRaid(GetMap()->GetDifficulty()))
{
if (!InBattleground() && !GetGroup()->isBFGroup()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
{
continue;
}
}
// There should be no mixed ReqItem/ReqSource drop
// This part for ReqItem drop
for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
{
if (itemid == qinfo->RequiredItemId[j] && q_status.ItemCount[j] < qinfo->RequiredItemCount[j])
{
if (showInLoot)
{
if (GetItemCount(itemid, true) < qinfo->RequiredItemCount[j])
{
return true;
}
*showInLoot = false;
}
else
{
return true;
}
}
if (turnIn && q_status.ItemCount[j] >= qinfo->RequiredItemCount[j])
{
return true;
}
}
// This part - for ReqSource
for (uint8 j = 0; j < QUEST_SOURCE_ITEM_IDS_COUNT; ++j)
{
// examined item is a source item
if (qinfo->ItemDrop[j] == itemid)
{
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid);
uint32 ownedCount = GetItemCount(itemid, true);
// 'unique' item
if ((pProto->MaxCount && int32(ownedCount) < pProto->MaxCount) || (turnIn && int32(ownedCount) >= pProto->MaxCount))
return true;
// allows custom amount drop when not 0
if (qinfo->ItemDropQuantity[j])
{
if ((ownedCount < qinfo->ItemDropQuantity[j]) || (turnIn && ownedCount >= qinfo->ItemDropQuantity[j]))
return true;
}
else if (ownedCount < pProto->GetMaxStackSize())
return true;
}
}
}
}
return false;
}
void Player::SendQuestComplete(uint32 quest_id)
{
if (quest_id)
{
WorldPacket data(SMSG_QUESTUPDATE_COMPLETE, 4);
data << uint32(quest_id);
SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_COMPLETE quest = {}", quest_id);
}
}
void Player::SendQuestReward(Quest const* quest, uint32 XP)
{
uint32 questid = quest->GetQuestId();
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_COMPLETE quest = {}", questid);
sGameEventMgr->HandleQuestComplete(questid);
WorldPacket data(SMSG_QUESTGIVER_QUEST_COMPLETE, (4 + 4 + 4 + 4 + 4));
data << uint32(questid);
if (!IsMaxLevel())
{
data << uint32(XP);
data << uint32(quest->GetRewOrReqMoney(GetLevel()));
}
else
{
data << uint32(0);
data << uint32(quest->GetRewOrReqMoney(GetLevel()) + quest->GetRewMoneyMaxLevel());
}
data << uint32(10 * quest->CalculateHonorGain(GetQuestLevel(quest)));
data << uint32(quest->GetBonusTalents()); // bonus talents
data << uint32(quest->GetRewArenaPoints());
SendDirectMessage(&data);
}
void Player::SendQuestFailed(uint32 questId, InventoryResult reason)
{
if (questId)
{
WorldPacket data(SMSG_QUESTGIVER_QUEST_FAILED, 4 + 4);
data << uint32(questId);
data << uint32(reason); // failed reason (valid reasons: 4, 16, 50, 17, 74, other values show default message)
SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_FAILED");
}
}
void Player::SendQuestTimerFailed(uint32 quest_id)
{
if (quest_id)
{
WorldPacket data(SMSG_QUESTUPDATE_FAILEDTIMER, 4);
data << uint32(quest_id);
SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_FAILEDTIMER");
}
}
void Player::SendCanTakeQuestResponse(uint32 msg) const
{
WorldPacket data(SMSG_QUESTGIVER_QUEST_INVALID, 4);
data << uint32(msg);
SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_INVALID");
}
void Player::SendQuestConfirmAccept(const Quest* quest, Player* pReceiver)
{
if (pReceiver)
{
//load locale from db
std::string strTitle = quest->GetTitle();
int loc_idx = pReceiver->GetSession()->GetSessionDbLocaleIndex();
if (loc_idx >= 0)
if (const QuestLocale* pLocale = sObjectMgr->GetQuestLocale(quest->GetQuestId()))
ObjectMgr::GetLocaleString(pLocale->Title, loc_idx, strTitle);
WorldPacket data(SMSG_QUEST_CONFIRM_ACCEPT, (4 + quest->GetTitle().size() + 8));
data << uint32(quest->GetQuestId());
data << quest->GetTitle();
data << GetGUID();
pReceiver->SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUEST_CONFIRM_ACCEPT");
}
}
void Player::SendPushToPartyResponse(Player const* player, uint8 msg) const
{
if (player)
{
WorldPacket data(MSG_QUEST_PUSH_RESULT, (8 + 1));
data << player->GetGUID();
data << uint8(msg); // valid values: 0-8
SendDirectMessage(&data);
LOG_DEBUG("network", "WORLD: Sent MSG_QUEST_PUSH_RESULT");
}
}
void Player::SendQuestUpdateAddItem(Quest const* /*quest*/, uint32 /*item_idx*/, uint16 /*count*/)
{
WorldPacket data(SMSG_QUESTUPDATE_ADD_ITEM, 0);
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_ADD_ITEM");
//data << quest->RequiredItemId[item_idx];
//data << count;
SendDirectMessage(&data);
}
void Player::SendQuestUpdateAddCreatureOrGo(Quest const* quest, ObjectGuid guid, uint32 creatureOrGO_idx, uint16 old_count, uint16 add_count)
{
ASSERT(old_count + add_count < 65536 && "mob/GO count store in 16 bits 2^16 = 65536 (0..65536)");
int32 entry = quest->RequiredNpcOrGo[ creatureOrGO_idx ];
if (entry < 0)
// client expected gameobject template id in form (id|0x80000000)
entry = (-entry) | 0x80000000;
WorldPacket data(SMSG_QUESTUPDATE_ADD_KILL, (4 * 4 + 8));
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_ADD_KILL");
data << uint32(quest->GetQuestId());
data << uint32(entry);
data << uint32(old_count + add_count);
data << uint32(quest->RequiredNpcOrGoCount[ creatureOrGO_idx ]);
data << guid;
SendDirectMessage(&data);
uint16 log_slot = FindQuestSlot(quest->GetQuestId());
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, creatureOrGO_idx, GetQuestSlotCounter(log_slot, creatureOrGO_idx) + add_count);
}
void Player::SendQuestUpdateAddPlayer(Quest const* quest, uint16 old_count, uint16 add_count)
{
ASSERT(old_count + add_count < 65536 && "player count store in 16 bits");
WorldPacket data(SMSG_QUESTUPDATE_ADD_PVP_KILL, (3 * 4));
LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_ADD_PVP_KILL");
data << uint32(quest->GetQuestId());
data << uint32(old_count + add_count);
data << uint32(quest->GetPlayersSlain());
SendDirectMessage(&data);
uint16 log_slot = FindQuestSlot(quest->GetQuestId());
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, QUEST_PVP_KILL_SLOT, GetQuestSlotCounter(log_slot, QUEST_PVP_KILL_SLOT) + add_count);
}
bool Player::HasPvPForcingQuest() const
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = GetQuestSlotQuestId(i);
if (questId == 0)
continue;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
return true;
}
return false;
}