From e6432f8c97e4336066cbc13da938810ff3cd5de5 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Tue, 13 Jun 2023 08:52:22 -0400 Subject: [PATCH] Core/Quests: Quest flag fixups * Update flag names * Implemented QUEST_FLAGS_COMPLETION_NO_DEATH and QUEST_FLAGS_FAIL_ON_LOGOUT * Started using QUEST_FLAGS_COMPLETION_EVENT and QUEST_FLAGS_COMPLETION_AREA_TRIGGER instead of a custom SpeclalFlag * Renamed Quest::IsAutoComplete to Quest::IsTurnIn to better describe what it means (a quest that can be turned in without accepting it to quest log) * Implemented QUEST_FLAGS_UPDATE_PHASESHIFT and removed forced phaseshift updates on every quest status change * Implemented QUEST_FLAGS_LAUNCH_GOSSIP_ACCEPT - reopens gossip menu with questgiver Port From (https://github.com/TrinityCore/TrinityCore/commit/4d4c7e68935df9ca40bd5539d602ac4e779f53d5) --- Source/Framework/Constants/QuestConst.cs | 54 ++++--- Source/Game/AI/SmartScripts/SmartAIManager.cs | 4 +- Source/Game/Entities/Creature/Gossip.cs | 4 +- Source/Game/Entities/Player/Player.Quest.cs | 151 ++++++++++-------- Source/Game/Entities/Player/Player.cs | 2 + Source/Game/Globals/ObjectManager.cs | 68 ++------ Source/Game/Handlers/MiscHandler.cs | 3 +- Source/Game/Handlers/QuestHandler.cs | 74 +++++---- Source/Game/Quest/Quest.cs | 29 ++-- Source/Game/Server/WorldSession.cs | 2 + Source/Game/Spells/Spell.cs | 4 +- Source/Game/Spells/SpellEffects.cs | 2 +- 12 files changed, 205 insertions(+), 192 deletions(-) diff --git a/Source/Framework/Constants/QuestConst.cs b/Source/Framework/Constants/QuestConst.cs index 4b1499eb5..a9b92ca7f 100644 --- a/Source/Framework/Constants/QuestConst.cs +++ b/Source/Framework/Constants/QuestConst.cs @@ -74,11 +74,15 @@ namespace Framework.Constants public enum QuestType { - AutoComplete= 0, - Disabled = 1, + TurnIn= 0, + WithMaxLevel = 1, Normal = 2, Task = 3, - Max = 4 + MaxDBAllowedQuestTypes = 4, + + // values used in quest menu packets + InProgress = 4, + TaskInProgress = 5 } public enum QuestInfos @@ -428,37 +432,37 @@ namespace Framework.Constants public enum QuestFlags : uint { None = 0x00, - StayAlive = 0x01, // Not Used Currently - PartyAccept = 0x02, // 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 - Exploration = 0x04, // Not Used Currently + CompletionNoDeath = 0x01, + CompletionEvent = 0x02, + CompletionAreaTrigger = 0x04, Sharable = 0x08, // Can Be Shared: Player.Cansharequest() HasCondition = 0x10, // Not Used Currently - HideRewardPoi = 0x20, // Not Used Currently: Unsure Of Content - Raid = 0x40, // Can be completed while in raid + HideRewardPoi = 0x20, // Hides questgiver turn-in minimap icon + RaidGroupOk = 0x40, // Can be completed while in raid WarModeRewardsOptIn = 0x80, // Not Used Currently - NoMoneyFromXp = 0x100, // Not Used Currently: Experience Is Not Converted To Gold At Max Level - HiddenRewards = 0x200, // 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)) - Tracking = 0x400, // These Quests Are Automatically Rewarded On Quest Complete And They Will Never Appear In Quest Log Client Side. + NoMoneyForXp = 0x100, // Experience is not converted to gold at max level + HideReward = 0x200, // 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)) + TrackingEvent = 0x400, // These quests are automatically rewarded on quest complete and they will never appear in quest log client side. DeprecateReputation = 0x800, // Not Used Currently Daily = 0x1000, // Used To Know Quest Is Daily One Pvp = 0x2000, // Having This Quest In Log Forces Pvp Flag - Unavailable = 0x4000, // Used On Quests That Are Not Generically Available + Deprecated = 0x4000, // Used on quests that are not generally available Weekly = 0x8000, AutoComplete = 0x10000, // Quests with this flag player submit automatically by special button in player gui DisplayItemInTracker = 0x20000, // Displays Usable Item In Quest Tracker - ObjText = 0x40000, // Use Objective Text As Complete Text + DisableCompletionText = 0x40000, // Use Objective Text As Complete Text AutoAccept = 0x80000, // The client recognizes this flag as auto-accept. - PlayerCastOnAccept = 0x100000, - PlayerCastOnComplete = 0x200000, - UpdatePhaseShift = 0x400000, + PlayerCastAccept = 0x100000, + PlayerCastComplete = 0x200000, + UpdatePhaseshift = 0x400000, SorWhitelist = 0x800000, LaunchGossipComplete = 0x1000000, - RemoveExtraGetItems = 0x2000000, - HideUntilDiscovered = 0x4000000, + RemoveSurplusItems = 0x2000000, // Remove all items from inventory that have the same id as the objective, not just the amount required by quest + WellKnown = 0x04000000, PortraitInQuestLog = 0x8000000, ShowItemWhenCompleted = 0x10000000, LaunchGossipAccept = 0x20000000, - ItemsGlowWhenDone = 0x40000000, + ItemsGlowWhenComplete = 0x40000000, FailOnLogout = 0x80000000 } @@ -524,14 +528,14 @@ namespace Framework.Constants { None = 0x00, // Flags For Set Specialflags In Db If Required But Used Only At Server - Repeatable = 0x001, - ExplorationOrEvent = 0x002, // If Required Area Explore, Spell Spell_Effect_Quest_Complete Casting, Table `*_Script` Command Script_Command_Quest_Explored Use, Set From Script) - AutoAccept = 0x004, // Quest Is To Be Auto-Accepted. - DfQuest = 0x008, // Quest Is Used By Dungeon Finder. - Monthly = 0x010, // Quest Is Reset At The Begining Of The Month + Repeatable = 0x001, // Set by 1 in SpecialFlags from DB + AutoPushToParty = 0x002, // Set by 2 in SpecialFlags from DB will make quest be pushed to entire party when one member accepts it + AutoAccept = 0x004, // Set by 4 in SpecialFlags in DB if the quest is to be auto-accepted. + DfQuest = 0x008, // Set by 8 in SpecialFlags in DB if the quest is used by Dungeon Finder. + Monthly = 0x010, // Set by 16 in SpecialFlags in DB if the quest is reset at the begining of the month // Room For More Custom Flags - DbAllowed = Repeatable | ExplorationOrEvent | AutoAccept | DfQuest | Monthly, + DbAllowed = Repeatable | AutoAccept | DfQuest | Monthly, SequencedObjectives = 0x20 // Internal flag computed only } diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index a9d1cc0e1..16dac528e 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -1461,9 +1461,9 @@ namespace Game.AI Quest qid = Global.ObjectMgr.GetQuestTemplate(e.Action.quest.questId); if (qid != null) { - if (!qid.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + if (!qid.HasFlag(QuestFlags.CompletionEvent) && !qid.HasFlag(QuestFlags.CompletionAreaTrigger)) { - Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} SpecialFlags for Quest entry {e.Action.quest.questId} does not include FLAGS_EXPLORATION_OR_EVENT(2), skipped."); + Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} Flags for Quest entry {e.Action.quest.questId} does not include QUEST_FLAGS_COMPLETION_EVENT or QUEST_FLAGS_COMPLETION_AREA_TRIGGER, skipped."); return false; } } diff --git a/Source/Game/Entities/Creature/Gossip.cs b/Source/Game/Entities/Creature/Gossip.cs index e86b5a724..b12d04682 100644 --- a/Source/Game/Entities/Creature/Gossip.cs +++ b/Source/Game/Entities/Creature/Gossip.cs @@ -282,7 +282,7 @@ namespace Game.Misc gossipText.QuestType = item.QuestIcon; gossipText.QuestFlags = (uint)quest.Flags; gossipText.QuestFlagsEx = (uint)quest.FlagsEx; - gossipText.Repeatable = quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly(); + gossipText.Repeatable = quest.IsTurnIn() && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly(); gossipText.QuestTitle = quest.LogTitle; Locale locale = _session.GetSessionDbLocaleIndex(); @@ -374,7 +374,7 @@ namespace Game.Misc text.QuestType = questMenuItem.QuestIcon; text.QuestFlags = (uint)quest.Flags; text.QuestFlagsEx = (uint)quest.FlagsEx; - text.Repeatable = quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly(); + text.Repeatable = quest.IsTurnIn() && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly(); text.QuestTitle = quest.LogTitle; if (localeConstant != Locale.enUS) diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index e1e788143..3e6062f3a 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -310,9 +310,9 @@ namespace Game.Entities 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(questId, 0); - else if (quest.IsAutoComplete()) + else if (quest.IsTurnIn()) qm.AddMenuItem(questId, 4); else if (GetQuestStatus(questId) == QuestStatus.None) qm.AddMenuItem(questId, 2); @@ -347,9 +347,9 @@ namespace Game.Entities if (quest.IsAutoAccept() && CanAddQuest(quest, true) && CanTakeQuest(quest, true)) AddQuestAndCheckCompletion(quest, source); - if (quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly()) + if (quest.IsTurnIn() && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly()) PlayerTalkClass.SendQuestGiverRequestItems(quest, source.GetGUID(), CanCompleteRepeatableQuest(quest), true); - else if (quest.IsAutoComplete() && !quest.IsDailyOrWeekly() && !quest.IsMonthly()) + else if (quest.IsTurnIn() && !quest.IsDailyOrWeekly() && !quest.IsMonthly()) PlayerTalkClass.SendQuestGiverRequestItems(quest, source.GetGUID(), CanRewardQuest(quest, false), true); else PlayerTalkClass.SendQuestGiverQuestDetails(quest, source.GetGUID(), true, false); @@ -376,7 +376,9 @@ namespace Game.Entities if (questGiver == this) { - Cypher.Assert(quest.HasFlag(QuestFlags.AutoComplete)); + if (!quest.HasFlag(QuestFlags.AutoComplete)) + return null; + return Global.ObjectMgr.GetQuestTemplate(nextQuestID); } @@ -453,7 +455,7 @@ namespace Game.Entities return false; // not allow re-complete quest // auto complete quest - if (qInfo.IsAutoComplete() && CanTakeQuest(qInfo, false)) + if (qInfo.IsTurnIn() && CanTakeQuest(qInfo, false)) return true; var q_status = m_QuestStatus.LookupByKey(questId); @@ -474,7 +476,7 @@ namespace Game.Entities } } - if (qInfo.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent) && !q_status.Explored) + if ((qInfo.HasFlag(QuestFlags.CompletionEvent) || qInfo.HasFlag(QuestFlags.CompletionAreaTrigger)) && !q_status.Explored) return false; if (qInfo.LimitTime != 0 && q_status.Timer == 0) @@ -512,7 +514,7 @@ namespace Game.Entities return false; // not auto complete quest and not completed quest (only cheating case, then ignore without message) - if (!quest.IsDFQuest() && !quest.IsAutoComplete() && GetQuestStatus(quest.Id) != QuestStatus.Complete) + if (!quest.IsDFQuest() && !quest.IsTurnIn() && GetQuestStatus(quest.Id) != QuestStatus.Complete) return false; // daily quest can't be rewarded (25 daily quest already completed) @@ -784,7 +786,7 @@ namespace Game.Entities { SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(quest.SourceSpellID, GetMap().GetDifficultyID()); Unit caster = this; - if (questGiver != null && questGiver.IsTypeMask(TypeMask.Unit) && !quest.HasFlag(QuestFlags.PlayerCastOnAccept) && !spellInfo.HasTargetType(Targets.UnitCaster) && !spellInfo.HasTargetType(Targets.DestCasterSummon)) + if (questGiver != null && questGiver.IsTypeMask(TypeMask.Unit) && !quest.HasFlag(QuestFlags.PlayerCastAccept) && !spellInfo.HasTargetType(Targets.UnitCaster) && !spellInfo.HasTargetType(Targets.DestCasterSummon)) { Unit unit = questGiver.ToUnit(); if (unit != null) @@ -819,7 +821,7 @@ namespace Game.Entities Quest qInfo = Global.ObjectMgr.GetQuestTemplate(quest_id); if (qInfo != null) - if (qInfo.HasFlag(QuestFlags.Tracking)) + if (qInfo.HasFlag(QuestFlags.TrackingEvent)) RewardQuest(qInfo, LootItemType.Item, 0, this, false); } } @@ -943,8 +945,13 @@ namespace Game.Entities switch (obj.Type) { case QuestObjectiveType.Item: - DestroyItemCount((uint)obj.ObjectID, (uint)obj.Amount, true); + { + int amountToDestroy = obj.Amount; + if (quest.HasFlag(QuestFlags.RemoveSurplusItems)) + amountToDestroy = int.MaxValue; + DestroyItemCount((uint)obj.ObjectID, (uint)amountToDestroy, true); break; + } case QuestObjectiveType.Currency: RemoveCurrency((uint)obj.ObjectID, obj.Amount, CurrencyDestroyReason.QuestTurnin); break; @@ -958,7 +965,9 @@ namespace Game.Entities if (quest.ItemDrop[i] != 0) { uint count = quest.ItemDropQuantity[i]; - DestroyItemCount(quest.ItemDrop[i], count != 0 ? count : 9999, true); + if (count == 0) + count = uint.MaxValue; + DestroyItemCount(quest.ItemDrop[i], count, true); } } } @@ -1117,7 +1126,7 @@ namespace Game.Entities { SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(quest.RewardSpell, GetMap().GetDifficultyID()); Unit caster = this; - if (questGiver != null && questGiver.IsTypeMask(TypeMask.Unit) && !quest.HasFlag(QuestFlags.PlayerCastOnComplete) && !spellInfo.HasTargetType(Targets.UnitCaster)) + if (questGiver != null && questGiver.IsTypeMask(TypeMask.Unit) && !quest.HasFlag(QuestFlags.PlayerCastComplete) && !spellInfo.HasTargetType(Targets.UnitCaster)) { Unit unit = questGiver.ToUnit(); if (unit != null) @@ -1137,7 +1146,7 @@ namespace Game.Entities SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(displaySpell.SpellId, GetMap().GetDifficultyID()); Unit caster = this; - if (questGiver && questGiver.IsTypeMask(TypeMask.Unit) && !quest.HasFlag(QuestFlags.PlayerCastOnComplete) && !spellInfo.HasTargetType(Targets.UnitCaster)) + if (questGiver && questGiver.IsTypeMask(TypeMask.Unit) && !quest.HasFlag(QuestFlags.PlayerCastComplete) && !spellInfo.HasTargetType(Targets.UnitCaster)) { Unit unit = questGiver.ToUnit(); if (unit) @@ -1170,69 +1179,69 @@ namespace Game.Entities SendQuestGiverStatusMultiple(); - bool conditionChanged = SendQuestUpdate(questId, false); + SendQuestUpdate(questId); + + bool updateVisibility = false; + if (quest.HasFlag(QuestFlags.UpdatePhaseshift)) + updateVisibility = PhasingHandler.OnConditionChange(this, false); //lets remove flag for delayed teleports SetCanDelayTeleport(false); - bool canHaveNextQuest = !quest.HasFlag(QuestFlags.AutoComplete) ? questGiver != null && !questGiver.IsPlayer() : true; - if (canHaveNextQuest) + switch (questGiver.GetTypeId()) { - switch (questGiver.GetTypeId()) + case TypeId.Unit: + case TypeId.Player: { - case TypeId.Unit: - case TypeId.Player: + //For AutoSubmition was added plr case there as it almost same exclute AI script cases. + // Send next quest + Quest nextQuest = GetNextQuest(questGiver, quest); + if (nextQuest != null) { - //For AutoSubmition was added plr case there as it almost same exclute AI script cases. - // Send next quest - Quest nextQuest = GetNextQuest(questGiver, quest); - if (nextQuest != null) + // Only send the quest to the player if the conditions are met + if (CanTakeQuest(nextQuest, false)) { - // 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); + if (nextQuest.IsAutoAccept() && CanAddQuest(nextQuest, true)) + AddQuestAndCheckCompletion(nextQuest, questGiver); - PlayerTalkClass.SendQuestGiverQuestDetails(nextQuest, questGiver.GetGUID(), true, false); - } + PlayerTalkClass.SendQuestGiverQuestDetails(nextQuest, questGiver.GetGUID(), true, false); } - - PlayerTalkClass.ClearMenus(); - Creature creatureQGiver = questGiver.ToCreature(); - if (creatureQGiver != null) - creatureQGiver.GetAI().OnQuestReward(this, quest, rewardType, rewardId); - break; } - case TypeId.GameObject: - { - // Send next quest - Quest nextQuest = GetNextQuest(questGiver, quest); - if (nextQuest != null) - { - // 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(); - questGiver.ToGameObject()?.GetAI()?.OnQuestReward(this, quest, rewardType, rewardId); - break; - } - default: - break; + PlayerTalkClass.ClearMenus(); + Creature creatureQGiver = questGiver.ToCreature(); + if (creatureQGiver != null) + creatureQGiver.GetAI().OnQuestReward(this, quest, rewardType, rewardId); + break; } - } + case TypeId.GameObject: + { + // Send next quest + Quest nextQuest = GetNextQuest(questGiver, quest); + if (nextQuest != null) + { + // 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(); + questGiver.ToGameObject()?.GetAI()?.OnQuestReward(this, quest, rewardType, rewardId); + break; + } + default: + break; + } Global.ScriptMgr.OnQuestStatusChange(this, questId); Global.ScriptMgr.OnQuestStatusChange(this, quest, oldStatus, QuestStatus.Rewarded); - if (conditionChanged) + if (updateVisibility) UpdateObjectVisibility(); } @@ -1299,6 +1308,21 @@ namespace Game.Entities } } + public void FailQuestsWithFlag(QuestFlags flag) + { + for (ushort slot = 0; slot < SharedConst.MaxQuestLogSize; ++slot) + { + uint questId = GetQuestSlotQuestId(slot); + if (questId == 0) + continue; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + if (quest.HasFlag(flag)) + FailQuest(questId); + } + } + public void AbandonQuest(uint questId) { Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); @@ -1865,7 +1889,7 @@ namespace Game.Entities QuestStatus oldStatus = m_QuestStatus[questId].Status; m_QuestStatus[questId].Status = status; - if (!quest.IsAutoComplete()) + if (!quest.IsTurnIn()) m_QuestStatusSave[questId] = QuestSaveType.Default; Global.ScriptMgr.OnQuestStatusChange(this, questId); @@ -1922,7 +1946,7 @@ namespace Game.Entities SendQuestUpdate(questId); } - bool SendQuestUpdate(uint questId, bool updateVisiblity = true) + void SendQuestUpdate(uint questId) { var saBounds = Global.SpellMgr.GetSpellAreaForQuestMapBounds(questId); if (!saBounds.Empty()) @@ -1970,7 +1994,6 @@ namespace Game.Entities } UpdateVisibleGameobjectsOrSpellClicks(); - return PhasingHandler.OnConditionChange(this, updateVisiblity); } public QuestGiverStatus GetQuestDialogStatus(WorldObject questgiver) @@ -2042,7 +2065,7 @@ namespace Game.Entities break; } - if (quest.IsAutoComplete() && CanTakeQuest(quest, false) && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly()) + if (quest.IsTurnIn() && CanTakeQuest(quest, false) && quest.IsRepeatable() && !quest.IsDailyOrWeekly() && !quest.IsMonthly()) { if (GetLevel() <= (GetQuestLevel(quest) + WorldConfig.GetIntValue(WorldCfg.QuestLowLevelHideDiff))) result |= QuestGiverStatus.RepeatableTurnin; diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index b893e85f2..b85c895b2 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -650,6 +650,8 @@ namespace Game.Entities InitializeSelfResurrectionSpells(); + FailQuestsWithFlag(QuestFlags.CompletionNoDeath); + UpdateCriteria(CriteriaType.DieOnMap, 1); UpdateCriteria(CriteriaType.DieAnywhere, 1); UpdateCriteria(CriteriaType.DieInInstance, 1); diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index a86e6effa..61e6e1ce5 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -1214,15 +1214,12 @@ namespace Game continue; } - if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + if (!quest.HasFlag(QuestFlags.CompletionEvent) && !quest.HasFlag(QuestFlags.CompletionAreaTrigger)) { - Log.outError(LogFilter.Sql, "Table `{0}` has quest (ID: {1}) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, 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.", + Log.outError(LogFilter.Sql, "Table `{0}` has quest (ID: {1}) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, but quest not have QUEST_FLAGS_COMPLETION_EVENT or QUEST_FLAGS_COMPLETION_AREA_TRIGGER in quest flags. Script command will do nothing.", tableName, tmp.QuestExplored.QuestID, tmp.id); - // this will prevent quest completing without objective - quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent); - - // continue; - quest objective requirement set and command can be allowed + continue; } if (tmp.QuestExplored.Distance > SharedConst.DefaultVisibilityDistance) @@ -7249,7 +7246,7 @@ namespace Game // additional quest integrity checks (GO, creaturetemplate and itemtemplate must be loaded already) - if (qinfo.Type >= QuestType.Max) + if (qinfo.Type >= QuestType.MaxDBAllowedQuestTypes) Log.outError(LogFilter.Sql, "Quest {0} has `Method` = {1}, expected values are 0, 1 or 2.", qinfo.Id, qinfo.Type); if (Convert.ToBoolean(qinfo.SpecialFlags & ~QuestSpecialFlags.DbAllowed)) @@ -7292,7 +7289,7 @@ namespace Game } } - if (Convert.ToBoolean(qinfo.Flags & QuestFlags.Tracking)) + if (Convert.ToBoolean(qinfo.Flags & QuestFlags.TrackingEvent)) { // at auto-reward can be rewarded only RewardChoiceItemId[0] for (int j = 1; j < qinfo.RewardChoiceItemId.Length; ++j) @@ -7878,35 +7875,7 @@ namespace Game } } - // check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE - foreach (SpellNameRecord spellNameEntry in CliDB.SpellNameStorage.Values) - { - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellNameEntry.Id, Difficulty.None); - if (spellInfo == null) - continue; - - foreach (var spellEffectInfo in spellInfo.GetEffects()) - { - if (spellEffectInfo.Effect != SpellEffectName.QuestComplete) - continue; - - uint questId = (uint)spellEffectInfo.MiscValue; - Quest quest = GetQuestTemplate(questId); - - // some quest referenced in spells not exist (outdated spells) - if (quest == null) - continue; - - if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) - { - Log.outError(LogFilter.Sql, "Spell (id: {0}) have SPELL_EFFECT_QUEST_COMPLETE for quest {1}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. " + - "Quest flags must be fixed, quest modified to enable objective.", spellInfo.Id, questId); - - // this will prevent quest completing without objective - quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent); - } - } - } + // don't check spells with SPELL_EFFECT_QUEST_COMPLETE, a lot of invalid db2 data // Make all paragon reward quests repeatable foreach (ParagonReputationRecord paragonReputation in CliDB.ParagonReputationStorage.Values) @@ -8122,35 +8091,30 @@ namespace Game { ++count; - uint trigger_ID = result.Read(0); - uint quest_ID = result.Read(1); + uint triggerId = result.Read(0); + uint questId = result.Read(1); - AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(trigger_ID); + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(triggerId); if (atEntry == null) { - Log.outError(LogFilter.Sql, "Area trigger (ID:{0}) does not exist in `AreaTrigger.dbc`.", trigger_ID); + Log.outError(LogFilter.Sql, "Area trigger (ID:{0}) does not exist in `AreaTrigger.dbc`.", triggerId); continue; } - Quest quest = GetQuestTemplate(quest_ID); - + Quest quest = GetQuestTemplate(questId); if (quest == null) { - Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not existing quest {1}", trigger_ID, quest_ID); + Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not existing quest {1}", triggerId, questId); continue; } - if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + if (!quest.HasFlag(QuestFlags.CompletionAreaTrigger) && !quest.HasQuestObjectiveType(QuestObjectiveType.AreaTrigger)) { - Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not quest {1}, 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 - quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent); - - // continue; - quest modified to required objective and trigger can be allowed. + Log.outError(LogFilter.Sql, $"Table `areatrigger_involvedrelation` has record (id: {triggerId}) for not quest {questId}, but quest not have flag QUEST_FLAGS_COMPLETION_AREA_TRIGGER and no objective with type QUEST_OBJECTIVE_AREATRIGGER. Trigger is obsolete, skipped."); + continue; } - _questAreaTriggerStorage.Add(trigger_ID, quest_ID); + _questAreaTriggerStorage.Add(triggerId, questId); } while (result.NextRow()); diff --git a/Source/Game/Handlers/MiscHandler.cs b/Source/Game/Handlers/MiscHandler.cs index 4c9ba4130..a9f66db85 100644 --- a/Source/Game/Handlers/MiscHandler.cs +++ b/Source/Game/Handlers/MiscHandler.cs @@ -220,7 +220,8 @@ namespace Game break; } - player.AreaExploredOrEventHappens(questId); + if (qInfo.HasFlag(QuestFlags.CompletionAreaTrigger)) + player.AreaExploredOrEventHappens(questId); if (player.CanCompleteQuest(questId)) player.CompleteQuest(questId); diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index 24f82502e..bde2a2844 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -148,7 +148,7 @@ namespace Game { _player.AddQuestAndCheckCompletion(quest, obj); - if (quest.HasFlag(QuestFlags.PartyAccept)) + if (quest.IsPushedToPartyOnAccept()) { var group = _player.GetGroup(); if (group) @@ -175,6 +175,26 @@ namespace Game _player.PlayerTalkClass.SendCloseGossip(); + if (quest.HasFlag(QuestFlags.LaunchGossipAccept)) + { + void launchGossip(WorldObject worldObject) + { + _player.PlayerTalkClass.ClearMenus(); + _player.PrepareGossipMenu(worldObject, _player.GetGossipMenuForSource(worldObject), true); + _player.SendPreparedGossip(worldObject); + } + + Creature creature = obj.ToCreature(); + if (creature != null) + launchGossip(creature); + else + { + GameObject go = obj.ToGameObject(); + if (go != null) + launchGossip(go); + } + } + return; } } @@ -202,7 +222,7 @@ namespace Game if (quest.IsAutoAccept() && GetPlayer().CanAddQuest(quest, true)) GetPlayer().AddQuestAndCheckCompletion(quest, obj); - if (quest.IsAutoComplete()) + if (quest.IsTurnIn()) GetPlayer().PlayerTalkClass.SendQuestGiverRequestItems(quest, obj.GetGUID(), GetPlayer().CanCompleteQuest(quest.Id), true); else GetPlayer().PlayerTalkClass.SendQuestGiverQuestDetails(quest, obj.GetGUID(), true, false); @@ -331,7 +351,7 @@ namespace Game } if ((!GetPlayer().CanSeeStartQuest(quest) && GetPlayer().GetQuestStatus(packet.QuestID) == QuestStatus.None) || - (GetPlayer().GetQuestStatus(packet.QuestID) != QuestStatus.Complete && !quest.IsAutoComplete())) + (GetPlayer().GetQuestStatus(packet.QuestID) != QuestStatus.Complete && !quest.IsTurnIn())) { Log.outError(LogFilter.Network, "Error in QuestStatus.Complete: player {0} ({1}) tried to complete quest {2}, but is not allowed to do so (possible packet-hacking or high latency)", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.QuestID); @@ -422,35 +442,34 @@ namespace Game [WorldPacketHandler(ClientOpcodes.QuestConfirmAccept)] void HandleQuestConfirmAccept(QuestConfirmAccept packet) { + if (_player.GetSharedQuestID() != packet.QuestID) + return; + + _player.ClearQuestSharingInfo(); Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); - if (quest != null) - { - if (!quest.HasFlag(QuestFlags.PartyAccept)) - return; + if (quest == null) + return; - Player originalPlayer = Global.ObjAccessor.FindPlayer(GetPlayer().GetPlayerSharingQuest()); - if (originalPlayer == null) - return; + Player originalPlayer = Global.ObjAccessor.FindPlayer(GetPlayer().GetPlayerSharingQuest()); + if (originalPlayer == null) + return; - if (!GetPlayer().IsInSameRaidWith(originalPlayer)) - return; + if (!_player.IsInSameRaidWith(originalPlayer)) + return; - if (!originalPlayer.IsActiveQuest(packet.QuestID)) - return; + if (!originalPlayer.IsActiveQuest(packet.QuestID)) + return; - if (!GetPlayer().CanTakeQuest(quest, true)) - return; + if (!_player.CanTakeQuest(quest, true)) + return; - if (GetPlayer().CanAddQuest(quest, true)) - { - GetPlayer().AddQuestAndCheckCompletion(quest, null); // NULL, this prevent DB script from duplicate running + if (!_player.CanAddQuest(quest, true)) + return; - if (quest.SourceSpellID > 0) - _player.CastSpell(_player, quest.SourceSpellID, true); - } - } + _player.AddQuestAndCheckCompletion(quest, null); // NULL, this prevent DB script from duplicate running - GetPlayer().ClearQuestSharingInfo(); + if (quest.SourceSpellID > 0) + _player.CastSpell(_player, quest.SourceSpellID, true); } [WorldPacketHandler(ClientOpcodes.QuestGiverCompleteQuest, Processing = PacketProcessing.Inplace)] @@ -462,9 +481,6 @@ namespace Game if (quest == null) return; - if (autoCompleteMode && !quest.HasFlag(QuestFlags.AutoComplete)) - return; - WorldObject obj; if (autoCompleteMode) obj = GetPlayer(); @@ -474,7 +490,7 @@ namespace Game if (!obj) return; - if (!autoCompleteMode) + if (!quest.HasFlag(QuestFlags.AutoComplete)) { if (!obj.HasInvolvedQuest(packet.QuestID)) return; @@ -666,7 +682,7 @@ namespace Game sender.SendPushToPartyResponse(receiver, QuestPushReason.Success); - if ((quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly()) || quest.HasFlag(QuestFlags.AutoComplete)) + if (quest.IsTurnIn() && quest.IsRepeatable() && !quest.IsDailyOrWeekly()) receiver.PlayerTalkClass.SendQuestGiverRequestItems(quest, sender.GetGUID(), receiver.CanCompleteRepeatableQuest(quest), true); else { diff --git a/Source/Game/Quest/Quest.cs b/Source/Game/Quest/Quest.cs index 01a6140a2..b0dda2672 100644 --- a/Source/Game/Quest/Quest.cs +++ b/Source/Game/Quest/Quest.cs @@ -462,7 +462,7 @@ namespace Game { return (uint)(MaxMoneyValue() * WorldConfig.GetFloatValue(WorldCfg.RateMoneyQuest)); } - + public QuestTagType? GetQuestTag() { QuestInfoRecord questInfo = CliDB.QuestInfoStorage.LookupByKey(QuestInfoID); @@ -471,7 +471,7 @@ namespace Game return null; } - + public void BuildQuestRewards(QuestRewards rewards, Player player) { rewards.ChoiceItemCount = GetRewChoiceItemsCount(); @@ -494,7 +494,7 @@ namespace Game if (++displaySpellIndex >= rewards.SpellCompletionDisplayID.Length) break; } - + rewards.SpellCompletionID = RewardSpell; rewards.SkillLineID = RewardSkillId; rewards.NumSkillUps = RewardSkillPoints; @@ -532,7 +532,7 @@ namespace Game public uint GetRewMoneyMaxLevel() { // If Quest has flag to not give money on max level, it's 0 - if (HasFlag(QuestFlags.NoMoneyFromXp)) + if (HasFlag(QuestFlags.NoMoneyForXp)) return 0; // Else, return the rewarded copper sum modified by the rate @@ -544,9 +544,9 @@ namespace Game return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoAccept) && HasFlag(QuestFlags.AutoAccept); } - public bool IsAutoComplete() + public bool IsTurnIn() { - return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoComplete) && Type == QuestType.AutoComplete; + return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoComplete) && Type == QuestType.TurnIn; } public bool IsRaidQuest(Difficulty difficulty) @@ -563,7 +563,7 @@ namespace Game break; } - if (Flags.HasAnyFlag(QuestFlags.Raid)) + if (Flags.HasAnyFlag(QuestFlags.RaidGroupOk)) return true; return false; @@ -655,7 +655,7 @@ namespace Game response.Info.RewardXPDifficulty = RewardXPDifficulty; response.Info.RewardXPMultiplier = RewardXPMultiplier; - if (!HasFlag(QuestFlags.HiddenRewards)) + if (!HasFlag(QuestFlags.HideReward)) response.Info.RewardMoney = (int)(player != null ? player.GetQuestMoneyReward(this) : GetMaxMoneyReward()); response.Info.RewardMoneyDifficulty = RewardMoneyDifficulty; @@ -699,7 +699,7 @@ namespace Game response.Info.ItemDropQuantity[i] = (int)ItemDropQuantity[i]; } - if (!HasFlag(QuestFlags.HiddenRewards)) + if (!HasFlag(QuestFlags.HideReward)) { for (byte i = 0; i < SharedConst.QuestRewardItemCount; ++i) { @@ -780,12 +780,12 @@ namespace Game public void SetSpecialFlag(QuestSpecialFlags flag) { SpecialFlags |= flag; } public bool HasQuestObjectiveType(QuestObjectiveType type) { return _usedQuestObjectiveTypes[(int)type]; } - - public bool IsAutoPush() { return HasFlagEx(QuestFlagsEx.AutoPush); } - public bool IsWorldQuest() { return HasFlagEx(QuestFlagsEx.IsWorldQuest);} + + public bool IsAutoPush() { return HasFlagEx(QuestFlagsEx.AutoPush); } + public bool IsWorldQuest() { return HasFlagEx(QuestFlagsEx.IsWorldQuest); } // Possibly deprecated flag - public bool IsUnavailable() { return HasFlag(QuestFlags.Unavailable); } + public bool IsUnavailable() { return HasFlag(QuestFlags.Deprecated); } // table data accessors: public bool IsRepeatable() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable); } @@ -800,7 +800,8 @@ namespace Game } public bool IsDailyOrWeekly() { return Flags.HasAnyFlag(QuestFlags.Daily | QuestFlags.Weekly); } public bool IsDFQuest() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.DfQuest); } - + public bool IsPushedToPartyOnAccept() { return HasSpecialFlag(QuestSpecialFlags.AutoPushToParty); } + public uint GetRewChoiceItemsCount() { return _rewChoiceItemsCount; } public uint GetRewItemsCount() { return _rewItemsCount; } public uint GetRewCurrencyCount() { return _rewCurrencyCount; } diff --git a/Source/Game/Server/WorldSession.cs b/Source/Game/Server/WorldSession.cs index 9e3f4608a..64b482f0f 100644 --- a/Source/Game/Server/WorldSession.cs +++ b/Source/Game/Server/WorldSession.cs @@ -153,6 +153,8 @@ namespace Game // Clear whisper whitelist _player.ClearWhisperWhiteList(); + _player.FailQuestsWithFlag(QuestFlags.FailOnLogout); + // 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/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 9ee111ed7..33f87ec92 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -71,10 +71,10 @@ namespace Game.Spells _triggeredCastFlags = triggerFlags; if (info.HasAttribute(SpellAttr2.DoNotReportSpellFailure)) - _triggeredCastFlags = _triggeredCastFlags | TriggerCastFlags.DontReportCastError; + _triggeredCastFlags |= TriggerCastFlags.DontReportCastError; if (m_spellInfo.HasAttribute(SpellAttr4.AllowCastWhileCasting)) - _triggeredCastFlags = _triggeredCastFlags | TriggerCastFlags.IgnoreCastInProgress; + _triggeredCastFlags |= TriggerCastFlags.IgnoreCastInProgress; m_castItemLevel = -1; diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index ba28c454f..ffb2788aa 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -3362,7 +3362,7 @@ namespace Game.Spells ushort logSlot = player.FindQuestSlot(questId); if (logSlot < SharedConst.MaxQuestLogSize) player.AreaExploredOrEventHappens(questId); - else if (quest.HasFlag(QuestFlags.Tracking)) // Check if the quest is used as a serverside flag. + else if (quest.HasFlag(QuestFlags.TrackingEvent)) // Check if the quest is used as a serverside flag. player.SetRewardedQuest(questId); // If so, set status to rewarded without broadcasting it to client. } }