diff --git a/Source/Framework/Constants/QuestConst.cs b/Source/Framework/Constants/QuestConst.cs index 8d3ac9710..7bf76d4fc 100644 --- a/Source/Framework/Constants/QuestConst.cs +++ b/Source/Framework/Constants/QuestConst.cs @@ -41,7 +41,8 @@ namespace Framework.Constants ObtainCurrency = 17, // requires the player to gain X currency after starting the quest but not required to keep it until the end (does not consume) IncreaseReputation = 18,// requires the player to gain X reputation with a faction AreaTriggerEnter = 19, - AreaTriggerExit = 20 + AreaTriggerExit = 20, + Max } public enum QuestObjectiveFlags @@ -494,17 +495,9 @@ namespace Framework.Constants 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 - Cast = 0x20, // Set by 32 in SpecialFlags in DB if the quest requires RequiredOrNpcGo killcredit but NOT kill (a spell cast) // Room For More Custom Flags - DbAllowed = Repeatable | ExplorationOrEvent | AutoAccept | DfQuest | Monthly | Cast, - - Deliver = 0x080, // Internal Flag Computed Only - Speakto = 0x100, // Internal Flag Computed Only - Kill = 0x200, // Internal Flag Computed Only - Timed = 0x400, // Internal Flag Computed Only - PlayerKill = 0x800, // Internal Flag Computed Only - CompletedAtStart = 0x1000 // Internal flag computed only + DbAllowed = Repeatable | ExplorationOrEvent | AutoAccept | DfQuest | Monthly, } public enum QuestSaveType diff --git a/Source/Game/Entities/Creature/Gossip.cs b/Source/Game/Entities/Creature/Gossip.cs index d1e989944..c0ff3319f 100644 --- a/Source/Game/Entities/Creature/Gossip.cs +++ b/Source/Game/Entities/Creature/Gossip.cs @@ -570,7 +570,7 @@ namespace Game.Misc // We can always call to RequestItems, but this packet only goes out if there are actually // items. Otherwise, we'll skip straight to the OfferReward - if (!quest.HasSpecialFlag(QuestSpecialFlags.Deliver) && canComplete) + if (!quest.HasQuestObjectiveType(QuestObjectiveType.Item) && canComplete) { SendQuestGiverOfferReward(quest, npcGUID, true); return; diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index f8f410d1c..8f8a4ccb2 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -694,7 +694,7 @@ namespace Game.Entities long quest_time = result.Read(2); - if (quest.HasSpecialFlag(QuestSpecialFlags.Timed) && !GetQuestRewardStatus(quest_id)) + if (quest.LimitTime != 0 && !GetQuestRewardStatus(quest_id)) { AddTimedQuest(quest_id); diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 127f76f54..93430601c 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -492,7 +492,7 @@ namespace Game.Entities } } - if (qInfo.HasSpecialFlag(QuestSpecialFlags.Timed) && q_status.Timer == 0) + if (qInfo.LimitTime != 0 && q_status.Timer == 0) return false; return true; @@ -509,7 +509,7 @@ namespace Game.Entities if (!CanTakeQuest(quest, false)) return false; - if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) + if (quest.HasQuestObjectiveType(QuestObjectiveType.Item)) foreach (QuestObjective obj in quest.Objectives) if (obj.Type == QuestObjectiveType.Item && !HasItemCount((uint)obj.ObjectID, (uint)obj.Amount)) return false; @@ -535,7 +535,7 @@ namespace Game.Entities return false; // prevent receive reward with quest items in bank - if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) + if (quest.HasQuestObjectiveType(QuestObjectiveType.Item)) { foreach (QuestObjective obj in quest.Objectives) { @@ -768,10 +768,9 @@ namespace Game.Entities } uint qtime = 0; - if (quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + uint limittime = quest.LimitTime; + if (limittime != 0) { - uint limittime = quest.LimitTime; - // shared timed quest if (questGiver != null && questGiver.IsTypeId(TypeId.Player)) limittime = questGiver.ToPlayer().m_QuestStatus[quest_id].Timer / Time.InMilliseconds; @@ -1195,7 +1194,7 @@ namespace Game.Entities if (qStatus != QuestStatus.Incomplete) { // completed timed quest with no requirements - if (qStatus != QuestStatus.Complete || !quest.HasSpecialFlag(QuestSpecialFlags.Timed) || !quest.HasSpecialFlag(QuestSpecialFlags.CompletedAtStart)) + if (qStatus != QuestStatus.Complete || quest.LimitTime == 0 || !quest.Objectives.Empty()) return; } @@ -1209,7 +1208,7 @@ namespace Game.Entities SetQuestSlotState(log_slot, QuestSlotStateMask.Fail); } - if (quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + if (quest.LimitTime != 0) { QuestStatusData q_status = m_QuestStatus[questId]; @@ -1539,7 +1538,7 @@ namespace Game.Entities public bool SatisfyQuestTimed(Quest qInfo, bool msg) { - if (!m_timedquests.Empty() && qInfo.HasSpecialFlag(QuestSpecialFlags.Timed)) + if (!m_timedquests.Empty() && qInfo.LimitTime != 0) { if (msg) { @@ -1970,16 +1969,17 @@ namespace Game.Entities public void AdjustQuestReqItemCount(Quest quest) { - if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) + // adjust progress of quest objectives that rely on external counters, like items + if (quest.HasQuestObjectiveType(QuestObjectiveType.Item)) { foreach (QuestObjective obj in quest.Objectives) { - if (obj.Type != QuestObjectiveType.Item) - continue; - - uint reqItemCount = (uint)obj.Amount; - uint curItemCount = GetItemCount((uint)obj.ObjectID, true); - SetQuestObjectiveData(obj, (int)Math.Min(curItemCount, reqItemCount)); + if (obj.Type == QuestObjectiveType.Item) + { + uint reqItemCount = (uint)obj.Amount; + uint curItemCount = GetItemCount((uint)obj.ObjectID, true); + SetQuestObjectiveData(obj, (int)Math.Min(curItemCount, reqItemCount)); + } } } } @@ -2144,7 +2144,7 @@ namespace Game.Entities continue; Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null || !qInfo.HasSpecialFlag(QuestSpecialFlags.Deliver)) + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.Item)) continue; foreach (QuestObjective obj in qInfo.Objectives) @@ -2183,10 +2183,9 @@ namespace Game.Entities uint questid = GetQuestSlotQuestId(i); if (questid == 0) continue; + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null) - continue; - if (!qInfo.HasSpecialFlag(QuestSpecialFlags.Deliver)) + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.Item)) continue; foreach (QuestObjective obj in qInfo.Objectives) @@ -2251,36 +2250,33 @@ namespace Game.Entities continue; Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null) + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.Monster)) continue; // just if !ingroup || !noraidgroup || raidgroup QuestStatusData q_status = m_QuestStatus[questid]; if (q_status.Status == QuestStatus.Incomplete && (GetGroup() == null || !GetGroup().IsRaidGroup() || qInfo.IsAllowedInRaid(GetMap().GetDifficultyID()))) { - if (qInfo.HasSpecialFlag(QuestSpecialFlags.Kill))// && !qInfo.HasSpecialFlag(QuestSpecialFlags.Cast)) + foreach (QuestObjective obj in qInfo.Objectives) { - foreach (QuestObjective obj in qInfo.Objectives) + if (obj.Type != QuestObjectiveType.Monster) + continue; + + int reqkill = obj.ObjectID; + if (reqkill == real_entry) { - if (obj.Type != QuestObjectiveType.Monster) - continue; - - int reqkill = obj.ObjectID; - if (reqkill == real_entry) + int curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curKillCount < obj.Amount) { - int curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curKillCount < obj.Amount) - { - SetQuestObjectiveData(obj, curKillCount + addKillCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curKillCount + addKillCount)); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; + SetQuestObjectiveData(obj, curKillCount + addKillCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curKillCount + addKillCount)); } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; } } } @@ -2298,7 +2294,7 @@ namespace Game.Entities continue; Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); - if (qInfo == null) + if (qInfo == null || !qInfo.HasQuestObjectiveType(QuestObjectiveType.PlayerKills)) continue; // just if !ingroup || !noraidgroup || raidgroup @@ -2344,32 +2340,29 @@ namespace Game.Entities if (q_status.Status == QuestStatus.Incomplete) { - if (qInfo.HasSpecialFlag(QuestSpecialFlags.Cast)) + foreach (QuestObjective obj in qInfo.Objectives) { - foreach (QuestObjective obj in qInfo.Objectives) + if (obj.Type != QuestObjectiveType.GameObject) + continue; + + int reqTarget = obj.ObjectID; + + // other not this creature/GO related objectives + if (reqTarget != entry) + continue; + + int curCastCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curCastCount < obj.Amount) { - if (obj.Type != QuestObjectiveType.GameObject) - continue; - - int reqTarget = obj.ObjectID; - - // other not this creature/GO related objectives - if (reqTarget != entry) - continue; - - int curCastCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curCastCount < obj.Amount) - { - SetQuestObjectiveData(obj, curCastCount + addCastCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curCastCount + addCastCount)); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). - break; + SetQuestObjectiveData(obj, curCastCount + addCastCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curCastCount + addCastCount)); } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; } } } @@ -2407,29 +2400,26 @@ namespace Game.Entities if (q_status.Status == QuestStatus.Incomplete) { - if (qInfo.HasSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast | QuestSpecialFlags.Speakto)) + foreach (QuestObjective obj in qInfo.Objectives) { - foreach (QuestObjective obj in qInfo.Objectives) + if (obj.Type != QuestObjectiveType.TalkTo) + continue; + + int reqTarget = obj.ObjectID; + if (reqTarget == entry) { - if (obj.Type != QuestObjectiveType.TalkTo) - continue; - - int reqTarget = obj.ObjectID; - if (reqTarget == entry) + int curTalkCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curTalkCount < obj.Amount) { - int curTalkCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); - if (curTalkCount < obj.Amount) - { - SetQuestObjectiveData(obj, curTalkCount + addTalkCount); - SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curTalkCount + addTalkCount)); - } - - if (CanCompleteQuest(questid)) - CompleteQuest(questid); - - // Quest can't have more than one objective for the same creature (code optimisation) - break; + SetQuestObjectiveData(obj, curTalkCount + addTalkCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curTalkCount + addTalkCount)); } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // Quest can't have more than one objective for the same creature (code optimisation) + break; } } } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 503ec2806..fe4a6de34 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -7082,23 +7082,18 @@ namespace Game switch (obj.Type) { case QuestObjectiveType.Item: - qinfo.SetSpecialFlag(QuestSpecialFlags.Deliver); if (GetItemTemplate((uint)obj.ObjectID) == null) Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing item entry {2}, quest can't be done.", qinfo.Id, obj.Id, obj.ObjectID); break; case QuestObjectiveType.Monster: - qinfo.SetSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast); if (GetCreatureTemplate((uint)obj.ObjectID) == null) Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing creature entry {2}, quest can't be done.", qinfo.Id, obj.Id, obj.ObjectID); break; case QuestObjectiveType.GameObject: - qinfo.SetSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast); if (GetGameObjectTemplate((uint)obj.ObjectID) == null) Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing gameobject entry {2}, quest can't be done.", qinfo.Id, obj.Id, obj.ObjectID); break; case QuestObjectiveType.TalkTo: - // Need checks (is it creature only?) - qinfo.SetSpecialFlag(QuestSpecialFlags.Cast | QuestSpecialFlags.Speakto); break; case QuestObjectiveType.MinReputation: case QuestObjectiveType.MaxReputation: @@ -7106,7 +7101,6 @@ namespace Game Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing faction id {2}", qinfo.Id, obj.Id, obj.ObjectID); break; case QuestObjectiveType.PlayerKills: - qinfo.SetSpecialFlag(QuestSpecialFlags.Kill); if (obj.Amount <= 0) Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid player kills count {2}", qinfo.Id, obj.Id, obj.Amount); break; @@ -7403,29 +7397,6 @@ namespace Game if (qinfo.ExclusiveGroup != 0) _exclusiveQuestGroups.Add(qinfo.ExclusiveGroup, qinfo.Id); - if (qinfo.LimitTime != 0) - qinfo.SetSpecialFlag(QuestSpecialFlags.Timed); - - // Special flag to determine if quest is completed from the start, used to determine if we can fail timed quest if it is completed - if (!qinfo.HasSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast | QuestSpecialFlags.Speakto | QuestSpecialFlags.ExplorationOrEvent)) - { - bool addFlag = true; - if (qinfo.HasSpecialFlag(QuestSpecialFlags.Deliver)) - { - foreach (QuestObjective obj in qinfo.Objectives) - { - if (obj.Type == QuestObjectiveType.Item) - if (obj.ObjectID != qinfo.SourceItemId || obj.Amount > qinfo.SourceItemIdCount) - { - addFlag = false; - break; - } - } - } - - if (addFlag) - qinfo.SetSpecialFlag(QuestSpecialFlags.CompletedAtStart); - } } // check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index ba88995b9..f1d588e8c 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -447,7 +447,7 @@ namespace Game if (quest != null) { - if (quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + if (quest.LimitTime != 0) GetPlayer().RemoveTimedQuest(questId); if (quest.HasFlag(QuestFlags.Pvp)) @@ -566,7 +566,7 @@ namespace Game } else { - if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) // some items required + if (quest.HasQuestObjectiveType(QuestObjectiveType.Item)) // some items required GetPlayer().PlayerTalkClass.SendQuestGiverRequestItems(quest, packet.QuestGiverGUID, GetPlayer().CanRewardQuest(quest, false), false); else // no items required GetPlayer().PlayerTalkClass.SendQuestGiverOfferReward(quest, packet.QuestGiverGUID, true); diff --git a/Source/Game/Quest/Quest.cs b/Source/Game/Quest/Quest.cs index 8022203b7..74d7cafb6 100644 --- a/Source/Game/Quest/Quest.cs +++ b/Source/Game/Quest/Quest.cs @@ -22,6 +22,7 @@ using Game.DataStorage; using Game.Entities; using Game.Networking.Packets; using System; +using System.Collections; using System.Collections.Generic; namespace Game @@ -252,6 +253,7 @@ namespace Game obj.Description = fields.Read(9); Objectives.Add(obj); + _usedQuestObjectiveTypes[(int)obj.Type] = true; } public void LoadQuestObjectiveVisualEffect(SQLFields fields) @@ -575,6 +577,8 @@ namespace Game public bool HasSpecialFlag(QuestSpecialFlags flag) { return (SpecialFlags & flag) != 0; } 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);} @@ -709,6 +713,7 @@ namespace Game public uint SourceItemIdCount; public uint RewardMailSenderEntry; public QuestSpecialFlags SpecialFlags; // custom flags, not sniffed/WDB + public BitArray _usedQuestObjectiveTypes = new((int)QuestObjectiveType.Max); public uint ScriptId; public List DependentPreviousQuests = new(); @@ -813,6 +818,8 @@ namespace Game case QuestObjectiveType.WinPetBattleAgainstNpc: case QuestObjectiveType.DefeatBattlePet: case QuestObjectiveType.CriteriaTree: + case QuestObjectiveType.AreaTriggerEnter: + case QuestObjectiveType.AreaTriggerExit: return true; default: break; diff --git a/sql/updates/world/master/2021_05_09_00_world.sql b/sql/updates/world/master/2021_05_09_00_world.sql new file mode 100644 index 000000000..5945bbbad --- /dev/null +++ b/sql/updates/world/master/2021_05_09_00_world.sql @@ -0,0 +1,20 @@ +UPDATE `quest_template_addon` SET `SpecialFlags`=`SpecialFlags`&~32; + +DELETE FROM `quest_template_addon` WHERE + `MaxLevel`=0 + AND `AllowableClasses`=0 + AND `SourceSpellID`=0 + AND `PrevQuestID`=0 + AND `NextQuestID`=0 + AND `ExclusiveGroup`=0 + AND `RewardMailTemplateID`=0 + AND `RewardMailDelay`=0 + AND `RequiredSkillID`=0 + AND `RequiredSkillPoints`=0 + AND `RequiredMinRepFaction`=0 + AND `RequiredMaxRepFaction`=0 + AND `RequiredMinRepValue`=0 + AND `RequiredMaxRepValue`=0 + AND `ProvidedItemCount`=0 + AND `SpecialFlags`=0 + AND `ScriptName`='';