// Copyright (c) CypherCore All rights reserved. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Collections; using Framework.Constants; using Framework.Database; using Game.DataStorage; using Game.Entities; using Game.Miscellaneous; using Game.Networking.Packets; using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Game { public class Quest { public Quest(SQLFields fields) { Id = fields.Read(0); Type = (QuestType)fields.Read(1); PackageID = fields.Read(2); ContentTuningId = fields.Read(3); QuestSortID = fields.Read(4); QuestInfoID = fields.Read(5); SuggestedPlayers = fields.Read(6); NextQuestInChain = fields.Read(7); RewardXPDifficulty = fields.Read(8); RewardXPMultiplier = fields.Read(9); RewardMoneyDifficulty = fields.Read(10); RewardMoneyMultiplier = fields.Read(11); RewardBonusMoney = fields.Read(12); RewardSpell = fields.Read(13); RewardHonor = fields.Read(14); RewardKillHonor = fields.Read(15); SourceItemId = fields.Read(16); RewardArtifactXPDifficulty = fields.Read(17); RewardArtifactXPMultiplier = fields.Read(18); RewardArtifactCategoryID = fields.Read(19); Flags = (QuestFlags)fields.Read(20); FlagsEx = (QuestFlagsEx)fields.Read(21); FlagsEx2 = (QuestFlagsEx2)fields.Read(22); FlagsEx3 = fields.Read(23); for (int i = 0; i < SharedConst.QuestItemDropCount; ++i) { RewardItemId[i] = fields.Read(24 + i * 4); RewardItemCount[i] = fields.Read(25 + i * 4); ItemDrop[i] = fields.Read(26 + i * 4); ItemDropQuantity[i] = fields.Read(27 + i * 4); if (RewardItemId[i] != 0) ++_rewItemsCount; } for (int i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) { RewardChoiceItemId[i] = fields.Read(40 + i * 3); RewardChoiceItemCount[i] = fields.Read(41 + i * 3); RewardChoiceItemDisplayId[i] = fields.Read(42 + i * 3); if (RewardChoiceItemId[i] != 0) ++_rewChoiceItemsCount; } POIContinent = fields.Read(58); POIx = fields.Read(59); POIy = fields.Read(60); POIPriority = fields.Read(61); RewardTitleId = fields.Read(62); RewardArenaPoints = fields.Read(63); RewardSkillId = fields.Read(64); RewardSkillPoints = fields.Read(65); QuestGiverPortrait = fields.Read(66); QuestGiverPortraitMount = fields.Read(67); QuestGiverPortraitModelSceneId = fields.Read(68); QuestTurnInPortrait = fields.Read(69); for (int i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) { RewardFactionId[i] = fields.Read(70 + i * 4); RewardFactionValue[i] = fields.Read(71 + i * 4); RewardFactionOverride[i] = fields.Read(72 + i * 4); RewardFactionCapIn[i] = fields.Read(73 + i * 4); } RewardReputationMask = fields.Read(90); for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) { RewardCurrencyId[i] = fields.Read(91 + i * 2); RewardCurrencyCount[i] = fields.Read(92 + i * 2); } SoundAccept = fields.Read(99); SoundTurnIn = fields.Read(100); AreaGroupID = fields.Read(101); LimitTime = fields.Read(102); AllowableRaces = new(fields.Read(103)); ResetByScheduler = fields.Read(104); Expansion = fields.Read(105); ManagedWorldStateID = fields.Read(106); QuestSessionBonus = fields.Read(107); LogTitle = fields.Read(108); LogDescription = fields.Read(109); QuestDescription = fields.Read(110); AreaDescription = fields.Read(111); PortraitGiverText = fields.Read(112); PortraitGiverName = fields.Read(113); PortraitTurnInText = fields.Read(114); PortraitTurnInName = fields.Read(115); QuestCompletionLog = fields.Read(116); } public void LoadRewardDisplaySpell(SQLFields fields) { uint spellId = fields.Read(1); uint playerConditionId = fields.Read(2); QuestCompleteSpellType type = (QuestCompleteSpellType)fields.Read(3); if (!Global.SpellMgr.HasSpellInfo(spellId, Difficulty.None)) { Log.outError(LogFilter.Sql, $"Table `quest_reward_display_spell` has non-existing Spell ({spellId}) set for quest {Id}. Skipped."); return; } if (playerConditionId != 0 && !CliDB.PlayerConditionStorage.ContainsKey(playerConditionId)) { if (!Global.ConditionMgr.HasConditionsForNotGroupedEntry(ConditionSourceType.PlayerCondition, playerConditionId)) { Log.outError(LogFilter.Sql, $"Table `quest_reward_display_spell` has serverside PlayerCondition ({playerConditionId}) set for quest {Id}. and spell {spellId} without conditions. Set to 0."); playerConditionId = 0; } } if (type >= QuestCompleteSpellType.Max) { Log.outError(LogFilter.Sql, $"Table `quest_reward_display_spell` invalid type value ({type}) set for quest {Id} and spell {spellId}. Set to 0."); type = QuestCompleteSpellType.LegacyBehavior; } RewardDisplaySpell.Add(new QuestRewardDisplaySpell(spellId, playerConditionId, type)); } public void LoadRewardChoiceItems(SQLFields fields) { for (int i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) RewardChoiceItemType[i] = (LootItemType)fields.Read(1 + i); } public void LoadQuestDetails(SQLFields fields) { for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) { ushort emoteId = fields.Read(1 + i); if (!CliDB.EmotesStorage.ContainsKey(emoteId)) { Log.outError(LogFilter.Sql, "Table `quest_details` has non-existing Emote{0} ({1}) set for quest {2}. Skipped.", 1 + i, emoteId, fields.Read(0)); continue; } DetailsEmote[i] = emoteId; } for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) DetailsEmoteDelay[i] = fields.Read(5 + i); } public void LoadQuestRequestItems(SQLFields fields) { EmoteOnComplete = fields.Read(1); EmoteOnIncomplete = fields.Read(2); if (!CliDB.EmotesStorage.ContainsKey(EmoteOnComplete)) Log.outError(LogFilter.Sql, "Table `quest_request_items` has non-existing EmoteOnComplete ({0}) set for quest {1}.", EmoteOnComplete, fields.Read(0)); if (!CliDB.EmotesStorage.ContainsKey(EmoteOnIncomplete)) Log.outError(LogFilter.Sql, "Table `quest_request_items` has non-existing EmoteOnIncomplete ({0}) set for quest {1}.", EmoteOnIncomplete, fields.Read(0)); EmoteOnCompleteDelay = fields.Read(3); EmoteOnIncompleteDelay = fields.Read(4); RequestItemsText = fields.Read(5); } public void LoadQuestOfferReward(SQLFields fields) { for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) { short emoteId = fields.Read(1 + i); if (emoteId < 0 || !CliDB.EmotesStorage.ContainsKey(emoteId)) { Log.outError(LogFilter.Sql, "Table `quest_offer_reward` has non-existing Emote{0} ({1}) set for quest {2}. Skipped.", 1 + i, emoteId, fields.Read(0)); continue; } OfferRewardEmote[i] = emoteId; } for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) OfferRewardEmoteDelay[i] = fields.Read(5 + i); OfferRewardText = fields.Read(9); } public void LoadQuestTemplateAddon(SQLFields fields) { MaxLevel = fields.Read(1); AllowableClasses = fields.Read(2); SourceSpellID = fields.Read(3); PrevQuestId = fields.Read(4); NextQuestId = fields.Read(5); ExclusiveGroup = fields.Read(6); BreadcrumbForQuestId = fields.Read(7); RewardMailTemplateId = fields.Read(8); RewardMailDelay = fields.Read(9); RequiredSkillId = fields.Read(10); RequiredSkillPoints = fields.Read(11); RequiredMinRepFaction = fields.Read(12); RequiredMaxRepFaction = fields.Read(13); RequiredMinRepValue = fields.Read(14); RequiredMaxRepValue = fields.Read(15); SourceItemIdCount = fields.Read(16); SpecialFlags = (QuestSpecialFlags)fields.Read(17); ScriptId = Global.ObjectMgr.GetScriptId(fields.Read(18)); if (SpecialFlags.HasAnyFlag(QuestSpecialFlags.AutoAccept)) Flags |= QuestFlags.AutoAccept; } public void LoadQuestMailSender(SQLFields fields) { RewardMailSenderEntry = fields.Read(1); } public void LoadQuestObjective(SQLFields fields) { QuestObjective obj = new(); obj.QuestID = fields.Read(0); obj.Id = fields.Read(1); obj.Type = (QuestObjectiveType)fields.Read(2); obj.StorageIndex = fields.Read(3); obj.ObjectID = fields.Read(4); obj.Amount = fields.Read(5); obj.Flags = (QuestObjectiveFlags)fields.Read(6); obj.Flags2 = (QuestObjectiveFlags2)fields.Read(7); obj.ProgressBarWeight = fields.Read(8); obj.Description = fields.Read(9); bool hasCompletionEffect = false; for (var i = 10; i < 15; ++i) { if (!fields.IsNull(i)) { hasCompletionEffect = true; break; } } if (hasCompletionEffect) { obj.CompletionEffect = new QuestObjectiveAction(); if (!fields.IsNull(10)) obj.CompletionEffect.GameEventId = fields.Read(10); if (!fields.IsNull(11)) obj.CompletionEffect.SpellId = fields.Read(11); if (!fields.IsNull(12)) obj.CompletionEffect.ConversationId = fields.Read(12); if (!fields.IsNull(13)) obj.CompletionEffect.UpdatePhaseShift = fields.Read(13); if (!fields.IsNull(14)) obj.CompletionEffect.UpdateZoneAuras = fields.Read(14); } Objectives.Add(obj); _usedQuestObjectiveTypes[(int)obj.Type] = true; } public void LoadQuestObjectiveVisualEffect(SQLFields fields) { uint objID = fields.Read(1); foreach (QuestObjective obj in Objectives) { if (obj.Id == objID) { byte effectIndex = fields.Read(3); if (obj.VisualEffects == null) obj.VisualEffects = new int[effectIndex + 1]; if (effectIndex >= obj.VisualEffects.Length) Array.Resize(ref obj.VisualEffects, effectIndex + 1); obj.VisualEffects[effectIndex] = fields.Read(4); break; } } } public void LoadConditionalConditionalQuestDescription(SQLFields fields) { Locale locale = fields.Read(4).ToEnum(); if (!WorldConfig.GetBoolValue(WorldCfg.LoadLocales) && locale != SharedConst.DefaultLocale) return; if (locale >= Locale.Total) { Log.outError(LogFilter.Sql, $"Table `quest_description_conditional` has invalid locale {fields.Read(4)} set for quest {fields.Read(0)}. Skipped."); return; } QuestConditionalText text = ConditionalQuestDescription.Find(text => text.PlayerConditionId == fields.Read(1) && text.QuestgiverCreatureId == fields.Read(2)); if (text == null) { text = new(); ConditionalQuestDescription.Add(text); } text.PlayerConditionId = fields.Read(1); text.QuestgiverCreatureId = fields.Read(2); ObjectManager.AddLocaleString(fields.Read(3), locale, text.Text); } public void LoadConditionalConditionalRequestItemsText(SQLFields fields) { Locale locale = fields.Read(4).ToEnum(); if (!WorldConfig.GetBoolValue(WorldCfg.LoadLocales) && locale != SharedConst.DefaultLocale) return; if (locale >= Locale.Total) { Log.outError(LogFilter.Sql, $"Table `quest_request_items_conditional` has invalid locale {fields.Read(4)} set for quest {fields.Read(0)}. Skipped."); return; } QuestConditionalText text = ConditionalRequestItemsText.Find(text => text.PlayerConditionId == fields.Read(1) && text.QuestgiverCreatureId == fields.Read(2)); if (text == null) { text = new(); ConditionalRequestItemsText.Add(text); } text.PlayerConditionId = fields.Read(1); text.QuestgiverCreatureId = fields.Read(2); ObjectManager.AddLocaleString(fields.Read(3), locale, text.Text); } public void LoadConditionalConditionalOfferRewardText(SQLFields fields) { Locale locale = fields.Read(4).ToEnum(); if (!WorldConfig.GetBoolValue(WorldCfg.LoadLocales) && locale != SharedConst.DefaultLocale) return; if (locale >= Locale.Total) { Log.outError(LogFilter.Sql, $"Table `quest_offer_reward_conditional` has invalid locale {fields.Read(4)} set for quest {fields.Read(0)}. Skipped."); return; } QuestConditionalText text = ConditionalOfferRewardText.Find(text => text.PlayerConditionId == fields.Read(1) && text.QuestgiverCreatureId == fields.Read(2)); if (text == null) { text = new(); ConditionalOfferRewardText.Add(text); } text.PlayerConditionId = fields.Read(1); text.QuestgiverCreatureId = fields.Read(2); ObjectManager.AddLocaleString(fields.Read(3), locale, text.Text); } public void LoadConditionalConditionalQuestCompletionLog(SQLFields fields) { Locale locale = fields.Read(4).ToEnum(); if (!WorldConfig.GetBoolValue(WorldCfg.LoadLocales) && locale != SharedConst.DefaultLocale) return; if (locale >= Locale.Total) { Log.outError(LogFilter.Sql, $"Table `quest_completion_log_conditional` has invalid locale {fields.Read(4)} set for quest {fields.Read(0)}. Skipped."); return; } QuestConditionalText text = ConditionalQuestCompletionLog.Find(text => text.PlayerConditionId == fields.Read(1) && text.QuestgiverCreatureId == fields.Read(2)); if (text == null) { text = new(); ConditionalQuestCompletionLog.Add(text); } text.PlayerConditionId = fields.Read(1); text.QuestgiverCreatureId = fields.Read(2); ObjectManager.AddLocaleString(fields.Read(3), locale, text.Text); } public void LoadTreasurePickers(SQLFields fields) { TreasurePickerID.Add(fields.Read(1)); } public uint XPValue(Player player) { return XPValue(player, ContentTuningId, RewardXPDifficulty, RewardXPMultiplier, Expansion); } public static uint XPValue(Player player, uint contentTuningId, uint xpDifficulty, float xpMultiplier = 1.0f, int expansion = -1) { if (player != null) { uint questLevel = (uint)player.GetQuestLevel(contentTuningId); QuestXPRecord questXp = CliDB.QuestXPStorage.LookupByKey(questLevel); if (questXp == null || xpDifficulty >= 10) return 0; uint xp = questXp.Difficulty[xpDifficulty]; var contentTuning = CliDB.ContentTuningStorage.LookupByKey(contentTuningId); if (contentTuning != null) xp = (uint)(xp * contentTuning.QuestXpMultiplier); int diffFactor = (int)(2 * (questLevel - player.GetLevel()) + 12); if (diffFactor < 1) diffFactor = 1; else if (diffFactor > 10) diffFactor = 10; xp = (uint)(diffFactor * xp * xpMultiplier / 10); if (player.GetLevel() >= Global.ObjectMgr.GetMaxLevelForExpansion(PlayerConst.CurrentExpansion - 1) && player.GetSession().GetExpansion() == PlayerConst.CurrentExpansion && expansion >= 0 && expansion < (int)PlayerConst.CurrentExpansion) xp = (uint)(xp / 9.0f); xp = RoundXPValue(xp); if (WorldConfig.GetUIntValue(WorldCfg.MinQuestScaledXpRatio) != 0) { uint minScaledXP = RoundXPValue((uint)(questXp.Difficulty[xpDifficulty] * xpMultiplier)) * WorldConfig.GetUIntValue(WorldCfg.MinQuestScaledXpRatio) / 100; xp = Math.Max(minScaledXP, xp); } return xp; } return 0; } public static bool IsTakingQuestEnabled(uint questId) { if (!Global.QuestPoolMgr.IsQuestActive(questId)) return false; return true; } public uint MoneyValue(Player player) { QuestMoneyRewardRecord money = CliDB.QuestMoneyRewardStorage.LookupByKey(player.GetQuestLevel(this)); if (money != null) return (uint)(money.Difficulty[RewardMoneyDifficulty] * RewardMoneyMultiplier); else return 0; } public uint MaxMoneyValue() { uint value = 0; var questLevels = Global.DB2Mgr.GetContentTuningData(ContentTuningId, 0); if (questLevels.HasValue) { var money = CliDB.QuestMoneyRewardStorage.LookupByKey(questLevels.Value.MaxLevel); if (money != null) value = (uint)(money.Difficulty[RewardMoneyDifficulty] * RewardMoneyMultiplier); } return value; } public uint GetMaxMoneyReward() { return (uint)(MaxMoneyValue() * WorldConfig.GetFloatValue(WorldCfg.RateMoneyQuest)); } public QuestTagType? GetQuestTag() { QuestInfoRecord questInfo = CliDB.QuestInfoStorage.LookupByKey(QuestInfoID); if (questInfo != null) return (QuestTagType)questInfo.Type; return null; } public bool IsImportant() { var questInfo = CliDB.QuestInfoStorage.LookupByKey(QuestInfoID); if (questInfo != null) return (questInfo.Modifiers & 0x400) != 0; return false; } public bool IsMeta() { var questInfo = CliDB.QuestInfoStorage.LookupByKey(QuestInfoID); if (questInfo != null) return (questInfo.Modifiers & 0x800) != 0; return false; } public bool IsResetByScheduler() { return ResetByScheduler; } public void BuildQuestRewards(QuestRewards rewards, Player player) { rewards.ChoiceItemCount = GetRewChoiceItemsCount(); rewards.ItemCount = GetRewItemsCount(); rewards.Money = player.GetQuestMoneyReward(this); rewards.XP = player.GetQuestXPReward(this); rewards.ArtifactCategoryID = RewardArtifactCategoryID; rewards.Title = RewardTitleId; rewards.FactionFlags = RewardReputationMask; var displaySpellIndex = 0; foreach (QuestRewardDisplaySpell displaySpell in RewardDisplaySpell) { if (!ConditionManager.IsPlayerMeetingCondition(player, displaySpell.PlayerConditionId)) continue; rewards.SpellCompletionDisplayID[displaySpellIndex] = (int)displaySpell.SpellId; if (++displaySpellIndex >= rewards.SpellCompletionDisplayID.Length) break; } rewards.SpellCompletionID = RewardSpell; rewards.SkillLineID = RewardSkillId; rewards.NumSkillUps = RewardSkillPoints; rewards.TreasurePickerID = TreasurePickerID; for (int i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) { rewards.ChoiceItems[i].LootItemType = RewardChoiceItemType[i]; rewards.ChoiceItems[i].Item = new ItemInstance(); rewards.ChoiceItems[i].Item.ItemID = RewardChoiceItemId[i]; rewards.ChoiceItems[i].Quantity = RewardChoiceItemCount[i]; } for (int i = 0; i < SharedConst.QuestRewardItemCount; ++i) { rewards.Items[i].ItemID = RewardItemId[i]; rewards.Items[i].ItemQty = RewardItemCount[i]; } for (int i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) { rewards.FactionID[i] = RewardFactionId[i]; rewards.FactionOverride[i] = RewardFactionOverride[i]; rewards.FactionValue[i] = RewardFactionValue[i]; rewards.FactionCapIn[i] = RewardFactionCapIn[i]; } for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) { rewards.Currencies[i].CurrencyID = RewardCurrencyId[i]; rewards.Currencies[i].CurrencyQty = RewardCurrencyCount[i]; } } public uint GetRewMoneyMaxLevel() { // If Quest has flag to not give money on max level, it's 0 if (HasFlag(QuestFlags.NoMoneyForXp)) return 0; // Else, return the rewarded copper sum modified by the rate return (uint)(RewardBonusMoney * WorldConfig.GetFloatValue(WorldCfg.RateMoneyMaxLevelQuest)); } public bool IsAutoAccept() { return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoAccept) && HasFlag(QuestFlags.AutoAccept); } public bool IsTurnIn() { return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoComplete) && Type == QuestType.TurnIn; } public bool IsRaidQuest(Difficulty difficulty) { switch ((QuestInfos)QuestInfoID) { case QuestInfos.Raid: return true; case QuestInfos.Raid10: return difficulty == Difficulty.Raid10N || difficulty == Difficulty.Raid10HC; case QuestInfos.Raid25: return difficulty == Difficulty.Raid25N || difficulty == Difficulty.Raid25HC; default: break; } if (Flags.HasAnyFlag(QuestFlags.RaidGroupOk)) return true; return false; } public bool IsAllowedInRaid(Difficulty difficulty) { if (IsRaidQuest(difficulty)) return true; return WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreRaid); } public uint CalculateHonorGain(uint level) { uint honor = 0; return honor; } public bool CanIncreaseRewardedQuestCounters() { // Dungeon Finder/Daily/Repeatable (if not weekly, monthly or seasonal) quests are never considered rewarded serverside. // This affects counters and client requests for completed quests. return (!IsDFQuest() && !IsDaily() && (!IsRepeatable() || IsWeekly() || IsMonthly() || IsSeasonal())); } public void InitializeQueryData() { for (var loc = Locale.enUS; loc < Locale.Total; ++loc) { if (!WorldConfig.GetBoolValue(WorldCfg.LoadLocales) && loc != SharedConst.DefaultLocale) continue; response[(int)loc] = BuildQueryData(loc, null); } } public QueryQuestInfoResponse BuildQueryData(Locale loc, Player player) { QueryQuestInfoResponse response = new(); response.Allow = true; response.QuestID = Id; response.Info.LogTitle = LogTitle; response.Info.LogDescription = LogDescription; response.Info.QuestDescription = QuestDescription; response.Info.AreaDescription = AreaDescription; response.Info.QuestCompletionLog = QuestCompletionLog; response.Info.PortraitGiverText = PortraitGiverText; response.Info.PortraitGiverName = PortraitGiverName; response.Info.PortraitTurnInText = PortraitTurnInText; response.Info.PortraitTurnInName = PortraitTurnInName; response.Info.ConditionalQuestDescription = ConditionalQuestDescription.Select(text => { string content = text.Text[(int)Locale.enUS]; ObjectManager.GetLocaleString(text.Text, loc, ref content); return new ConditionalQuestText(text.PlayerConditionId, text.QuestgiverCreatureId, content); }).ToList(); response.Info.ConditionalQuestCompletionLog = ConditionalQuestCompletionLog.Select(text => { string content = text.Text[(int)Locale.enUS]; ObjectManager.GetLocaleString(text.Text, loc, ref content); return new ConditionalQuestText(text.PlayerConditionId, text.QuestgiverCreatureId, content); }).ToList(); if (loc != Locale.enUS) { var questTemplateLocale = Global.ObjectMgr.GetQuestLocale(Id); if (questTemplateLocale != null) { ObjectManager.GetLocaleString(questTemplateLocale.LogTitle, loc, ref response.Info.LogTitle); ObjectManager.GetLocaleString(questTemplateLocale.LogDescription, loc, ref response.Info.LogDescription); ObjectManager.GetLocaleString(questTemplateLocale.QuestDescription, loc, ref response.Info.QuestDescription); ObjectManager.GetLocaleString(questTemplateLocale.AreaDescription, loc, ref response.Info.AreaDescription); ObjectManager.GetLocaleString(questTemplateLocale.QuestCompletionLog, loc, ref response.Info.QuestCompletionLog); ObjectManager.GetLocaleString(questTemplateLocale.PortraitGiverText, loc, ref response.Info.PortraitGiverText); ObjectManager.GetLocaleString(questTemplateLocale.PortraitGiverName, loc, ref response.Info.PortraitGiverName); ObjectManager.GetLocaleString(questTemplateLocale.PortraitTurnInText, loc, ref response.Info.PortraitTurnInText); ObjectManager.GetLocaleString(questTemplateLocale.PortraitTurnInName, loc, ref response.Info.PortraitTurnInName); } } response.Info.QuestID = Id; response.Info.QuestType = (int)Type; response.Info.ContentTuningID = ContentTuningId; response.Info.QuestPackageID = PackageID; response.Info.QuestSortID = QuestSortID; response.Info.QuestInfoID = QuestInfoID; response.Info.SuggestedGroupNum = SuggestedPlayers; response.Info.RewardNextQuest = NextQuestInChain; response.Info.RewardXPDifficulty = RewardXPDifficulty; response.Info.RewardXPMultiplier = RewardXPMultiplier; if (!HasFlag(QuestFlags.HideReward)) response.Info.RewardMoney = (int)(player != null ? player.GetQuestMoneyReward(this) : GetMaxMoneyReward()); response.Info.RewardMoneyDifficulty = RewardMoneyDifficulty; response.Info.RewardMoneyMultiplier = RewardMoneyMultiplier; response.Info.RewardBonusMoney = RewardBonusMoney; foreach (QuestRewardDisplaySpell displaySpell in RewardDisplaySpell) { QuestCompleteDisplaySpell rewardDisplaySpell = new(); rewardDisplaySpell.SpellID = displaySpell.SpellId; rewardDisplaySpell.PlayerConditionID = displaySpell.PlayerConditionId; rewardDisplaySpell.Type = (int)displaySpell.Type; response.Info.RewardDisplaySpell.Add(rewardDisplaySpell); } response.Info.RewardSpell = RewardSpell; response.Info.RewardHonor = RewardHonor; response.Info.RewardKillHonor = RewardKillHonor; response.Info.RewardArtifactXPDifficulty = (int)RewardArtifactXPDifficulty; response.Info.RewardArtifactXPMultiplier = RewardArtifactXPMultiplier; response.Info.RewardArtifactCategoryID = (int)RewardArtifactCategoryID; response.Info.StartItem = SourceItemId; response.Info.Flags = (uint)Flags; response.Info.FlagsEx = (uint)FlagsEx; response.Info.FlagsEx2 = (uint)FlagsEx2; response.Info.FlagsEx3 = FlagsEx3; response.Info.RewardTitle = RewardTitleId; response.Info.RewardArenaPoints = RewardArenaPoints; response.Info.RewardSkillLineID = RewardSkillId; response.Info.RewardNumSkillUps = RewardSkillPoints; response.Info.RewardFactionFlags = RewardReputationMask; response.Info.PortraitGiver = QuestGiverPortrait; response.Info.PortraitGiverMount = QuestGiverPortraitMount; response.Info.PortraitGiverModelSceneID = QuestGiverPortraitModelSceneId; response.Info.PortraitTurnIn = QuestTurnInPortrait; for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i) { response.Info.ItemDrop[i] = (int)ItemDrop[i]; response.Info.ItemDropQuantity[i] = (int)ItemDropQuantity[i]; } if (!HasFlag(QuestFlags.HideReward)) { for (byte i = 0; i < SharedConst.QuestRewardItemCount; ++i) { response.Info.RewardItems[i] = RewardItemId[i]; response.Info.RewardAmount[i] = RewardItemCount[i]; } for (byte i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) { response.Info.UnfilteredChoiceItems[i].ItemID = RewardChoiceItemId[i]; response.Info.UnfilteredChoiceItems[i].Quantity = RewardChoiceItemCount[i]; } } for (byte i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) { response.Info.RewardFactionID[i] = RewardFactionId[i]; response.Info.RewardFactionValue[i] = RewardFactionValue[i]; response.Info.RewardFactionOverride[i] = RewardFactionOverride[i]; response.Info.RewardFactionCapIn[i] = RewardFactionCapIn[i]; } response.Info.POIContinent = POIContinent; response.Info.POIx = POIx; response.Info.POIy = POIy; response.Info.POIPriority = POIPriority; response.Info.AllowableRaces = AllowableRaces; response.Info.TreasurePickerID = TreasurePickerID; response.Info.Expansion = Expansion; response.Info.ManagedWorldStateID = ManagedWorldStateID; response.Info.QuestSessionBonus = 0; //GetQuestSessionBonus(); // this is only sent while quest session is active response.Info.QuestGiverCreatureID = 0; // only sent during npc interaction foreach (QuestObjective questObjective in Objectives) { response.Info.Objectives.Add(questObjective); if (loc != Locale.enUS) { var questObjectivesLocale = Global.ObjectMgr.GetQuestObjectivesLocale(questObjective.Id); if (questObjectivesLocale != null) ObjectManager.GetLocaleString(questObjectivesLocale.Description, loc, ref response.Info.Objectives.Last().Description); } } for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) { response.Info.RewardCurrencyID[i] = RewardCurrencyId[i]; response.Info.RewardCurrencyQty[i] = RewardCurrencyCount[i]; } response.Info.AcceptedSoundKitID = SoundAccept; response.Info.CompleteSoundKitID = SoundTurnIn; response.Info.AreaGroupID = AreaGroupID; response.Info.TimeAllowed = LimitTime; response.Info.ResetByScheduler = IsResetByScheduler(); response.Write(); return response; } public static uint RoundXPValue(uint xp) { if (xp <= 100) return 5 * ((xp + 2) / 5); else if (xp <= 500) return 10 * ((xp + 5) / 10); else if (xp <= 1000) return 25 * ((xp + 12) / 25); else return 50 * ((xp + 25) / 50); } public bool HasFlag(QuestFlags flag) { return (Flags & flag) != 0; } public bool HasFlagEx(QuestFlagsEx flag) { return (FlagsEx & flag) != 0; } public bool HasFlagEx(QuestFlagsEx2 flag) { return (FlagsEx2 & flag) != 0; } 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); } // Possibly deprecated flag public bool IsUnavailable() { return HasFlag(QuestFlags.Deprecated); } // table data accessors: public bool IsRepeatable() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable); } public bool IsDaily() { return Flags.HasAnyFlag(QuestFlags.Daily); } public bool IsWeekly() { return Flags.HasAnyFlag(QuestFlags.Weekly); } public bool IsMonthly() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.Monthly); } public bool IsSeasonal() { return (QuestSortID == -(int)QuestSort.Seasonal || QuestSortID == -(int)QuestSort.Special || QuestSortID == -(int)QuestSort.LunarFestival || QuestSortID == -(int)QuestSort.Midsummer || QuestSortID == -(int)QuestSort.Brewfest || QuestSortID == -(int)QuestSort.LoveIsInTheAir || QuestSortID == -(int)QuestSort.Noblegarden) && !IsRepeatable(); } 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 void SetEventIdForQuest(ushort eventId) { _eventIdForQuest = eventId; } public ushort GetEventIdForQuest() { return _eventIdForQuest; } #region Fields public uint Id; public QuestType Type; public uint PackageID; public uint ContentTuningId; public int QuestSortID; public uint QuestInfoID; public uint SuggestedPlayers; public uint NextQuestInChain { get; set; } public uint RewardXPDifficulty; public float RewardXPMultiplier; public uint RewardMoneyDifficulty; public float RewardMoneyMultiplier; public uint RewardBonusMoney; public List RewardDisplaySpell = new(); public uint RewardSpell { get; set; } public uint RewardHonor; public uint RewardKillHonor; public uint RewardArtifactXPDifficulty; public float RewardArtifactXPMultiplier; public uint RewardArtifactCategoryID; public uint SourceItemId { get; set; } public QuestFlags Flags { get; set; } public QuestFlagsEx FlagsEx; public QuestFlagsEx2 FlagsEx2; public uint FlagsEx3; public uint[] RewardItemId = new uint[SharedConst.QuestRewardItemCount]; public uint[] RewardItemCount = new uint[SharedConst.QuestRewardItemCount]; public uint[] ItemDrop = new uint[SharedConst.QuestItemDropCount]; public uint[] ItemDropQuantity = new uint[SharedConst.QuestItemDropCount]; public LootItemType[] RewardChoiceItemType = new LootItemType[SharedConst.QuestRewardChoicesCount]; public uint[] RewardChoiceItemId = new uint[SharedConst.QuestRewardChoicesCount]; public uint[] RewardChoiceItemCount = new uint[SharedConst.QuestRewardChoicesCount]; public uint[] RewardChoiceItemDisplayId = new uint[SharedConst.QuestRewardChoicesCount]; public uint POIContinent; public float POIx; public float POIy; public uint POIPriority; public uint RewardTitleId { get; set; } public int RewardArenaPoints; public uint RewardSkillId; public uint RewardSkillPoints; public uint QuestGiverPortrait; public uint QuestGiverPortraitMount; public int QuestGiverPortraitModelSceneId; public uint QuestTurnInPortrait; public uint[] RewardFactionId = new uint[SharedConst.QuestRewardReputationsCount]; public int[] RewardFactionValue = new int[SharedConst.QuestRewardReputationsCount]; public int[] RewardFactionOverride = new int[SharedConst.QuestRewardReputationsCount]; public int[] RewardFactionCapIn = new int[SharedConst.QuestRewardReputationsCount]; public uint RewardReputationMask; public uint[] RewardCurrencyId = new uint[SharedConst.QuestRewardCurrencyCount]; public uint[] RewardCurrencyCount = new uint[SharedConst.QuestRewardCurrencyCount]; public uint SoundAccept { get; set; } public uint SoundTurnIn { get; set; } public uint AreaGroupID; public long LimitTime; public RaceMask AllowableRaces { get; set; } public List TreasurePickerID = new(); public int Expansion; public int ManagedWorldStateID; public int QuestSessionBonus; public List Objectives = new(); public string LogTitle = ""; public string LogDescription = ""; public string QuestDescription = ""; public string AreaDescription = ""; public string PortraitGiverText = ""; public string PortraitGiverName = ""; public string PortraitTurnInText = ""; public string PortraitTurnInName = ""; public string QuestCompletionLog = ""; public bool ResetByScheduler; // quest_description_conditional public List ConditionalQuestDescription = new(); // quest_completion_log_conditional public List ConditionalQuestCompletionLog = new(); // quest_detais table public uint[] DetailsEmote = new uint[SharedConst.QuestEmoteCount]; public uint[] DetailsEmoteDelay = new uint[SharedConst.QuestEmoteCount]; // quest_request_items table public uint EmoteOnComplete; public uint EmoteOnIncomplete; public uint EmoteOnCompleteDelay; public uint EmoteOnIncompleteDelay; public string RequestItemsText = ""; // quest_request_items_conditional public List ConditionalRequestItemsText = new(); // quest_offer_reward table public int[] OfferRewardEmote = new int[SharedConst.QuestEmoteCount]; public uint[] OfferRewardEmoteDelay = new uint[SharedConst.QuestEmoteCount]; public string OfferRewardText = ""; // quest_offer_reward_conditional public List ConditionalOfferRewardText = new(); // quest_template_addon table (custom data) public uint MaxLevel { get; set; } public uint AllowableClasses { get; set; } public uint SourceSpellID { get; set; } public int PrevQuestId { get; set; } public uint NextQuestId { get; set; } public int ExclusiveGroup { get; set; } public int BreadcrumbForQuestId { get; set; } public uint RewardMailTemplateId { get; set; } public uint RewardMailDelay { get; set; } public uint RequiredSkillId { get; set; } public uint RequiredSkillPoints { get; set; } public uint RequiredMinRepFaction { get; set; } public int RequiredMinRepValue { get; set; } public uint RequiredMaxRepFaction { get; set; } public int RequiredMaxRepValue { get; set; } public uint SourceItemIdCount { get; set; } public uint RewardMailSenderEntry { get; set; } public QuestSpecialFlags SpecialFlags { get; set; } // custom flags, not sniffed/WDB public BitArray _usedQuestObjectiveTypes = new((int)QuestObjectiveType.Max); public uint ScriptId { get; set; } public List DependentPreviousQuests = new(); public List DependentBreadcrumbQuests = new(); public QueryQuestInfoResponse[] response = new QueryQuestInfoResponse[(int)Locale.Total]; uint _rewChoiceItemsCount; uint _rewItemsCount; ushort _eventIdForQuest; #endregion } public class QuestStatusData { public ushort Slot = SharedConst.MaxQuestLogSize; public QuestStatus Status; public long AcceptTime; public uint Timer; public bool Explored; public List<(sbyte, uint)> SpawnTrackingList = new(); } public class QuestGreeting { public QuestGreeting() { Text = ""; } public QuestGreeting(ushort emoteType, uint emoteDelay, string text) { EmoteType = emoteType; EmoteDelay = emoteDelay; Text = text; } public ushort EmoteType; public uint EmoteDelay; public string Text; } public class QuestGreetingLocale { public StringArray Greeting = new((int)Locale.Total); } public class QuestTemplateLocale { public StringArray LogTitle = new((int)Locale.Total); public StringArray LogDescription = new((int)Locale.Total); public StringArray QuestDescription = new((int)Locale.Total); public StringArray AreaDescription = new((int)Locale.Total); public StringArray PortraitGiverText = new((int)Locale.Total); public StringArray PortraitGiverName = new((int)Locale.Total); public StringArray PortraitTurnInText = new((int)Locale.Total); public StringArray PortraitTurnInName = new((int)Locale.Total); public StringArray QuestCompletionLog = new((int)Locale.Total); } public class QuestRequestItemsLocale { public StringArray CompletionText = new((int)Locale.Total); } public class QuestObjectivesLocale { public StringArray Description = new((int)Locale.Total); } public class QuestOfferRewardLocale { public StringArray RewardText = new((int)Locale.Total); } public struct QuestRewardDisplaySpell { public uint SpellId; public uint PlayerConditionId; public QuestCompleteSpellType Type; public QuestRewardDisplaySpell(uint spellId, uint playerConditionId, QuestCompleteSpellType type) { SpellId = spellId; PlayerConditionId = playerConditionId; Type = type; } } public class QuestConditionalText { public int PlayerConditionId; public int QuestgiverCreatureId; public StringArray Text = new((int)Locale.Total); } public class QuestObjectiveAction { public uint? GameEventId; public uint? SpellId; public uint? ConversationId; public bool UpdatePhaseShift; public bool UpdateZoneAuras; } public class QuestObjective { public uint Id; public uint QuestID; public QuestObjectiveType Type; public sbyte StorageIndex; public int ObjectID; public int Amount; public QuestObjectiveFlags Flags; public QuestObjectiveFlags2 Flags2; public float ProgressBarWeight; public string Description; public int[] VisualEffects = Array.Empty(); public QuestObjectiveAction CompletionEffect; public bool IsStoringValue() { switch (Type) { case QuestObjectiveType.Monster: case QuestObjectiveType.Item: case QuestObjectiveType.GameObject: case QuestObjectiveType.TalkTo: case QuestObjectiveType.PlayerKills: case QuestObjectiveType.WinPvpPetBattles: case QuestObjectiveType.HaveCurrency: case QuestObjectiveType.ObtainCurrency: case QuestObjectiveType.IncreaseReputation: case QuestObjectiveType.KillWithLabel: return true; default: break; } return false; } public bool IsStoringFlag() { switch (Type) { case QuestObjectiveType.AreaTrigger: case QuestObjectiveType.WinPetBattleAgainstNpc: case QuestObjectiveType.DefeatBattlePet: case QuestObjectiveType.CriteriaTree: case QuestObjectiveType.AreaTriggerEnter: case QuestObjectiveType.AreaTriggerExit: return true; default: break; } return false; } public static bool CanAlwaysBeProgressedInRaid(QuestObjectiveType type) { switch (type) { case QuestObjectiveType.Item: case QuestObjectiveType.Currency: case QuestObjectiveType.LearnSpell: case QuestObjectiveType.MinReputation: case QuestObjectiveType.MaxReputation: case QuestObjectiveType.Money: case QuestObjectiveType.HaveCurrency: case QuestObjectiveType.IncreaseReputation: return true; default: break; } return false; } } }