From a3dd0d75cfeed8eafd47091fb38473294dee4dbb Mon Sep 17 00:00:00 2001 From: Ovahlord Date: Tue, 11 Jul 2023 14:15:40 +0200 Subject: [PATCH] Core/Quests: ported quest flag and visbility update changes from master branch --- .../game/AI/SmartScripts/SmartScriptMgr.cpp | 4 +- .../game/Entities/Creature/GossipDef.cpp | 4 +- src/server/game/Entities/Player/Player.cpp | 159 +++++++++++------- src/server/game/Entities/Player/Player.h | 3 +- src/server/game/Globals/ObjectMgr.cpp | 59 ++----- src/server/game/Handlers/QuestHandler.cpp | 145 ++++++---------- src/server/game/Miscellaneous/SharedDefines.h | 38 +++-- src/server/game/Phasing/PhasingHandler.cpp | 5 +- src/server/game/Phasing/PhasingHandler.h | 2 +- src/server/game/Quests/QuestDef.cpp | 34 ++-- src/server/game/Quests/QuestDef.h | 84 ++++----- .../game/Server/Packets/QuestPackets.cpp | 4 +- src/server/game/Server/WorldSession.cpp | 2 + src/server/game/Spells/SpellEffects.cpp | 2 +- src/server/game/Spells/SpellInfo.cpp | 14 ++ src/server/game/Spells/SpellInfo.h | 1 + 16 files changed, 282 insertions(+), 278 deletions(-) diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 54091af37c..b49ad2a1a0 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -1205,9 +1205,9 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_CALL_GROUPEVENTHAPPENS: if (Quest const* qid = sObjectMgr->GetQuestTemplate(e.action.quest.quest)) { - if (!qid->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT)) + if (!qid->HasFlag(QUEST_FLAGS_COMPLETION_EVENT) && !qid->HasFlag(QUEST_FLAGS_COMPLETION_AREA_TRIGGER)) { - TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u SpecialFlags for Quest entry %u does not include FLAGS_EXPLORATION_OR_EVENT(2), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.quest.quest); + TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u Flags for Quest entry %u does not include QUEST_FLAGS_COMPLETION_EVENT or QUEST_FLAGS_COMPLETION_AREA_TRIGGER, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.quest.quest); return false; } } diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index 4f447a59ec..74e387f8a2 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -235,7 +235,7 @@ void PlayerMenu::SendGossipMenu(uint32 titleTextId, ObjectGuid objectGUID) text.QuestType = itr.QuestIcon; text.QuestFlags = quest->GetFlags(); text.QuestLevel = quest->GetQuestLevel(); - text.Repeatable = quest->IsAutoComplete() && quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly(); + text.Repeatable = quest->IsTurnIn() && quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly(); text.QuestTitle = quest->GetTitle(); LocaleConstant localeConstant = _session->GetSessionDbLocaleIndex(); @@ -373,7 +373,7 @@ void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string if (questLevelInTitle) Quest::AddQuestLevelToTitle(title, quest->GetQuestLevel()); - bool repeatable = quest->IsAutoComplete() && quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly(); + bool repeatable = quest->IsTurnIn() && quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly(); questList.QuestDataText.emplace_back(questID, questMenuItem.QuestIcon, quest->GetQuestLevel(), quest->GetFlags(), repeatable, title); } } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index ea251cfe3d..c0ed80ea48 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1342,6 +1342,9 @@ void Player::setDeathState(DeathState s) // passive spell if (!ressSpellId) ressSpellId = GetResurrectionSpellId(); + + FailQuestsWithFlag(QUEST_FLAGS_COMPLETION_NO_DEATH); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP, 1); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH, 1); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_IN_DUNGEON, 1); @@ -13705,9 +13708,9 @@ void Player::PrepareQuestMenu(ObjectGuid guid) if (!CanTakeQuest(quest, false)) continue; - if (quest->IsAutoComplete() && (!quest->IsRepeatable() || quest->IsDaily() || quest->IsWeekly() || quest->IsMonthly())) + if (quest->IsTurnIn() && (!quest->IsRepeatable() || quest->IsDaily() || quest->IsWeekly() || quest->IsMonthly())) qm.AddMenuItem(quest_id, 0); - else if (quest->IsAutoComplete()) + else if (quest->IsTurnIn()) qm.AddMenuItem(quest_id, 4); else if (GetQuestStatus(quest_id) == QUEST_STATUS_NONE) qm.AddMenuItem(quest_id, 2); @@ -13745,7 +13748,7 @@ void Player::SendPreparedQuest(ObjectGuid guid) if (quest->IsAutoAccept() && CanAddQuest(quest, true) && CanTakeQuest(quest, true)) AddQuestAndCheckCompletion(quest, object); - if (quest->IsAutoComplete() && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) + if (quest->IsTurnIn() && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) PlayerTalkClass->SendQuestGiverRequestItems(quest, guid, CanCompleteRepeatableQuest(quest), true); else { @@ -13808,48 +13811,30 @@ 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) const +Quest const* Player::GetNextQuest(Object const* questGiver, Quest const* quest) const { - QuestRelationResult quests; uint32 nextQuestID = quest->GetNextQuestInChain(); + if (!nextQuestID) + return nullptr; - switch (guid.GetHigh()) + if (questGiver == this) { - case HighGuid::Player: - ASSERT(quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE)); - return sObjectMgr->GetQuestTemplate(nextQuestID); - case HighGuid::Unit: - case HighGuid::Pet: - case HighGuid::Vehicle: - { - if (Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid)) - quests = sObjectMgr->GetCreatureQuestRelations(creature->GetEntry()); - else - return nullptr; - break; - } - case HighGuid::GameObject: - { - //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); - if (GameObject* gameObject = _map->GetGameObject(guid)) - quests = sObjectMgr->GetGOQuestRelations(gameObject->GetEntry()); - else - return nullptr; - break; - } - default: + if (!quest->HasFlag(QUEST_FLAGS_AUTO_COMPLETE)) return nullptr; + + return sObjectMgr->GetQuestTemplate(nextQuestID); } - // for unit and go state - if (nextQuestID) - if (quests.HasQuest(nextQuestID)) - return sObjectMgr->GetQuestTemplate(nextQuestID); + //we should obtain map pointer from GetMap() in 99% of cases. Special case + //only for quests which cast teleport spells on player + if (WorldObject const* worldObjectQuestGiver = dynamic_cast(questGiver)) + if (!IsInMap(worldObjectQuestGiver)) + return nullptr; - return nullptr; + if (questGiver && !questGiver->hasQuest(nextQuestID)) + return nullptr; + + return sObjectMgr->GetQuestTemplate(nextQuestID); } bool Player::CanSeeStartQuest(Quest const* quest) const @@ -13914,7 +13899,7 @@ bool Player::CanCompleteQuest(uint32 quest_id) return false; // not allow re-complete quest // auto complete quest - if (qInfo->IsAutoComplete() && CanTakeQuest(qInfo, false)) + if (qInfo->IsTurnIn() && CanTakeQuest(qInfo, false)) return true; QuestStatusMap::iterator itr = m_QuestStatus.find(quest_id); @@ -13934,7 +13919,7 @@ bool Player::CanCompleteQuest(uint32 quest_id) } } - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO)) + if (qInfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO))) { for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++) { @@ -13950,7 +13935,7 @@ bool Player::CanCompleteQuest(uint32 quest_id) if (qInfo->GetPlayersSlain() != 0 && q_status.PlayerCount < qInfo->GetPlayersSlain()) return false; - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT) && !q_status.Explored) + if ((qInfo->HasFlag(QUEST_FLAGS_COMPLETION_EVENT) || qInfo->HasFlag(QUEST_FLAGS_COMPLETION_AREA_TRIGGER)) && !q_status.Explored) return false; if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED) && q_status.Timer == 0) @@ -13994,7 +13979,7 @@ bool Player::CanCompleteRepeatableQuest(Quest const* quest) 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() && GetQuestStatus(quest->GetQuestId()) != QUEST_STATUS_COMPLETE) + if (!quest->IsDFQuest() && !quest->IsTurnIn() && GetQuestStatus(quest->GetQuestId()) != QUEST_STATUS_COMPLETE) return false; // daily quest can't be rewarded (25 daily quest already completed) @@ -14156,7 +14141,7 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) questStatusData.ItemCount[i] = 0; } - if (quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO)) + if (quest->HasSpecialFlag(QuestSpecialFlags(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; @@ -14198,6 +14183,17 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) UpdatePvPState(); } + if (quest->GetSrcSpell() > 0) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetSrcSpell()); + Unit* caster = this; + if (questGiver && questGiver->isType(TYPEMASK_UNIT) && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ACCEPT) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER) && !spellInfo->HasTargetType(TARGET_DEST_CASTER_SUMMON)) + if (Unit* unit = questGiver->ToUnit()) + caster = unit; + + caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK)); + } + SetQuestSlot(log_slot, quest_id, qtime); m_QuestStatusSave[quest_id] = QUEST_DEFAULT_SAVE_TYPE; @@ -14235,7 +14231,7 @@ void Player::CompleteQuest(uint32 quest_id) SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE); if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id)) - if (qInfo->HasFlag(QUEST_FLAGS_TRACKING)) + if (qInfo->HasFlag(QUEST_FLAGS_TRACKING_EVENT)) RewardQuest(qInfo, 0, this, false); } @@ -14273,14 +14269,16 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) { - if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->RequiredItemId[i])) + if (quest->RequiredItemId[i]) { - if (quest->RequiredItemCount[i] > 0 && itemTemplate->GetBonding() == BIND_QUEST && !quest->IsRepeatable() && !HasQuestForItem(quest->RequiredItemId[i], quest_id, true)) - DestroyItemCount(quest->RequiredItemId[i], 9999, true); - else - DestroyItemCount(quest->RequiredItemId[i], quest->RequiredItemCount[i], true); + uint32 amountToDestroy = quest->RequiredItemCount[i]; + if (quest->HasFlag(QUEST_FLAGS_REMOVE_SURPLUS_ITEMS)) + amountToDestroy = std::numeric_limits::max(); + + DestroyItemCount(quest->RequiredItemId[i], amountToDestroy, true); } } + for (uint8 i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i) if (quest->RequiredCurrencyId[i]) ModifyCurrency(quest->RequiredCurrencyId[i], -int32(quest->RequiredCurrencyCount[i])); @@ -14445,7 +14443,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (quest->GetRewSpellCast() > 0) { SpellInfo const* spellInfo = ASSERT_NOTNULL(sSpellMgr->GetSpellInfo(quest->GetRewSpellCast())); - if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ON_COMPLETE)) + if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_COMPLETE)) { if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) creature->CastSpell(this, quest->GetRewSpellCast(), true); @@ -14456,7 +14454,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) && !spellInfo->IsSelfCast() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ON_COMPLETE)) + if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_COMPLETE)) { if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) creature->CastSpell(this, quest->GetRewSpell(), true); @@ -14481,13 +14479,43 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, SendQuestUpdate(quest_id); + bool updateVisibility = false; + if (quest->HasFlag(QUEST_FLAGS_UPDATE_PHASESHIFT)) + updateVisibility = PhasingHandler::OnConditionChange(this, false); + //Update QuestGivers SendQuestGiverStatusMultiple(); //lets remove flag for delayed teleports SetCanDelayTeleport(false); + if (questGiver && questGiver->isType(TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT)) + { + //For AutoSubmition was added plr case there as it almost same exclute AI script cases. + // Send next quest + if (Quest const* nextQuest = GetNextQuest(questGiver, quest)) + { + // Only send the quest to the player if the conditions are met + if (CanTakeQuest(nextQuest, false)) + { + if (nextQuest->IsAutoAccept() && CanAddQuest(nextQuest, true)) + AddQuestAndCheckCompletion(nextQuest, questGiver); + + PlayerTalkClass->SendQuestGiverQuestDetails(nextQuest, questGiver->GetGUID(), true, false); + } + } + + PlayerTalkClass->ClearMenus(); + if (Creature* creatureQGiver = questGiver->ToCreature()) + creatureQGiver->AI()->QuestReward(this, quest, reward); + else if (GameObject* goQGiver = questGiver->ToGameObject()) + goQGiver->AI()->QuestReward(this, quest, reward); + } + sScriptMgr->OnQuestStatusChange(this, quest_id); + + if (updateVisibility) + UpdateObjectVisibility(); } void Player::SetRewardedQuest(uint32 quest_id) @@ -14545,6 +14573,21 @@ void Player::FailQuest(uint32 questId) } } + +void Player::FailQuestsWithFlag(QuestFlags flag) +{ + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = GetQuestSlotQuestId(slot); + if (!questId) + continue; + + if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId)) + if (quest->HasFlag(flag)) + FailQuest(questId); + } +} + void Player::AbandonQuest(uint32 questId) { if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId)) @@ -15133,7 +15176,7 @@ void Player::SetQuestStatus(uint32 questId, QuestStatus status, bool update /*= { m_QuestStatus[questId].Status = status; - if (!quest->IsAutoComplete()) + if (!quest->IsTurnIn()) m_QuestStatusSave[questId] = QUEST_DEFAULT_SAVE_TYPE; } @@ -15230,7 +15273,6 @@ void Player::SendQuestUpdate(uint32 questId) } UpdateVisibleGameobjectsOrSpellClicks(); - PhasingHandler::OnConditionChange(this); } bool Player::SatisfyFirstLFGReward(uint32 dungeonId, uint8 maxRewCount) const @@ -15294,7 +15336,7 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) else if (status == QUEST_STATUS_INCOMPLETE) result2 = DIALOG_STATUS_INCOMPLETE; - if (quest->IsAutoComplete() && CanTakeQuest(quest, false) && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) + if (quest->IsTurnIn() && CanTakeQuest(quest, false) && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) result2 = DIALOG_STATUS_REWARD_REP; if (result2 > result) @@ -15335,7 +15377,7 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) else result2 = DIALOG_STATUS_LOW_LEVEL_AVAILABLE; } - else if (quest->IsAutoComplete()) + else if (quest->IsTurnIn()) { if (isNotLowLevelQuest) result2 = DIALOG_STATUS_REWARD_REP; @@ -15804,7 +15846,7 @@ void Player::TalkedToCreature(uint32 entry, ObjectGuid guid) if (q_status.Status == QUEST_STATUS_INCOMPLETE) { - if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO)) + if (qInfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO))) { for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) { @@ -16022,9 +16064,10 @@ void Player::SendQuestReward(Quest const* quest, Creature const* questGiver, uin if (questGiver) { - if (questGiver->IsGossip() && quest->HasFlag(QUEST_FLAGS_LAUNCH_GOSSIP_COMPLETE)) - packet.LaunchGossip = true; - else if (quest->GetNextQuestInChain() && !quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE)) + if (questGiver->IsGossip()) + packet.LaunchGossip = quest->HasFlag(QUEST_FLAGS_LAUNCH_GOSSIP_COMPLETE); + + else if (quest->GetNextQuestInChain() && !quest->HasFlag(QUEST_FLAGS_AUTO_COMPLETE)) if (Quest const* rewardQuest = sObjectMgr->GetQuestTemplate(quest->GetNextQuestInChain())) packet.UseQuestReward = CanTakeQuest(rewardQuest, false); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 8e6741ab62..b39befd76f 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1331,7 +1331,7 @@ class TC_GAME_API Player : public Unit, public GridObject void PrepareQuestMenu(ObjectGuid guid); void SendPreparedQuest(ObjectGuid guid); bool IsActiveQuest(uint32 quest_id) const; - Quest const* GetNextQuest(ObjectGuid guid, Quest const* quest) const; + Quest const* GetNextQuest(Object const* questGiver, Quest const* quest) const; bool CanSeeStartQuest(Quest const* quest) const; bool CanTakeQuest(Quest const* quest, bool msg) const; bool CanAddQuest(Quest const* quest, bool msg) const; @@ -1347,6 +1347,7 @@ class TC_GAME_API Player : public Unit, public GridObject void RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, bool announce = true); void SetRewardedQuest(uint32 quest_id); void FailQuest(uint32 quest_id); + void FailQuestsWithFlag(QuestFlags flag); bool SatisfyQuestSkill(Quest const* qInfo, bool msg) const; bool SatisfyQuestLevel(Quest const* qInfo, bool msg) const; bool SatisfyQuestLog(bool msg) const; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index eb7666729a..a638e4fbdd 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -4119,8 +4119,8 @@ void ObjectMgr::LoadQuests() // additional quest integrity checks (GO, creature_template and item_template must be loaded already) - if (qinfo->GetQuestMethod() >= 3) - TC_LOG_ERROR("sql.sql", "Quest %u has `Method` = %u, expected values are 0, 1 or 2.", qinfo->GetQuestId(), qinfo->GetQuestMethod()); + if (qinfo->GetQuestType() >= MAX_DB_ALLOWED_QUEST_TYPES) + TC_LOG_ERROR("sql.sql", "Quest %u has `Method` = %u, expected values are 0, 1 or 2.", qinfo->GetQuestId(), qinfo->GetQuestType()); if (qinfo->_specialFlags & ~QUEST_SPECIAL_FLAGS_DB_ALLOWED) { @@ -4162,7 +4162,7 @@ void ObjectMgr::LoadQuests() } } - if (qinfo->_flags & QUEST_FLAGS_TRACKING) + if (qinfo->_flags & QUEST_FLAGS_TRACKING_EVENT) { // at auto-reward can be rewarded only RewardChoiceItemId[0] for (uint32 j = 1; j < QUEST_REWARD_CHOICES_COUNT; ++j) @@ -4445,7 +4445,7 @@ void ObjectMgr::LoadQuests() { // In fact SpeakTo and Kill are quite same: either you can speak to mob:SpeakTo or you can't:Kill/Cast - qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO); + qinfo->SetSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO)); if (!qinfo->RequiredNpcOrGoCount[j]) { @@ -4784,7 +4784,8 @@ void ObjectMgr::LoadQuests() 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)) + if (!qinfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO)) && + !qinfo->HasFlag(QUEST_FLAGS_COMPLETION_AREA_TRIGGER | QUEST_FLAGS_COMPLETION_EVENT)) { bool addFlag = true; if (qinfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER)) @@ -4836,35 +4837,7 @@ void ObjectMgr::LoadQuests() } } - // check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE - for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i) - { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(i); - if (!spellInfo) - continue; - - for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) - { - if (spellInfo->Effects[j].Effect != SPELL_EFFECT_QUEST_COMPLETE) - continue; - - uint32 quest_id = spellInfo->Effects[j].MiscValue; - - Quest const* quest = GetQuestTemplate(quest_id); - - // some quest referenced in spells not exist (outdated spells) - if (!quest) - continue; - - if (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT)) - { - TC_LOG_ERROR("sql.sql", "Spell (id: %u) have SPELL_EFFECT_QUEST_COMPLETE for quest %u, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. Quest flags must be fixed, quest modified to enable objective.", spellInfo->Id, quest_id); - - // this will prevent quest completing without objective - const_cast(quest)->SetSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT); - } - } - } + // don't check spells with SPELL_EFFECT_QUEST_COMPLETE, a lot of invalid db2 data TC_LOG_INFO("server.loading", ">> Loaded %lu quests definitions in %u ms", (unsigned long)_questTemplates.size(), GetMSTimeDiffToNow(oldMSTime)); } @@ -5033,15 +5006,11 @@ void ObjectMgr::LoadScripts(ScriptsType type) continue; } - if (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT)) + if (!quest->HasFlag(QUEST_FLAGS_COMPLETION_EVENT) && !quest->HasFlag(QUEST_FLAGS_COMPLETION_AREA_TRIGGER)) { TC_LOG_ERROR("sql.sql", "Table `%s` has quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT in quest flags. Script command or quest flags wrong. Quest modified to require objective.", tableName.c_str(), tmp.QuestExplored.QuestID, tmp.id); - - // this will prevent quest completing without objective - const_cast(quest)->SetSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT); - - // continue; - quest objective requirement set and command can be allowed + continue; } if (float(tmp.QuestExplored.Distance) > DEFAULT_VISIBILITY_DISTANCE) @@ -6007,14 +5976,10 @@ void ObjectMgr::LoadQuestAreaTriggers() continue; } - if (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT)) + if (!quest->HasFlag(QUEST_FLAGS_COMPLETION_AREA_TRIGGER)) { - TC_LOG_ERROR("sql.sql", "Table `areatrigger_involvedrelation` has record (id: %u) for not quest %u, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. Trigger or quest flags must be fixed, quest modified to require objective.", trigger_ID, quest_ID); - - // this will prevent quest completing without objective - const_cast(quest)->SetSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT); - - // continue; - quest modified to required objective and trigger can be allowed. + TC_LOG_ERROR("sql.sql", "Table `areatrigger_involvedrelation` has record (id: %u) for not quest %u, but quest not have flag QUEST_FLAGS_COMPLETION_AREA_TRIGGER. Trigger or quest flags must be fixed, quest modified to require objective.", trigger_ID, quest_ID); + continue; } _questAreaTriggerStore[trigger_ID] = quest_ID; diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp index c6a65716f0..4cc00d61ed 100644 --- a/src/server/game/Handlers/QuestHandler.cpp +++ b/src/server/game/Handlers/QuestHandler.cpp @@ -196,7 +196,7 @@ void WorldSession::HandleQuestgiverAcceptQuestOpcode(WorldPackets::Quest::QuestG { _player->AddQuestAndCheckCompletion(quest, object); - if (quest->HasFlag(QUEST_FLAGS_PARTY_ACCEPT)) + if (quest->IsPushedToPartyOnAccept()) { if (Group* group = _player->GetGroup()) { @@ -222,6 +222,21 @@ void WorldSession::HandleQuestgiverAcceptQuestOpcode(WorldPackets::Quest::QuestG _player->PlayerTalkClass->SendCloseGossip(); + if (quest->HasFlag(QUEST_FLAGS_LAUNCH_GOSSIP_ACCEPT)) + { + auto launchGossip = [&](WorldObject* worldObject) + { + _player->PlayerTalkClass->ClearMenus(); + _player->PrepareGossipMenu(worldObject, _player->GetDefaultGossipMenuForSource(worldObject), true); + _player->SendPreparedGossip(worldObject); + }; + + if (Creature* creature = object->ToCreature()) + launchGossip(creature); + else if (GameObject* go = object->ToGameObject()) + launchGossip(go); + } + if (quest->GetSrcSpell() > 0) _player->CastSpell(_player, quest->GetSrcSpell(), true); @@ -258,7 +273,7 @@ void WorldSession::HandleQuestgiverQueryQuestOpcode(WorldPackets::Quest::QuestGi if (quest->IsAutoAccept() && _player->CanAddQuest(quest, true)) _player->AddQuestAndCheckCompletion(quest, object); - if (quest->IsAutoComplete()) + if (quest->IsTurnIn()) _player->PlayerTalkClass->SendQuestGiverRequestItems(quest, object->GetGUID(), _player->CanCompleteQuest(quest->GetQuestId()), true); else { @@ -295,7 +310,7 @@ void WorldSession::HandleQuestgiverChooseRewardOpcode(WorldPackets::Quest::Quest Object* object = _player; - if (!quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE)) + if (!quest->HasFlag(QUEST_FLAGS_AUTO_COMPLETE)) { object = ObjectAccessor::GetObjectByTypeMask(*_player, packet.QuestGiverGUID, TYPEMASK_UNIT|TYPEMASK_GAMEOBJECT); if (!object || !object->hasInvolvedQuest(packet.QuestID)) @@ -307,70 +322,17 @@ void WorldSession::HandleQuestgiverChooseRewardOpcode(WorldPackets::Quest::Quest } if ((!_player->CanSeeStartQuest(quest) && _player->GetQuestStatus(packet.QuestID) == QUEST_STATUS_NONE) || - (_player->GetQuestStatus(packet.QuestID) != QUEST_STATUS_COMPLETE && !quest->IsAutoComplete())) + (_player->GetQuestStatus(packet.QuestID) != QUEST_STATUS_COMPLETE && !quest->IsTurnIn())) { TC_LOG_ERROR("entities.player.cheat", "Error in QUEST_STATUS_COMPLETE: player %s (guid %u) tried to complete quest %u, but is not allowed to do so (possible packet-hacking or high latency)", _player->GetName().c_str(), _player->GetGUID().GetCounter(), packet.QuestID); return; } - if (_player->CanRewardQuest(quest, packet.ItemChoiceID, true)) + if (_player->CanRewardQuest(quest, true)) { - _player->RewardQuest(quest, packet.ItemChoiceID, object); - - switch (object->GetTypeId()) - { - case TYPEID_UNIT: - case TYPEID_PLAYER: - { - // Send next quest - if (Quest const* nextQuest = _player->GetNextQuest(packet.QuestGiverGUID, quest)) - { - // Only send the quest to the player if the conditions are met - if (_player->CanTakeQuest(nextQuest, false)) - { - if (nextQuest->IsAutoAccept() && _player->CanAddQuest(nextQuest, true)) - _player->AddQuestAndCheckCompletion(nextQuest, object); - - // Previous quest has been rewarded via popup. The player may now accept the follow quest from himself. - if (object->IsPlayer() && quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE) && !nextQuest->IsAutoAccept()) - _player->SetPopupQuestId(nextQuest->GetQuestId()); - else - _player->SetPopupQuestId(0); - - _player->PlayerTalkClass->SendQuestGiverQuestDetails(nextQuest, packet.QuestGiverGUID, true, false); - } - } - - _player->PlayerTalkClass->ClearMenus(); - if (Creature* creatureQGiver = object->ToCreature()) - creatureQGiver->AI()->QuestReward(_player, quest, packet.ItemChoiceID); - break; - } - case TYPEID_GAMEOBJECT: - { - // Send next quest - if (Quest const* nextQuest = _player->GetNextQuest(packet.QuestGiverGUID, quest)) - { - // Only send the quest to the player if the conditions are met - if (_player->CanTakeQuest(nextQuest, false)) - { - if (nextQuest->IsAutoAccept() && _player->CanAddQuest(nextQuest, true)) - _player->AddQuestAndCheckCompletion(nextQuest, object); - - _player->SetPopupQuestId(0); - _player->PlayerTalkClass->SendQuestGiverQuestDetails(nextQuest, packet.QuestGiverGUID, true, false); - } - } - - _player->PlayerTalkClass->ClearMenus(); - if (GameObject* questGiver = object->ToGameObject()) - questGiver->AI()->QuestReward(_player, quest, packet.ItemChoiceID); - break; - } - default: - break; - } + if (_player->CanRewardQuest(quest, packet.ItemChoiceID, true)) + _player->RewardQuest(quest, packet.ItemChoiceID, object); } else _player->PlayerTalkClass->SendQuestGiverOfferReward(quest, packet.QuestGiverGUID, true); @@ -473,49 +435,48 @@ void WorldSession::HandleQuestConfirmAccept(WorldPackets::Quest::QuestConfirmAcc { TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUEST_CONFIRM_ACCEPT QuestID = %u", packet.QuestID); - if (Quest const* quest = sObjectMgr->GetQuestTemplate(packet.QuestID)) - { - if (!quest->HasFlag(QUEST_FLAGS_PARTY_ACCEPT)) - return; - - Player* originalPlayer = ObjectAccessor::FindPlayer(_player->GetPlayerSharingQuest()); - if (!originalPlayer) - return; - - if (!_player->IsInSameRaidWith(originalPlayer)) - return; - - if (!originalPlayer->IsActiveQuest(packet.QuestID)) - return; - - if (!_player->CanTakeQuest(quest, true)) - return; - - if (_player->CanAddQuest(quest, true)) - { - _player->AddQuestAndCheckCompletion(quest, nullptr); // nullptr, this prevent DB script from duplicate running - - if (quest->GetSrcSpell() > 0) - _player->CastSpell(_player, quest->GetSrcSpell(), true); - } - } + if (_player->GetSharedQuestID() != uint32(packet.QuestID)) + return; _player->ClearQuestSharingInfo(); + Quest const* quest = sObjectMgr->GetQuestTemplate(packet.QuestID); + if (!quest) + return; + + Player* originalPlayer = ObjectAccessor::FindPlayer(_player->GetPlayerSharingQuest()); + if (!originalPlayer) + return; + + if (!_player->IsInSameRaidWith(originalPlayer)) + return; + + if (!originalPlayer->IsActiveQuest(packet.QuestID)) + return; + + if (!_player->CanTakeQuest(quest, true)) + return; + + if (!_player->CanAddQuest(quest, true)) + return; + + _player->AddQuestAndCheckCompletion(quest, nullptr); // NULL, this prevent DB script from duplicate running + + if (quest->GetSrcSpell() > 0) + _player->CastSpell(_player, quest->GetSrcSpell(), true); } void WorldSession::HandleQuestgiverCompleteQuest(WorldPackets::Quest::QuestGiverCompleteQuest& packet) { + bool autoCompleteMode = packet.FromScript; // 0 - standart complete quest mode with npc, 1 - auto-complete mode + TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_COMPLETE_QUEST npc = %s, questId = %u self-complete: %u", packet.QuestGiverGUID.ToString().c_str(), packet.QuestID, packet.FromScript ? 1 : 0); Quest const* quest = sObjectMgr->GetQuestTemplate(packet.QuestID); if (!quest) return; - if (packet.FromScript && !quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE)) - return; - Object* object = nullptr; - if (packet.FromScript) + if (autoCompleteMode) object = _player; else object = ObjectAccessor::GetObjectByTypeMask(*_player, packet.QuestGiverGUID, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT); @@ -523,7 +484,7 @@ void WorldSession::HandleQuestgiverCompleteQuest(WorldPackets::Quest::QuestGiver if (!object) return; - if (!packet.FromScript) + if (!quest->HasFlag(QUEST_FLAGS_AUTO_COMPLETE)) { if (!object->hasInvolvedQuest(packet.QuestID)) return; @@ -641,7 +602,7 @@ void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket) if (quest->IsAutoAccept() && receiver->CanAddQuest(quest, true) && receiver->CanTakeQuest(quest, true)) receiver->AddQuestAndCheckCompletion(quest, sender); - if (quest->IsAutoComplete() && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) + if (quest->IsTurnIn() && quest->IsRepeatable() && !quest->IsDailyOrWeekly()) receiver->PlayerTalkClass->SendQuestGiverRequestItems(quest, sender->GetGUID(), receiver->CanCompleteRepeatableQuest(quest), true); else { diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 0e03583ad2..121b23d98a 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -3220,20 +3220,32 @@ enum HolidayIds : uint32 HOLIDAY_ANNIVERSARY_8_YEARS = 484 }; -// values based at QuestInfo.dbc -enum QuestTypes +enum QuestType { - QUEST_TYPE_ELITE = 1, - QUEST_TYPE_LIFE = 21, - QUEST_TYPE_PVP = 41, - QUEST_TYPE_RAID = 62, - QUEST_TYPE_DUNGEON = 81, - QUEST_TYPE_WORLD_EVENT = 82, - QUEST_TYPE_LEGENDARY = 83, - QUEST_TYPE_ESCORT = 84, - QUEST_TYPE_HEROIC = 85, - QUEST_TYPE_RAID_10 = 88, - QUEST_TYPE_RAID_25 = 89 + QUEST_TYPE_TURNIN = 0, + QUEST_TYPE_WITH_MAX_LEVEL = 1, + QUEST_TYPE_NORMAL = 2, + MAX_DB_ALLOWED_QUEST_TYPES = 3, + + // values used in quest menu packets + QUEST_TYPE_IN_PROGRESS = 4, + QUEST_TYPE_TASK_IN_PROGRESS = 5 +}; + +// values based at QuestInfo.dbc +enum QuestInfo +{ + QUEST_INFO_GROUP = 1, + QUEST_INFO_CLASS = 21, + QUEST_INFO_PVP = 41, + QUEST_INFO_RAID = 62, + QUEST_INFO_DUNGEON = 81, + QUEST_INFO_WORLD_EVENT = 82, + QUEST_INFO_LEGENDARY = 83, + QUEST_INFO_ESCORT = 84, + QUEST_INFO_HEROIC = 85, + QUEST_INFO_RAID_10 = 88, + QUEST_INFO_RAID_25 = 89 }; // values based at QuestSort.dbc diff --git a/src/server/game/Phasing/PhasingHandler.cpp b/src/server/game/Phasing/PhasingHandler.cpp index 27ac997e36..84bf8db57f 100644 --- a/src/server/game/Phasing/PhasingHandler.cpp +++ b/src/server/game/Phasing/PhasingHandler.cpp @@ -285,7 +285,7 @@ void PhasingHandler::OnAreaChange(WorldObject* object) UpdateVisibilityIfNeeded(object, true, changed); } -void PhasingHandler::OnConditionChange(WorldObject* object) +bool PhasingHandler::OnConditionChange(WorldObject* object, bool updateVisibility /*= true*/) { PhaseShift& phaseShift = object->GetPhaseShift(); PhaseShift& suppressedPhaseShift = object->GetSuppressedPhaseShift(); @@ -393,7 +393,8 @@ void PhasingHandler::OnConditionChange(WorldObject* object) unit->RemoveNotOwnSingleTargetAuras(true); } - UpdateVisibilityIfNeeded(object, true, changed); + UpdateVisibilityIfNeeded(object, updateVisibility, changed); + return changed; } void PhasingHandler::SendToPlayer(Player const* player, PhaseShift const& phaseShift) diff --git a/src/server/game/Phasing/PhasingHandler.h b/src/server/game/Phasing/PhasingHandler.h index a887848186..7603d15bc8 100644 --- a/src/server/game/Phasing/PhasingHandler.h +++ b/src/server/game/Phasing/PhasingHandler.h @@ -51,7 +51,7 @@ public: static void OnMapChange(WorldObject* object); static void OnAreaChange(WorldObject* object); - static void OnConditionChange(WorldObject* object); + static bool OnConditionChange(WorldObject* object, bool updateVisibility = true); static void SendToPlayer(Player const* player, PhaseShift const& phaseShift); static void SendToPlayer(Player const* player); diff --git a/src/server/game/Quests/QuestDef.cpp b/src/server/game/Quests/QuestDef.cpp index 091c98f381..334f0c28f9 100644 --- a/src/server/game/Quests/QuestDef.cpp +++ b/src/server/game/Quests/QuestDef.cpp @@ -29,11 +29,11 @@ Quest::Quest(Field* questRecord) { _id = questRecord[0].GetUInt32(); - _method = questRecord[1].GetUInt8(); + _type = questRecord[1].GetUInt8(); _level = questRecord[2].GetInt16(); _minLevel = questRecord[3].GetInt16(); _zoneOrSort = questRecord[4].GetInt16(); - _type = questRecord[5].GetUInt16(); + _questInfoId = questRecord[5].GetUInt16(); _suggestedPlayers = questRecord[6].GetUInt8(); _requiredFactionId1 = questRecord[7].GetUInt16(); _requiredFactionId2 = questRecord[8].GetUInt16(); @@ -339,7 +339,7 @@ void Quest::BuildQuestRewards(WorldPackets::Quest::QuestRewards& rewards, Player uint32 Quest::GetRewMoneyMaxLevel() const { // If Quest has flag to not give money on max level, it's 0 - if (HasFlag(QUEST_FLAGS_NO_MONEY_FROM_XP)) + if (HasFlag(QUEST_FLAGS_NO_MONEY_FOR_XP)) return 0; // Else, return the rewarded copper sum modified by the rate @@ -351,26 +351,26 @@ bool Quest::IsAutoAccept() const return !sWorld->getBoolConfig(CONFIG_QUEST_IGNORE_AUTO_ACCEPT) && HasFlag(QUEST_FLAGS_AUTO_ACCEPT); } -bool Quest::IsAutoComplete() const +bool Quest::IsTurnIn() const { - return !sWorld->getBoolConfig(CONFIG_QUEST_IGNORE_AUTO_COMPLETE) && _method == 0; + return !sWorld->getBoolConfig(CONFIG_QUEST_IGNORE_AUTO_COMPLETE) && _type == QUEST_TYPE_TURNIN; } bool Quest::IsRaidQuest(Difficulty difficulty) const { - switch (_type) + switch (_questInfoId) { - case QUEST_TYPE_RAID: - return true; - case QUEST_TYPE_RAID_10: - return !(difficulty & RAID_DIFFICULTY_MASK_25MAN); - case QUEST_TYPE_RAID_25: - return difficulty & RAID_DIFFICULTY_MASK_25MAN; - default: - break; + case QUEST_INFO_RAID: + return true; + case QUEST_INFO_RAID_10: + return difficulty == RAID_DIFFICULTY_10MAN_NORMAL || difficulty == RAID_DIFFICULTY_10MAN_HEROIC; + case QUEST_INFO_RAID_25: + return difficulty == RAID_DIFFICULTY_25MAN_NORMAL || difficulty == RAID_DIFFICULTY_25MAN_HEROIC; + default: + break; } - if ((_flags & QUEST_FLAGS_RAID) != 0) + if ((_flags & QUEST_FLAGS_RAID_GROUP_OK) != 0) return true; return false; @@ -470,11 +470,11 @@ WorldPacket Quest::BuildQueryData(LocaleConstant loc) const ObjectMgr::GetLocaleString(localeData->ObjectiveText[i], loc, locQuestObjectiveText[i]); } - response.Info.QuestType = GetQuestMethod(); + response.Info.QuestType = GetQuestType(); response.Info.QuestLevel = GetQuestLevel(); response.Info.QuestMinLevel = GetMinLevel(); response.Info.QuestSortID = GetZoneOrSort(); - response.Info.QuestInfoID = GetType(); + response.Info.QuestInfoID = GetQuestInfoId(); response.Info.SuggestedGroupNum = GetSuggestedPlayers(); diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index fae62cf22e..f617430f8a 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -131,40 +131,40 @@ enum QuestGiverStatus }; -enum QuestFlags +enum QuestFlags : uint32 { QUEST_FLAGS_NONE = 0x00000000, - QUEST_FLAGS_STAY_ALIVE = 0x00000001, // Not used currently - QUEST_FLAGS_PARTY_ACCEPT = 0x00000002, // Not used currently. If player in party, all players that can accept this quest will receive confirmation box to accept quest CMSG_QUEST_CONFIRM_ACCEPT/SMSG_QUEST_CONFIRM_ACCEPT - QUEST_FLAGS_EXPLORATION = 0x00000004, // Not used currently + QUEST_FLAGS_COMPLETION_NO_DEATH = 0x00000001, + QUEST_FLAGS_COMPLETION_EVENT = 0x00000002, + QUEST_FLAGS_COMPLETION_AREA_TRIGGER = 0x00000004, QUEST_FLAGS_SHARABLE = 0x00000008, // Can be shared: Player::CanShareQuest() QUEST_FLAGS_HAS_CONDITION = 0x00000010, // Not used currently - QUEST_FLAGS_HIDE_REWARD_POI = 0x00000020, // Not used currently: Unsure of content - QUEST_FLAGS_RAID = 0x00000040, // Can be completed while in raid - QUEST_FLAGS_TBC = 0x00000080, // Not used currently: Available if TBC expansion enabled only - QUEST_FLAGS_NO_MONEY_FROM_XP = 0x00000100, // Not used currently: Experience is not converted to gold at max level - QUEST_FLAGS_HIDDEN_REWARDS = 0x00000200, // Items and money rewarded only sent in SMSG_QUESTGIVER_OFFER_REWARD (not in SMSG_QUESTGIVER_QUEST_DETAILS or in client quest log(SMSG_QUEST_QUERY_RESPONSE)) - QUEST_FLAGS_TRACKING = 0x00000400, // These quests are automatically rewarded on quest complete and they will never appear in quest log client side. + QUEST_FLAGS_HIDE_REWARD_POI = 0x00000020, // Hides questgiver turn-in minimap icon + QUEST_FLAGS_RAID_GROUP_OK = 0x00000040, // Can be completed while in raid + QUEST_FLAGS_EXPANSION_ONLY = 0x00000080, // Not used currently + QUEST_FLAGS_NO_MONEY_FOR_XP = 0x00000100, // Experience is not converted to gold at max level + QUEST_FLAGS_HIDE_REWARD = 0x00000200, // Items and money rewarded only sent in SMSG_QUESTGIVER_OFFER_REWARD (not in SMSG_QUEST_GIVER_QUEST_DETAILS or in client quest log(SMSG_QUEST_QUERY_RESPONSE)) + QUEST_FLAGS_TRACKING_EVENT = 0x00000400, // These quests are automatically rewarded on quest complete and they will never appear in quest log client side. QUEST_FLAGS_DEPRECATE_REPUTATION = 0x00000800, // Not used currently QUEST_FLAGS_DAILY = 0x00001000, // Used to know quest is Daily one QUEST_FLAGS_FLAGS_PVP = 0x00002000, // Having this quest in log forces PvP flag - QUEST_FLAGS_UNAVAILABLE = 0x00004000, // Used on quests that are not generically available + QUEST_FLAGS_DEPRECATED = 0x00004000, // Used on quests that are not generally available QUEST_FLAGS_WEEKLY = 0x00008000, - QUEST_FLAGS_AUTOCOMPLETE = 0x00010000, // Quests with this flag player submit automatically by special button in player gui + QUEST_FLAGS_AUTO_COMPLETE = 0x00010000, // Quests with this flag player submit automatically by special button in player gui QUEST_FLAGS_DISPLAY_ITEM_IN_TRACKER = 0x00020000, // Displays usable item in quest tracker - QUEST_FLAGS_OBJ_TEXT = 0x00040000, // use Objective text as Complete text + QUEST_FLAGS_DISABLE_COMPLETION_TEXT = 0x00040000, // use Objective text as Complete text QUEST_FLAGS_AUTO_ACCEPT = 0x00080000, // The client recognizes this flag as auto-accept. - QUEST_FLAGS_PLAYER_CAST_ON_ACCEPT = 0x00100000, - QUEST_FLAGS_PLAYER_CAST_ON_COMPLETE = 0x00200000, - QUEST_FLAGS_UPDATE_PHASE_SHIFT = 0x00400000, + QUEST_FLAGS_PLAYER_CAST_ACCEPT = 0x00100000, + QUEST_FLAGS_PLAYER_CAST_COMPLETE = 0x00200000, + QUEST_FLAGS_UPDATE_PHASESHIFT = 0x00400000, QUEST_FLAGS_SOR_WHITELIST = 0x00800000, QUEST_FLAGS_LAUNCH_GOSSIP_COMPLETE = 0x01000000, - QUEST_FLAGS_REMOVE_EXTRA_GET_ITEMS = 0x02000000, - QUEST_FLAGS_HIDE_UNTIL_DISCOVERED = 0x04000000, + QUEST_FLAGS_REMOVE_SURPLUS_ITEMS = 0x02000000, // Remove all items from inventory that have the same id as the objective, not just the amount required by quest + QUEST_FLAGS_WELL_KNOWN = 0x04000000, QUEST_FLAGS_PORTRAIT_IN_QUEST_LOG = 0x08000000, QUEST_FLAGS_SHOW_ITEM_WHEN_COMPLETED = 0x10000000, QUEST_FLAGS_LAUNCH_GOSSIP_ACCEPT = 0x20000000, - QUEST_FLAGS_ITEMS_GLOW_WHEN_DONE = 0x40000000, + QUEST_FLAGS_ITEMS_GLOW_WHEN_COMPLETE = 0x40000000, QUEST_FLAGS_FAIL_ON_LOGOUT = 0x80000000 }; @@ -173,14 +173,14 @@ enum QuestSpecialFlags QUEST_SPECIAL_FLAGS_NONE = 0x000, // Trinity flags for set SpecialFlags in DB if required but used only at server QUEST_SPECIAL_FLAGS_REPEATABLE = 0x001, // Set by 1 in SpecialFlags from DB - QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT = 0x002, // Set by 2 in SpecialFlags from DB (if required area explore, spell SPELL_EFFECT_QUEST_COMPLETE casting, table `FECT_QUEST_COMPLETE casting, table `*_script` command SCRIPT_COMMAND_QUEST_EXPLORED use, set from script) + QUEST_SPECIAL_FLAGS_AUTO_PUSH_TO_PARTY = 0x002, // Set by 2 in SpecialFlags from DB will make quest be pushed to entire party when one member accepts it QUEST_SPECIAL_FLAGS_AUTO_ACCEPT = 0x004, // Set by 4 in SpecialFlags in DB if the quest is to be auto-accepted. QUEST_SPECIAL_FLAGS_DF_QUEST = 0x008, // Set by 8 in SpecialFlags in DB if the quest is used by Dungeon Finder. QUEST_SPECIAL_FLAGS_MONTHLY = 0x010, // Set by 16 in SpecialFlags in DB if the quest is reset at the begining of the month QUEST_SPECIAL_FLAGS_CAST = 0x020, // Set by 32 in SpecialFlags in DB if the quest requires RequiredOrNpcGo killcredit but NOT kill (a spell cast) // room for more custom flags - QUEST_SPECIAL_FLAGS_DB_ALLOWED = QUEST_SPECIAL_FLAGS_REPEATABLE | QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT | QUEST_SPECIAL_FLAGS_AUTO_ACCEPT | QUEST_SPECIAL_FLAGS_DF_QUEST | QUEST_SPECIAL_FLAGS_MONTHLY | QUEST_SPECIAL_FLAGS_CAST, + QUEST_SPECIAL_FLAGS_DB_ALLOWED = QUEST_SPECIAL_FLAGS_REPEATABLE | QUEST_SPECIAL_FLAGS_AUTO_ACCEPT | QUEST_SPECIAL_FLAGS_DF_QUEST | QUEST_SPECIAL_FLAGS_MONTHLY | QUEST_SPECIAL_FLAGS_CAST, QUEST_SPECIAL_FLAGS_DELIVER = 0x080, // Internal flag computed only QUEST_SPECIAL_FLAGS_SPEAKTO = 0x100, // Internal flag computed only @@ -242,20 +242,23 @@ class TC_GAME_API Quest bool HasFlag(uint32 flag) const { return (_flags & flag) != 0; } void SetFlag(uint32 flag) { _flags |= flag; } - bool HasSpecialFlag(uint32 flag) const { return (_specialFlags & flag) != 0; } - void SetSpecialFlag(uint32 flag) { _specialFlags |= flag; } + bool HasSpecialFlag(QuestSpecialFlags flag) const { return (_specialFlags & flag) != 0; } + void SetSpecialFlag(QuestSpecialFlags flag) { _specialFlags |= flag; } + + // Possibly deprecated flag + bool IsUnavailable() const { return HasFlag(QUEST_FLAGS_DEPRECATED); } // whether the quest is globally enabled (spawned by pool, game event active etc.) static bool IsTakingQuestEnabled(uint32 questId); // table data accessors: uint32 GetQuestId() const { return _id; } - uint32 GetQuestMethod() const { return _method; } + uint32 GetQuestType() const { return _type; } int32 GetZoneOrSort() const { return _zoneOrSort; } uint32 GetMinLevel() const { return _minLevel; } uint32 GetMaxLevel() const { return _maxLevel; } int32 GetQuestLevel() const { return _level; } - uint32 GetType() const { return _type; } + uint32 GetQuestInfoId() const { return _questInfoId; } uint32 GetAllowableClasses() const { return _allowableClasses; } uint32 GetAllowableRaces() const { return _allowableRaces; } uint32 GetRequiredSkill() const { return _requiredSkillId; } @@ -312,9 +315,9 @@ class TC_GAME_API Quest uint32 GetSoundTurnIn() const { return _soundTurnIn; } uint32 GetIncompleteEmote() const { return _emoteOnIncomplete; } uint32 GetCompleteEmote() const { return _emoteOnComplete; } - bool IsRepeatable() const { return _specialFlags & QUEST_SPECIAL_FLAGS_REPEATABLE; } - bool IsAutoAccept() const; - bool IsAutoComplete() const; + bool IsRepeatable() const { return _specialFlags & QUEST_SPECIAL_FLAGS_REPEATABLE; } + bool IsAutoAccept() const; + bool IsTurnIn() const; uint32 GetFlags() const { return _flags; } uint32 GetSpecialFlags() const { return _specialFlags; } uint32 GetMinimapTargetMark() const { return _minimapTargetMark; } @@ -323,17 +326,18 @@ class TC_GAME_API Quest uint32 GetRewardReputationMask() const { return _rewardReputationMask; } uint32 GetQuestGiverPortrait() const { return _questGiverPortrait; } uint32 GetQuestTurnInPortrait() const { return _questTurnInPortrait; } - bool IsDaily() const { return (_flags & QUEST_FLAGS_DAILY) != 0; } - bool IsWeekly() const { return (_flags & QUEST_FLAGS_WEEKLY) != 0; } - bool IsMonthly() const { return (_specialFlags & QUEST_SPECIAL_FLAGS_MONTHLY) != 0; } - bool IsSeasonal() const { return (_zoneOrSort == -QUEST_SORT_SEASONAL || _zoneOrSort == -QUEST_SORT_SPECIAL || _zoneOrSort == -QUEST_SORT_LUNAR_FESTIVAL || _zoneOrSort == -QUEST_SORT_MIDSUMMER || _zoneOrSort == -QUEST_SORT_BREWFEST || _zoneOrSort == -QUEST_SORT_LOVE_IS_IN_THE_AIR || _zoneOrSort == -QUEST_SORT_NOBLEGARDEN) && !IsRepeatable(); } - bool IsDailyOrWeekly() const { return (_flags & (QUEST_FLAGS_DAILY | QUEST_FLAGS_WEEKLY)) != 0; } - bool IsRaidQuest(Difficulty difficulty) const; - bool IsAllowedInRaid(Difficulty difficulty) const; - bool IsDFQuest() const { return (_specialFlags & QUEST_SPECIAL_FLAGS_DF_QUEST) != 0; } + bool IsDaily() const { return (_flags & QUEST_FLAGS_DAILY) != 0; } + bool IsWeekly() const { return (_flags & QUEST_FLAGS_WEEKLY) != 0; } + bool IsMonthly() const { return (_specialFlags & QUEST_SPECIAL_FLAGS_MONTHLY) != 0; } + bool IsSeasonal() const { return (_zoneOrSort == -QUEST_SORT_SEASONAL || _zoneOrSort == -QUEST_SORT_SPECIAL || _zoneOrSort == -QUEST_SORT_LUNAR_FESTIVAL || _zoneOrSort == -QUEST_SORT_MIDSUMMER || _zoneOrSort == -QUEST_SORT_BREWFEST || _zoneOrSort == -QUEST_SORT_LOVE_IS_IN_THE_AIR || _zoneOrSort == -QUEST_SORT_NOBLEGARDEN) && !IsRepeatable(); } + bool IsDailyOrWeekly() const { return (_flags & (QUEST_FLAGS_DAILY | QUEST_FLAGS_WEEKLY)) != 0; } + bool IsRaidQuest(Difficulty difficulty) const; + bool IsAllowedInRaid(Difficulty difficulty) const; + bool IsDFQuest() const { return (_specialFlags & QUEST_SPECIAL_FLAGS_DF_QUEST) != 0; } + bool IsPushedToPartyOnAccept() const { return HasSpecialFlag(QUEST_SPECIAL_FLAGS_AUTO_PUSH_TO_PARTY); } uint32 CalculateHonorGain(uint8 level) const; - bool CanIncreaseRewardedQuestCounters() const; - bool IsStartAtAreaTrigger() const { return _startsAtAreaTrigger; } + bool CanIncreaseRewardedQuestCounters() const; + bool IsStartAtAreaTrigger() const { return _startsAtAreaTrigger; } // multiple values std::string ObjectiveText[QUEST_OBJECTIVES_COUNT]; @@ -395,11 +399,11 @@ class TC_GAME_API Quest // table data uint32 _id = 0; - uint32 _method = 0; + uint32 _type = 0; int32 _zoneOrSort = 0; uint32 _minLevel = 0; int32 _level = 0; - uint32 _type = 0; + uint32 _questInfoId = 0; uint32 _requiredFactionId1 = 0; int32 _requiredFactionValue1 = 0; uint32 _requiredFactionId2 = 0; diff --git a/src/server/game/Server/Packets/QuestPackets.cpp b/src/server/game/Server/Packets/QuestPackets.cpp index e29d232a70..6d1a763415 100644 --- a/src/server/game/Server/Packets/QuestPackets.cpp +++ b/src/server/game/Server/Packets/QuestPackets.cpp @@ -316,7 +316,7 @@ WorldPacket const* WorldPackets::Quest::QueryQuestInfoResponse::Write() _worldPacket << int32(Info.RewardNextQuest); _worldPacket << uint32(Info.RewardXPDifficulty); - if ((Info.Flags & QUEST_FLAGS_HIDDEN_REWARDS) != 0) + if ((Info.Flags & QUEST_FLAGS_HIDE_REWARD) != 0) _worldPacket << int32(0); else _worldPacket << int32(Info.RewardMoney); @@ -339,7 +339,7 @@ WorldPacket const* WorldPackets::Quest::QueryQuestInfoResponse::Write() _worldPacket << uint32(Info.PortraitGiver); _worldPacket << uint32(Info.PortraitTurnIn); - if ((Info.Flags & QUEST_FLAGS_HIDDEN_REWARDS) != 0) + if ((Info.Flags & QUEST_FLAGS_HIDE_REWARD) != 0) { for (uint8 i = 0; i < QUEST_REWARDS_COUNT; ++i) _worldPacket << int32(0) << uint32(0); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index b7cfa8983a..d076f5c2fa 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -609,6 +609,8 @@ void WorldSession::LogoutPlayer(bool save) ///- Clear whisper whitelist _player->ClearWhisperWhiteList(); + _player->FailQuestsWithFlag(QUEST_FLAGS_FAIL_ON_LOGOUT); + ///- empty buyback items and save the player in the database // some save parts only correctly work in case player present in map/player_lists (pets, etc) if (save) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 1aaee9abd2..1b3ce4635c 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -4314,7 +4314,7 @@ void Spell::EffectQuestComplete(SpellEffIndex effIndex) uint16 logSlot = player->FindQuestSlot(questId); if (logSlot < MAX_QUEST_LOG_SIZE) player->AreaExploredOrEventHappens(questId); - else if (quest->HasFlag(QUEST_FLAGS_TRACKING)) // Check if the quest is used as a serverside flag. + else if (quest->HasFlag(QUEST_FLAGS_TRACKING_EVENT)) // Check if the quest is used as a serverside flag. player->SetRewardedQuest(questId); // If so, set status to rewarded without broadcasting it to client. } } diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index d8f9178fd1..08d4809a55 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1103,6 +1103,20 @@ bool SpellInfo::HasOnlyDamageEffects() const return true; } +bool SpellInfo::HasTargetType(::Targets target) const +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!Effects[i].IsEffect()) + continue; + + if (Effects[i].TargetA.GetTarget() == target || Effects[i].TargetB.GetTarget() == target) + return true; + } + + return false; +} + bool SpellInfo::CanBeInterrupted(Unit const* interruptTarget, bool ignoreImmunity /*= false*/) const { return HasAttribute(SPELL_ATTR7_CAN_ALWAYS_BE_INTERRUPTED) diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 988a5c51ed..d74f9b5be8 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -474,6 +474,7 @@ class TC_GAME_API SpellInfo bool HasAura(AuraType aura) const; bool HasAreaAuraEffect() const; bool HasOnlyDamageEffects() const; + bool HasTargetType(::Targets target) const; inline bool HasAttribute(SpellAttr0 attribute) const { return !!(Attributes & attribute); } inline bool HasAttribute(SpellAttr1 attribute) const { return !!(AttributesEx & attribute); }