diff --git a/Source/Framework/Constants/QuestConst.cs b/Source/Framework/Constants/QuestConst.cs index a9b92ca7f..444d73912 100644 --- a/Source/Framework/Constants/QuestConst.cs +++ b/Source/Framework/Constants/QuestConst.cs @@ -452,62 +452,62 @@ namespace Framework.Constants DisplayItemInTracker = 0x20000, // Displays Usable Item In Quest Tracker DisableCompletionText = 0x40000, // Use Objective Text As Complete Text AutoAccept = 0x80000, // The client recognizes this flag as auto-accept. - PlayerCastAccept = 0x100000, - PlayerCastComplete = 0x200000, - UpdatePhaseshift = 0x400000, - SorWhitelist = 0x800000, - LaunchGossipComplete = 0x1000000, + PlayerCastAccept = 0x100000, // Accept Spell Player Cast + PlayerCastComplete = 0x200000, // Complete Spell Player Cast + UpdatePhaseshift = 0x400000, // Update Phase Shift + SorWhitelist = 0x800000, // Scroll of Resurrection Whitelist + LaunchGossipComplete = 0x1000000, // Gossip on Quest Completion - Force Gossip 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, - ItemsGlowWhenComplete = 0x40000000, - FailOnLogout = 0x80000000 + WellKnown = 0x04000000, // Well-Known + PortraitInQuestLog = 0x8000000, // Portrait from Log + ShowItemWhenCompleted = 0x10000000, // Show Item When Completed + LaunchGossipAccept = 0x20000000, // Gossip on Quest Accept - Force Gossip + ItemsGlowWhenComplete = 0x40000000, // Items Glow When Done + FailOnLogout = 0x80000000 // Fail on Logout } [Flags] public enum QuestFlagsEx : uint { None = 0x00, - KeepAdditionalItems = 0x01, - SuppressGossipComplete = 0x02, - SuppressGossipAccept = 0x04, - DisallowPlayerAsQuestgiver = 0x08, - DisplayClassChoiceRewards = 0x10, - DisplaySpecChoiceRewards = 0x20, - RemoveFromLogOnPeriodicReset = 0x40, - AccountLevelQuest = 0x80, - LegendaryQuest = 0x100, - NoGuildXp = 0x200, - ResetCacheOnAccept = 0x400, - NoAbandonOnceAnyObjectiveComplete = 0x800, - RecastAcceptSpellOnLogin = 0x1000, - UpdateZoneAuras = 0x2000, - NoCreditForProxy = 0x4000, - DisplayAsDailyQuest = 0x8000, - PartOfQuestLine = 0x10000, - QuestForInternalBuildsOnly = 0x20000, - SuppressSpellLearnTextLine = 0x40000, - DisplayHeaderAsObjectiveForTasks = 0x80000, - GarrisonNonOwnersAllowed = 0x100000, - RemoveQuestOnWeeklyReset = 0x200000, - SuppressFarewellAudioAfterQuestAccept = 0x400000, - RewardsBypassWeeklyCapsAndSeasonTotal = 0x800000, - IsWorldQuest = 0x1000000, - NotIgnorable = 0x2000000, - AutoPush = 0x4000000, - NoSpellCompleteEffects = 0x8000000, - DoNotToastHonorReward = 0x10000000, - KeepRepeatableQuestOnFactionChange = 0x20000000, - KeepProgressOnFactionChange = 0x40000000, + NoItemRemoval = 0x01, // Keep Additional Items + SuppressGossipComplete = 0x02, // Gossip On Quest Completion - Suppress Gossip + SuppressGossipAccept = 0x04, // Gossip On Quest Accept - Suppress Gossip + DenyPlayerQuestgiver = 0x08, // Disallow Player As Questgiver (Advanced) + DisplayClassChoiceRewards = 0x10, // Choice Reward Filter - Matches Class + DisplaySpecChoiceRewards = 0x20, // Choice Reward Filter - Matches Spec + RemoveOnPeriodicReset = 0x40, // Remove From Log On Periodic Reset + Account = 0x80, // Account-Level Quest + Legendary = 0x100, // Legendary Quest + NoGuildXp = 0x200, // No Guild Xp + ResetCacheOnAccept = 0x400, // Reset Cache On Accept (Internal) + NoAbandonOnceBegun = 0x800, // No Abandon Once Any Objective Complete + RecastAcceptSpellOnLogin = 0x1000, // Recast Accept Spell On Login + UpdateZoneAuras = 0x2000, // Update Zone Auras + NoCreditForProxy = 0x4000, // No Credit For Proxy Creatures + DisplayAsDaily = 0x8000, // Display As Daily Quest + DisplayQuestLine = 0x10000, + InternalBuildsOnly = 0x20000, // Quest For Internal Builds Only + SuppressSpellLearnText = 0x40000, // Suppress Spell Learn Text Line (For Followers) + DisplayAsObjective = 0x80000, // Display Header As Objective For Tasks + AllowAllInGarrison = 0x100000, // Garrison Non-Owners Allowed + RemoveOnWeeklyReset = 0x200000, // Remove Quest On Weekly Reset + SuppressGreetingsOnAccept = 0x400000, // Suppress Farewell Audio After Quest Accept + RewardsIgnoreCaps = 0x800000, // Rewards Bypass Weekly Caps And Season Total + IsWorldQuest = 0x1000000, // Is A World Quest + NotIgnorable = 0x2000000, // Not Ignorable + AutoPush = 0x4000000, // Auto Push + NoSpellCompleteEffects = 0x8000000, // No Complete Quest Spell Effect + DoNotToastHonorReward = 0x10000000, // Do Not Toast Honor Reward + KeepRepeatableQuestOnFactionChange = 0x20000000, // Keep Repeatable Quest On Faction Change + KeepProgressOnFactionChange = 0x40000000, // Keep Quest Progress On Faction Change PushTeamQuestUsingMapController = 0x80000000 } public enum QuestFlagsEx2 { ResetOnGameMilestone = 0x01, - NoWarModeBonus = 0x02, + WarModeRewardsOptOut = 0x02, AwardHighestProfession = 0x04, NotReplayable = 0x08, NoReplayRewards = 0x10, @@ -521,7 +521,12 @@ namespace Framework.Constants HideTaskOnMainMap = 0x1000, HideTaskInTracker = 0x2000, SkipDisabledCheck = 0x4000, - EnforceMaximumQuestLevel = 0x8000 + EnforceMaximumQuestLevel = 0x8000, + ContentAlert = 0x10000, + DisplayTimeRemaining = 0x20000, + ClearTaskProgressWhenAbandoned = 0x40000, + SuppressGreetingsOnComplete = 0x80000, + HideRequiredItemsOnTurnIn = 0x100000 } public enum QuestSpecialFlags diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index 8c5272edb..7665f876a 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -763,6 +763,9 @@ namespace Game.Entities else if (questStatusData.Status == QuestStatus.Failed) SetQuestSlotState(slot, QuestSlotStateMask.Fail); + if (quest.HasFlagEx(QuestFlagsEx.RecastAcceptSpellOnLogin) && quest.HasFlag(QuestFlags.PlayerCastAccept) && quest.SourceSpellID > 0) + CastSpell(this, quest.SourceSpellID, new CastSpellExtraArgs(TriggerCastFlags.FullMask)); + ++slot; } diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 3e6062f3a..c4b66b931 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -170,6 +170,24 @@ namespace Game.Entities m_DFQuests.Clear(); // Dungeon Finder Quests. + 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 || !quest.IsDaily() || !quest.HasFlagEx(QuestFlagsEx.RemoveOnPeriodicReset)) + continue; + + SetQuestSlot(slot, 0); + AbandonQuest(questId); + RemoveActiveQuest(questId); + + if (quest.LimitTime != 0) + RemoveTimedQuest(questId); + } + // DB data deleted in caller m_DailyQuestChanged = false; m_lastDailyQuestTime = 0; @@ -190,6 +208,24 @@ namespace Game.Entities SetQuestCompletedBit(questBit, false); } + 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 || !quest.IsWeekly() || !quest.HasFlagEx(QuestFlagsEx.RemoveOnWeeklyReset)) + continue; + + SetQuestSlot(slot, 0); + AbandonQuest(questId); + RemoveActiveQuest(questId); + + if (quest.LimitTime != 0) + RemoveTimedQuest(questId); + } + m_weeklyquests.Clear(); // DB data deleted in caller m_WeeklyQuestChanged = false; @@ -958,7 +994,7 @@ namespace Game.Entities } } - if (!quest.FlagsEx.HasAnyFlag(QuestFlagsEx.KeepAdditionalItems)) + if (!quest.FlagsEx.HasAnyFlag(QuestFlagsEx.NoItemRemoval)) { for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i) { @@ -994,7 +1030,15 @@ namespace Game.Entities } CurrencyGainSource currencyGainSource = CurrencyGainSource.QuestReward; - if (quest.IsDaily()) + + if (quest.HasFlagEx(QuestFlagsEx.RewardsIgnoreCaps)) + { + if (quest.IsWorldQuest()) + currencyGainSource = CurrencyGainSource.WorldQuestRewardIgnoreCaps; + + currencyGainSource = CurrencyGainSource.QuestRewardIgnoreCaps; + } + else if (quest.IsDaily()) currencyGainSource = CurrencyGainSource.DailyQuestReward; else if (quest.IsWeekly()) currencyGainSource = CurrencyGainSource.WeeklyQuestReward; @@ -2050,7 +2094,7 @@ namespace Game.Entities case QuestStatus.Complete: if (quest.GetQuestTag() == QuestTagType.CovenantCalling) result |= quest.HasFlag(QuestFlags.HideRewardPoi) ? QuestGiverStatus.CovenantCallingRewardCompleteNoPOI : QuestGiverStatus.CovenantCallingRewardCompletePOI; - else if (quest.HasFlagEx(QuestFlagsEx.LegendaryQuest)) + else if (quest.HasFlagEx(QuestFlagsEx.Legendary)) result |= quest.HasFlag(QuestFlags.HideRewardPoi) ? QuestGiverStatus.LegendaryRewardCompleteNoPOI : QuestGiverStatus.LegendaryRewardCompletePOI; else result |= quest.HasFlag(QuestFlags.HideRewardPoi) ? QuestGiverStatus.RewardCompleteNoPOI : QuestGiverStatus.RewardCompletePOI; @@ -2093,7 +2137,7 @@ namespace Game.Entities { if (quest.GetQuestTag() == QuestTagType.CovenantCalling) result |= QuestGiverStatus.CovenantCallingQuest; - else if (quest.HasFlagEx(QuestFlagsEx.LegendaryQuest)) + else if (quest.HasFlagEx(QuestFlagsEx.Legendary)) result |= QuestGiverStatus.LegendaryQuest; else if (quest.IsDaily()) result |= QuestGiverStatus.DailyQuest; @@ -2449,6 +2493,10 @@ namespace Game.Entities if (!IsQuestObjectiveCompletable(logSlot, quest, objective)) continue; + if (quest.HasFlagEx(QuestFlagsEx.NoCreditForProxy)) + if (objective.Type == QuestObjectiveType.Monster && victimGuid.IsEmpty()) + continue; + bool objectiveWasComplete = IsQuestObjectiveComplete(logSlot, quest, objective); if (!objectiveWasComplete || addCount < 0) { diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index b85c895b2..c8092a1da 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -1127,6 +1127,8 @@ namespace Game.Entities gainSource == CurrencyGainSource.PlayerTraitRefund) isGainOnRefund = true; + bool ignoreCaps = isGainOnRefund || gainSource == CurrencyGainSource.QuestRewardIgnoreCaps || gainSource == CurrencyGainSource.WorldQuestRewardIgnoreCaps; + if (amount > 0 && !isGainOnRefund && gainSource != CurrencyGainSource.Vendor) { amount = (int)(amount * GetTotalAuraMultiplierByMiscValue(AuraType.ModCurrencyGain, (int)id)); @@ -1164,16 +1166,18 @@ namespace Game.Entities _currencyStorage.Add(id, playerCurrency); } - // Weekly cap uint weeklyCap = GetCurrencyWeeklyCap(currency); - if (weeklyCap != 0 && amount > 0 && (playerCurrency.WeeklyQuantity + amount) > weeklyCap) - if (!isGainOnRefund) // Ignore weekly cap for refund + if (!ignoreCaps) // Ignore weekly cap for refund + { + // Weekly cap + if (weeklyCap != 0 && amount > 0 && (playerCurrency.WeeklyQuantity + amount) > weeklyCap) amount = (int)(weeklyCap - playerCurrency.WeeklyQuantity); - // Max cap - uint maxCap = GetCurrencyMaxQuantity(currency, false, gainSource == CurrencyGainSource.UpdatingVersion); - if (maxCap != 0 && amount > 0 && (playerCurrency.Quantity + amount) > maxCap) - amount = (int)(maxCap - playerCurrency.Quantity); + // Max cap + uint maxCap = GetCurrencyMaxQuantity(currency, false, gainSource == CurrencyGainSource.UpdatingVersion); + if (maxCap != 0 && amount > 0 && (playerCurrency.Quantity + amount) > maxCap) + amount = (int)(maxCap - playerCurrency.Quantity); + } // Underflow protection if (amount < 0 && Math.Abs(amount) > playerCurrency.Quantity) @@ -1187,7 +1191,7 @@ namespace Game.Entities playerCurrency.Quantity += (uint)amount; - if (amount > 0 && !isGainOnRefund) // Ignore total values update for refund + if (amount > 0 && !ignoreCaps) // Ignore total values update for refund { if (weeklyCap != 0) playerCurrency.WeeklyQuantity += (uint)amount; @@ -1198,7 +1202,8 @@ namespace Game.Entities if (currency.HasTotalEarned()) playerCurrency.EarnedQuantity += (uint)amount; - UpdateCriteria(CriteriaType.CurrencyGained, id, (ulong)amount); + if (!isGainOnRefund) + UpdateCriteria(CriteriaType.CurrencyGained, id, (ulong)amount); } CurrencyChanged(id, amount); diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index bde2a2844..1bb7e28f1 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -411,6 +411,13 @@ namespace Game if (quest != null) { + if (quest.HasFlagEx(QuestFlagsEx.NoAbandonOnceBegun)) + { + foreach (QuestObjective objective in quest.Objectives) + if (_player.IsQuestObjectiveComplete(packet.Entry, quest, objective)) + return; + } + if (quest.LimitTime != 0) GetPlayer().RemoveTimedQuest(questId); diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index 735f1a79e..b59a41219 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -20,6 +20,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text; namespace Game { @@ -2012,6 +2013,17 @@ namespace Game player.DailyReset(); } + StringBuilder questIds = new StringBuilder("DELETE cq, cqo FROM character_queststatus cq LEFT JOIN character_queststatus_objectives cqo ON cq.quest = cqo.quest WHERE cq.quest IN ("); + foreach (var (questId, quest) in Global.ObjectMgr.GetQuestTemplates()) + { + if (quest.IsDaily() && quest.HasFlagEx(QuestFlagsEx.RemoveOnPeriodicReset)) + questIds.Append($"{questId},"); + } + questIds.Append("0)"); + + DB.Characters.Execute(questIds.ToString()); + + // reselect pools Global.QuestPoolMgr.ChangeDailyQuests(); @@ -2054,6 +2066,16 @@ namespace Game player.ResetWeeklyQuestStatus(); } + StringBuilder questIds = new StringBuilder("DELETE cq, cqo FROM character_queststatus cq LEFT JOIN character_queststatus_objectives cqo ON cq.quest = cqo.quest WHERE cq.quest IN ("); + foreach (var (questId, quest) in Global.ObjectMgr.GetQuestTemplates()) + { + if (quest.IsWeekly() && quest.HasFlagEx(QuestFlagsEx.RemoveOnWeeklyReset)) + questIds.Append($"{questId},"); + } + questIds.Append("0)"); + + DB.Characters.Execute(questIds.ToString()); + // reselect pools Global.QuestPoolMgr.ChangeWeeklyQuests();