using AssortGenerator.Common.Helpers; using QuestValidator.Common; using QuestValidator.Common.Helpers; using QuestValidator.Helpers; using QuestValidator.Models; using System; using System.Collections.Generic; using System.Linq; namespace QuestValidator { class Program { static void Main(string[] args) { var inputPath = DiskHelpers.CreateWorkingFolders(); InputFileHelper.SetInputFiles(inputPath); //read in quest file var questData = QuestHelper.GetQuestData(); var liveQuestData = QuestHelper.GetLiveQuestData(); if (questData is null || liveQuestData is null) { LoggingHelpers.LogError("Unable to read quest data. Are you sure the both quest files are in 'QuestValidator//bin//Debug//netcoreapp3.1//input'"); return; } ListMissingQuestsInLive(questData, liveQuestData); CheckForMissingQuestsInAkiFile(liveQuestData, questData); CheckForMissingQuestIcons(questData); foreach (var item in questData) { var quest = item.Value; LogQuestDetails(quest); // Get live quest var relatedLiveQuest = liveQuestData.data.FirstOrDefault(x => x._id == quest._id); if (!ItemExists(relatedLiveQuest, "live quest. Live dump too old ?")) { LoggingHelpers.LogInfo(""); continue; } CheckRootItemValues(quest, relatedLiveQuest); CheckSuccessRewardItems(quest, relatedLiveQuest); CheckStartedRewardItems(quest, relatedLiveQuest); CheckAvailableForFinishConditionItems(quest, relatedLiveQuest); CheckAvailableForStartConditionItems(quest, relatedLiveQuest); CheckFailConditionItems(quest, relatedLiveQuest); LoggingHelpers.LogInfo(""); LoggingHelpers.LogInfo("-----"); LoggingHelpers.LogInfo(""); } } private static void CheckForMissingQuestIcons(Dictionary questData) { string iconPath = "TODO add path here Server\\project\\assets\\images\\quests"; if (iconPath.Contains("TODO add path here")) { throw new Exception("Add the correct icon path to Program.cs"); } foreach (var quest in questData) { var imageId = quest.Value.image.Split("/")[4].Split(".")[0]; if (!QuestHelper.DoesIconExist(iconPath, imageId)) { LoggingHelpers.LogError($"ERROR - Quest {quest.Value.QuestName} is missing an icon file, add {imageId}.png"); } } } private static void ListMissingQuestsInLive(Dictionary questData, QuestRoot liveQuestData) { var missingQuests = new List(); foreach (var quest in questData.Values) { var liveQuest = liveQuestData.data.FirstOrDefault(x=>x._id == quest._id); if (liveQuest is null) { missingQuests.Add(quest); LoggingHelpers.LogError($"ERROR Quest {quest._id} {QuestHelper.GetQuestNameById(quest._id)} missing in live"); } } } private static void CheckFailConditionItems(Quest quest, Quest relatedLiveQuest) { var liveFailConditions = relatedLiveQuest.conditions.Fail; // Check count CheckValuesMatch(quest.conditions.Fail.Count, liveFailConditions.Count, "FailCondition mismatch"); foreach (var failItem in quest.conditions.Fail) { var liveFailItem = liveFailConditions.FirstOrDefault(x => x._props.id == failItem._props.id); if (!ItemExists(liveFailItem, "condition fail item", failItem._props.index.Value)) { continue; } // Check parentId CheckValuesMatch(failItem._props.parentId, liveFailItem._props.parentId, "AvailableForFinish parentId mismatch", failItem._props.id); } } private static void CheckAvailableForStartConditionItems(Quest quest, Quest relatedLiveQuest) { var liveStartConditions = relatedLiveQuest.conditions.AvailableForStart; // Check count CheckValuesMatch(quest.conditions.AvailableForStart.Count, liveStartConditions.Count, "AvailableForStartCondition mismtch"); foreach (var availableForStartItem in quest.conditions.AvailableForStart) { var liveStartItem = liveStartConditions.FirstOrDefault(x=>x._props.id == availableForStartItem._props.id); if (!ItemExists(liveStartItem, "AvailableForStart item", availableForStartItem._props.index.Value)) { continue; } // Check parentId CheckValuesMatch(availableForStartItem._props.parentId, liveStartItem._props.parentId, "AvailableForFinish parentId mismatch", availableForStartItem._props.id); } } private static void LogQuestDetails(Quest quest) { var questName = QuestHelper.GetQuestNameById(quest._id); var trader = TraderHelper.GetTraderTypeById(quest.traderId); LoggingHelpers.LogInfo($"### Quest name: {questName} ({quest._id}) ({trader})"); LoggingHelpers.LogInfo($"Wiki: https://escapefromtarkov.fandom.com/wiki/{questName.Replace(' ', '_')}"); } private static void CheckRootItemValues(Quest quest, Quest relatedLiveQuest) { // Check image id matches CheckValuesMatch(quest.image.Substring(0, quest.image.Length - 4), relatedLiveQuest?.image.Substring(0, relatedLiveQuest.image.Length - 4), "item path mismatch"); //Check image id contains quest id if (!quest.image.Contains(quest._id)) { LoggingHelpers.LogInfo($"INFO Quest image path does not contain quest id"); } // Check started reward count matches CheckValuesMatch(quest.rewards.Started.Count, relatedLiveQuest.rewards.Started.Count, "Started item count mismatch"); // Check success reward count matches CheckValuesMatch(quest.rewards.Success.Count, relatedLiveQuest.rewards.Success.Count, "success item count mismatch"); // Check Fail reward count matches CheckValuesMatch(quest.rewards.Fail.Count, relatedLiveQuest.rewards.Fail.Count, "fail item count mismatch"); // Check location matches CheckValuesMatch(quest.location, relatedLiveQuest.location, "location value mismatch"); // Check traderid matches CheckValuesMatch(quest.traderId, relatedLiveQuest.traderId, "traderid value mismatch"); // Check type matches CheckValuesMatch(quest.type, relatedLiveQuest.type, "quest type value mismatch"); } private static void CheckSuccessRewardItems(Quest quest, Quest relatedLiveQuest) { var liveQuestSuccessRewardItems = relatedLiveQuest.rewards.Success; foreach (RewardStatus questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "Item")) { // Get live reward item by index and type var relatedLiveRewardItem = GetLiveRewardItem(questSuccessRewardItem, liveQuestSuccessRewardItems); if (relatedLiveRewardItem is null) { LogUnableToFindSuccessItemInLiveData(questSuccessRewardItem, relatedLiveRewardItem); continue; } // Ensure target matches the objects items[0].id value if (questSuccessRewardItem.items[0]?._id != questSuccessRewardItem.target) { LoggingHelpers.LogWarning($"WARNING target does not match first item: {questSuccessRewardItem.target}, expected {questSuccessRewardItem.items[0]?._id}"); } // Check template ids match CheckValuesMatch(questSuccessRewardItem.items[0]._tpl, relatedLiveRewardItem.items[0]._tpl, "mismatch for template id", questSuccessRewardItem.items[0]._id, true); // Check value values match CheckValuesMatch(questSuccessRewardItem.value.ToString(), relatedLiveRewardItem.value.ToString(), "mismatch for success item reward value", questSuccessRewardItem.id); // Check item stack count if (questSuccessRewardItem.items[0]?.upd != null && relatedLiveRewardItem.items[0]?.upd != null) { CheckValuesMatch(questSuccessRewardItem.items[0].upd.StackObjectsCount.Value, relatedLiveRewardItem.items[0].upd.StackObjectsCount.Value, "mismatch for success item StackObjectsCount", questSuccessRewardItem.items[0]._id); } // check sub items match CheckSubItemsMatch(questSuccessRewardItem, relatedLiveRewardItem); } foreach (var questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "Experience")) { var relatedLiveRewardItem = liveQuestSuccessRewardItems.FirstOrDefault(x => x.type == "Experience"); if (!ItemExists(relatedLiveRewardItem, "experience success reward item", questSuccessRewardItem.index)) { continue; } // check experience value matches CheckValuesMatch(questSuccessRewardItem.value, relatedLiveRewardItem.value, "experience value mismatch"); } foreach (var questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "TraderStanding")) { var relatedLiveRewardItem = liveQuestSuccessRewardItems.FirstOrDefault(x => x.target == questSuccessRewardItem.target && x.type == "TraderStanding"); if (!ItemExists(relatedLiveRewardItem, "TraderStanding success reward item", questSuccessRewardItem.index)) { continue; } // check standing value matches CheckValuesMatch(questSuccessRewardItem.value, relatedLiveRewardItem.value, "trader standing value mismatch"); // check target value matches CheckValuesMatch(questSuccessRewardItem.target, relatedLiveRewardItem.target, "trader target value mismatch"); } foreach (var questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "AssortmentUnlock")) { // Find the assort unlock item in the list of success rewards var possibleLiveRewardItems = liveQuestSuccessRewardItems.Where(x => x.id == questSuccessRewardItem.id && x.type == "AssortmentUnlock"); RewardStatus relatedLiveRewardItem = null; // we found the one we want if (possibleLiveRewardItems?.Count() == 1) { relatedLiveRewardItem = possibleLiveRewardItems.First(); } // multiple found if (possibleLiveRewardItems?.Count() > 1) { // be more specific, get my index relatedLiveRewardItem = possibleLiveRewardItems.FirstOrDefault(x=>x.index == questSuccessRewardItem.index); // nothing found by index, try by if (relatedLiveRewardItem is null) { relatedLiveRewardItem = possibleLiveRewardItems.FirstOrDefault(x => x.traderId == questSuccessRewardItem.traderId); } } if (relatedLiveRewardItem is null) { relatedLiveRewardItem = liveQuestSuccessRewardItems.Find(x => x.traderId == questSuccessRewardItem.traderId && x.index == questSuccessRewardItem.index && x.type == "AssortmentUnlock" && x.items[0]._tpl == questSuccessRewardItem.items[0]._tpl); } if (!ItemExists(relatedLiveRewardItem, "AssortmentUnlock success reward item", questSuccessRewardItem.index)) { continue; } // Check loyalty level CheckValuesMatch(questSuccessRewardItem.loyaltyLevel.Value, relatedLiveRewardItem.loyaltyLevel.Value, "loyalty level value mismatch", questSuccessRewardItem.id); // Check traderId CheckValuesMatch(questSuccessRewardItem.traderId, relatedLiveRewardItem.traderId, "traderId value mismatch", questSuccessRewardItem.id); // check target equals items[0].id CheckValuesMatch(questSuccessRewardItem.target, questSuccessRewardItem.items[0]._id, "target value does not match items[0].id mismatch", questSuccessRewardItem.id); } } private static void CheckSubItemsMatch(RewardStatus questSuccessRewardItem, RewardStatus relatedLiveRewardItem) { foreach (var subItem in questSuccessRewardItem.items.Where(x => !string.IsNullOrEmpty(x.slotId))) { // find live item by slotid var liveCounterpart = relatedLiveRewardItem.items.Where(x => x.slotId == subItem.slotId); if (liveCounterpart is null || liveCounterpart.Count() == 0) { // Look for live item by template id liveCounterpart = relatedLiveRewardItem.items.Where(x => x._tpl == subItem._tpl); if (liveCounterpart is null || liveCounterpart.Count() == 0) { LoggingHelpers.LogWarning($"a live counterpart for the subItem {subItem.slotId} could not be found by slotid or tpId, skipping subItem check"); continue; } } if (liveCounterpart.Count() > 1) { LoggingHelpers.LogWarning($"Multiple live counterparts for the subItem {subItem.slotId} found, skipping subItem check"); continue; } var firstLiveItem = liveCounterpart.FirstOrDefault(); CheckValuesMatch(subItem._tpl, firstLiveItem._tpl, $"mismatch for success subItem({subItem.slotId}) reward templateId", subItem._id); } } private static void LogUnableToFindSuccessItemInLiveData(RewardStatus questSuccessRewardItem, RewardStatus relatedLiveRewardItem) { if (relatedLiveRewardItem is null) { LoggingHelpers.LogError($"ERROR unable to find success reward item in live quest data by index: ({questSuccessRewardItem.index}) OR template id: {questSuccessRewardItem.items[0]._tpl} ({ItemTemplateHelper.GetTemplateById(questSuccessRewardItem.items[0]._tpl)._name})"); LoggingHelpers.LogError("Existing items:"); LogSuccessItems(questSuccessRewardItem); LoggingHelpers.LogError($"ERROR Skipping quest success item. id: {questSuccessRewardItem.id}"); } } private static void LogSuccessItems(RewardStatus rewardItem) { foreach (var item in rewardItem.items) { LoggingHelpers.LogInfo($"{item._tpl} ({ItemTemplateHelper.GetTemplateById(item._tpl)._name})"); } } /// /// Find live success item reward by index /// If item at index does not match templateId to desired item /// get live success item reward by template id /// /// /// /// private static RewardStatus GetLiveRewardItem(RewardStatus questSuccessRewardItem, List liveQuestSuccessRewardItems) { var LiveItemRewards = liveQuestSuccessRewardItems.Where(x => x.type == "Item"); var liveRewardItemByIndex = LiveItemRewards.FirstOrDefault(x => x.index == questSuccessRewardItem.index); // no item found by index, find by template id if (liveRewardItemByIndex is null) { foreach (var liveItem in LiveItemRewards .SelectMany(liveItem => liveItem.items .Where(subItem => subItem._tpl == questSuccessRewardItem.items[0]._tpl) .Select(subItem => liveItem))) { return liveItem; } } // item found by index but template id didnt match if (liveRewardItemByIndex != null && liveRewardItemByIndex.items[0]._tpl != questSuccessRewardItem.items[0]._tpl) { return LiveItemRewards.FirstOrDefault(x => x.items[0]._tpl == questSuccessRewardItem.items[0]._tpl); } return liveRewardItemByIndex; } private static void CheckStartedRewardItems(Quest quest, Quest relatedLiveQuest) { var liveQuestStartedRewardItems = relatedLiveQuest.rewards.Started; foreach (var questStartedRewardItem in quest.rewards.Started.Where(x => x.type == "Item")) { var errorMessage = string.Empty; // Get live reward item by index and type var relatedLiveRewardItem = liveQuestStartedRewardItems.Find(x => x.index == questStartedRewardItem.index && x.type == "Item"); if (relatedLiveRewardItem is null) { // Get live reward item by templateId and type as we cant find it by index relatedLiveRewardItem = liveQuestStartedRewardItems.Find(x => x.items != null && x.items[0]?._tpl == questStartedRewardItem.items[0]?._tpl && x.type == "Item"); if (relatedLiveRewardItem is null) { LoggingHelpers.LogError($"ERROR unable to find started reward item in live quest data by index: ({questStartedRewardItem.index}) OR template id: {questStartedRewardItem.items[0]._tpl}"); LoggingHelpers.LogError($"ERROR Skipping quest started item: {questStartedRewardItem.id}"); continue; } } // Ensure target matches the objects items[0].id value if (questStartedRewardItem.items[0]?._id != questStartedRewardItem.target) { LoggingHelpers.LogWarning($"WARNING target does not match first item: {questStartedRewardItem.target}, expected {questStartedRewardItem.items[0]?._id}"); } // Check template ids match CheckValuesMatch(questStartedRewardItem.items[0]._tpl, relatedLiveRewardItem.items[0]._tpl, "mismatch for template id", questStartedRewardItem.items[0]._id, true); // Check 'value' values match CheckValuesMatch(questStartedRewardItem.value, relatedLiveRewardItem.value, "mismatch for success item reward value", questStartedRewardItem.id); // Check item stack count if (questStartedRewardItem.items[0] != null && questStartedRewardItem.items[0].upd != null) { CheckValuesMatch(questStartedRewardItem.items[0].upd.StackObjectsCount.Value, relatedLiveRewardItem.items[0].upd.StackObjectsCount.Value, "mismatch for started item StackObjectsCount", questStartedRewardItem.items[0]._id); } } foreach (var questStartedRewardItem in quest.rewards.Started.Where(x => x.type == "AssortmentUnlock")) { // Get live reward item by id var relatedLiveRewardItem = liveQuestStartedRewardItems.FirstOrDefault(x => x.id == questStartedRewardItem.id && x.type == "AssortmentUnlock"); if (relatedLiveRewardItem is null) { // Cant find live reward item by id, get my template id inside items[0] relatedLiveRewardItem = liveQuestStartedRewardItems.Find(x => x.traderId == questStartedRewardItem.traderId && x.index == questStartedRewardItem.index && x.type == "AssortmentUnlock" && x.items[0]._tpl == questStartedRewardItem.items[0]._tpl); } if (!ItemExists(relatedLiveRewardItem, "AssortmentUnlock started reward item", questStartedRewardItem.index)) { continue; } // Check loyalty level CheckValuesMatch(questStartedRewardItem.loyaltyLevel.Value, relatedLiveRewardItem.loyaltyLevel.Value, "loyalty level value mismatch", questStartedRewardItem.id); // Check traderId CheckValuesMatch(questStartedRewardItem.traderId, relatedLiveRewardItem.traderId, "traderId value mismatch", questStartedRewardItem.id); // check target equals items[0].id CheckValuesMatch(questStartedRewardItem.target, questStartedRewardItem.items[0]._id, "target value does not match items[0].id mismatch", questStartedRewardItem.id); } } private static void CheckAvailableForFinishConditionItems(Quest quest, Quest relatedLiveQuest) { // Check count CheckValuesMatch(quest.conditions.AvailableForFinish.Count, relatedLiveQuest.conditions.AvailableForFinish.Count, "AvailableForFinish mismtch"); foreach (var availableForFinishItem in quest.conditions.AvailableForFinish) { AvailableFor liveFinishItem = relatedLiveQuest.conditions.AvailableForFinish.Find(x => x._props.id == availableForFinishItem._props.id); if (!ItemExists(liveFinishItem, "AvailableForFinish item", availableForFinishItem._props.index.Value)) { continue; } // Check parentId CheckValuesMatch(availableForFinishItem._props.parentId, liveFinishItem._props.parentId, "AvailableForFinish parentId mismatch", availableForFinishItem._props.id); // check AvailableForFinish resetOnSessionEnd if (availableForFinishItem._props.resetOnSessionEnd.HasValue && liveFinishItem._props.resetOnSessionEnd.HasValue) { CheckValuesMatch(availableForFinishItem._props.resetOnSessionEnd.Value, liveFinishItem._props.resetOnSessionEnd.Value, "AvailableForFinish resetOnSessionEnd value mismatch", availableForFinishItem._props.id); } // check AvailableForFinish target CheckValuesMatch(Convert.ToString(availableForFinishItem._props.target), Convert.ToString(liveFinishItem._props.target), "AvailableForFinish target value mismatch", availableForFinishItem._props.id); // check weapons allowed match //CheckValuesMatch(availableForFinishItem._props.weapon.Count, liveFinishItem._props.counter.conditions), "AvailableForFinish target value mismatch", availableForFinishItem._props.id); } } private static bool ItemExists(object itemToCheck, string message, int index = -1) { if (itemToCheck is null) { if (index == -1) { LoggingHelpers.LogError($"ERROR no match found for {message}"); } else { LoggingHelpers.LogError($"ERROR no match found for {message} at index: {index}"); } return false; } return true; } private static void CheckValuesMatch(int firstValue, int secondValue, string message, string associatedId = "") { if (firstValue != secondValue) { if (associatedId == string.Empty) { LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'"); } else { LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'"); } } } private static void CheckValuesMatch(T firstValue, T secondValue, string message, string associatedId = "") where T : struct { if (firstValue.Equals(secondValue)) { if (associatedId == string.Empty) { LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'"); } else { LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'"); } } } private static void CheckValuesMatch(string firstValue, string secondValue, string message, string associatedId = "", bool performTemplateIdLookup = false) { if (firstValue != secondValue) { if (performTemplateIdLookup) { firstValue = $"{firstValue} ({ItemTemplateHelper.GetTemplateById(firstValue)._name})"; secondValue = $"{secondValue} ({ItemTemplateHelper.GetTemplateById(secondValue)._name})"; } if (associatedId == string.Empty) { LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'"); } else { LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'"); } } } private static void CheckValuesMatch(bool firstValue, bool secondValue, string message, string associatedId = "") { if (firstValue != secondValue) { if (associatedId == string.Empty) { LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'"); } else { LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'"); } } } private static void CheckForMissingQuestsInAkiFile(QuestRoot liveQuestData, Dictionary akiQuestData) { // iterate over live quests and look for quests that exist in live but not in aki var missingQuests = new List(); foreach (var liveQuest in liveQuestData.data) { if (!akiQuestData.ContainsKey(liveQuest._id)) { missingQuests.Add($"{liveQuest._id} {QuestHelper.GetQuestNameById(liveQuest._id)}"); } } // Quests in live but not in aki were found, log it if (missingQuests.Count > 0) { LoggingHelpers.LogWarning($"WARNING aki quest list is missing quests:"); foreach (var item in missingQuests) { LoggingHelpers.LogWarning(item); } } } } }