From 309b882796074a25e326ddbe2e6e2d1ac1dde279 Mon Sep 17 00:00:00 2001 From: Dev Date: Sun, 9 Jul 2023 11:36:00 +0100 Subject: [PATCH] Rework assorting finding code with score-based system --- AssortGenerator.Common/Helpers/QuestHelper.cs | 30 +++ AssortGenerator.Models/Input/Quest.cs | 24 +- AssortGenerator.Models/Output/AssortRoot.cs | 8 +- AssortGenerator/Program.cs | 244 +++++++++++------- 4 files changed, 206 insertions(+), 100 deletions(-) diff --git a/AssortGenerator.Common/Helpers/QuestHelper.cs b/AssortGenerator.Common/Helpers/QuestHelper.cs index 724a652..2514593 100644 --- a/AssortGenerator.Common/Helpers/QuestHelper.cs +++ b/AssortGenerator.Common/Helpers/QuestHelper.cs @@ -23,6 +23,30 @@ namespace AssortGenerator.Common.Helpers return _questData; } + public static Dictionary GetFinalisedQuestData() + { + var questFilePath = InputFileHelper.GetInputFilePaths().FirstOrDefault(x => x.Contains("quests.json")); + var questDataJson = File.ReadAllText(questFilePath); + var finalisedQuestData = JsonSerializer.Deserialize> (questDataJson); + + var jsondoc = JsonDocument.Parse(questDataJson); + var root = jsondoc.RootElement; + + + + return finalisedQuestData; + } + + public static JsonDocument GetFinalisedQuestDataJsonDoc() + { + var questFilePath = InputFileHelper.GetInputFilePaths().FirstOrDefault(x => x.Contains("quests.json")); + var questDataJson = File.ReadAllText(questFilePath); + + var jsondoc = JsonDocument.Parse(questDataJson); + + return jsondoc; + } + public static List GetAssortUnlocks() { if (_assortUnlocks == null) @@ -30,6 +54,12 @@ namespace AssortGenerator.Common.Helpers _assortUnlocks = new List(); foreach (var quest in GetQuestData().data) { + // debut + if (quest._id == "5936d90786f7742b1420ba5b") + { + var x = 2; + } + foreach (var reward in quest.rewards.Success) { if (string.Equals(reward.type, "assortmentunlock", System.StringComparison.OrdinalIgnoreCase)) diff --git a/AssortGenerator.Models/Input/Quest.cs b/AssortGenerator.Models/Input/Quest.cs index b9452cd..c155bdc 100644 --- a/AssortGenerator.Models/Input/Quest.cs +++ b/AssortGenerator.Models/Input/Quest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using AssortGenerator.Models.Output; +using System.Collections.Generic; namespace AssortGenerator.Models.Input { @@ -11,7 +12,18 @@ namespace AssortGenerator.Models.Input public class Quest { + public string QuestName { get; set; } public string _id { get; set; } + public bool canShowNotificationsInGame { get; set; } + public string acceptPlayerMessage { get; set; } + public string changeQuestMessageText { get; set; } + public string completePlayerMessage { get; set; } + public object conditions { get; set; } + public string description { get; set; } + public string failMessageText { get; set; } + public string name { get; set; } + public string note { get; set; } + public object questStatus { get; set; } public string traderId { get; set; } public string location { get; set; } public string image { get; set; } @@ -20,9 +32,11 @@ namespace AssortGenerator.Models.Input public bool restartable { get; set; } public bool instantComplete { get; set; } public bool secretQuest { get; set; } - public int min_level { get; set; } - public bool canShowNotificationsInGame { get; set; } + public string startedMessageText { get; set; } + public string successMessageText { get; set; } + public string templateId { get; set; } public Rewards rewards { get; set; } + public string side { get; set; } } public class Rewards @@ -42,13 +56,15 @@ namespace AssortGenerator.Models.Input public List items { get; set; } public int? loyaltyLevel { get; set; } public string traderId { get; set; } + public bool? findInRaid { get; set; } + public bool? unknown { get; set; } } public class QuestRewardItem { public string _id { get; set; } public string _tpl { get; set; } - public QuestRewardUpd upd { get; set; } + public object upd { get; set; } public string parentId { get; set; } public string slotId { get; set; } } diff --git a/AssortGenerator.Models/Output/AssortRoot.cs b/AssortGenerator.Models/Output/AssortRoot.cs index afa773b..dacb358 100644 --- a/AssortGenerator.Models/Output/AssortRoot.cs +++ b/AssortGenerator.Models/Output/AssortRoot.cs @@ -5,7 +5,7 @@ namespace AssortGenerator.Models.Output public class AssortRoot { public List items { get; set; } - public Dictionary barter_scheme { get; set; } + public Dictionary>> barter_scheme { get; set; } public Dictionary loyal_level_items { get; set; } } @@ -31,7 +31,11 @@ namespace AssortGenerator.Models.Output public class BarterObject { - public int count { get; set; } + // Can be int or decimal, thanks bsg + public object count { get; set; } public string _tpl { get; set; } + public int? level { get; set; } + public string side { get; set; } + public bool? onlyFunctional { get; set; } } } diff --git a/AssortGenerator/Program.cs b/AssortGenerator/Program.cs index 77deb28..fc05d34 100644 --- a/AssortGenerator/Program.cs +++ b/AssortGenerator/Program.cs @@ -6,6 +6,7 @@ using AssortGenerator.Models.Output; using Common; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; @@ -21,15 +22,12 @@ namespace AssortGenerator // Get trader assort files from assorts input folder var traderAssortFilePaths = InputFileHelper.GetInputFilePaths().Where(x => TraderHelper.GetTraders().Values.Any(x.Contains)).ToList(); - + var finalisedQuestData = QuestHelper.GetFinalisedQuestData(); foreach (var trader in TraderHelper.GetTraders()) { // Get relevant trader dump var assortDumpPath = traderAssortFilePaths.Find(x => x.Contains($"getTraderAssort.{trader.Value}")); - if (assortDumpPath == null) - { - assortDumpPath = traderAssortFilePaths.Find(x => x.Contains($"{trader.Value}") && x.Contains("getTraderAssort")); - } + assortDumpPath ??= traderAssortFilePaths.Find(x => x.Contains($"{trader.Value}") && x.Contains("getTraderAssort")); // Convert input dump json into object var json = File.ReadAllText(assortDumpPath); @@ -49,14 +47,16 @@ namespace AssortGenerator // Find barter scheme node and parse into dictionary var barterSchemeJson = data.GetProperty("barter_scheme").ToString(); - var barterSchemeItems = JsonSerializer.Deserialize>(barterSchemeJson); + var barterSchemeItems = JsonSerializer.Deserialize>>>(barterSchemeJson); // Find loyalty level node and parse into dictionary var loyaltyLevelItemsJson = data.GetProperty("loyal_level_items").ToString(); var loyaltyLevelItems = JsonSerializer.Deserialize>(loyaltyLevelItemsJson); - WriteOutputFilesForTrader(trader, items, barterSchemeItems, loyaltyLevelItems); + WriteOutputFilesForTrader(trader, items, barterSchemeItems, loyaltyLevelItems, finalisedQuestData); } + + JsonWriter.WriteJson(finalisedQuestData, "", Directory.GetCurrentDirectory(), "quests"); } private static void FixZeroSizedStackAssorts(List items, int defaultStackSize) @@ -129,9 +129,9 @@ namespace AssortGenerator private static void WriteOutputFilesForTrader( KeyValuePair trader, List items, - Dictionary - barterSchemeItems, - Dictionary loyaltyLevelItems) + Dictionary>> barterSchemeItems, + Dictionary loyaltyLevelItems, + Dictionary finalisedQuestData) { var workingPath = Directory.GetCurrentDirectory(); @@ -151,7 +151,7 @@ namespace AssortGenerator var outputBaseFile = traderData.data.Find(x => x._id == trader.Value); JsonWriter.WriteJson(outputBaseFile, traderFolderPath, workingPath, "base"); - QuestAssort questAssort = GenerateQuestAssort(trader.Key, outputAssortFile); + QuestAssort questAssort = GenerateQuestAssortForTrader(trader.Key, outputAssortFile); JsonWriter.WriteJson(questAssort, traderFolderPath, workingPath, "questassort"); // create suits file for ragman @@ -199,7 +199,7 @@ namespace AssortGenerator /// /// /// - private static QuestAssort GenerateQuestAssort(Trader trader, AssortRoot assortRoot) + private static QuestAssort GenerateQuestAssortForTrader(Trader trader, AssortRoot assortRoot) { var result = new QuestAssort(); var questData = QuestHelper.GetQuestData(); @@ -209,9 +209,10 @@ namespace AssortGenerator // Store already matched items var assortItemsThatMatchBlackList = new List(); + int unknownCount = 1; foreach (var assortUnlock in assortUnlocks.Where(x => x.TraderType == trader)) { - if (assortUnlock.ItemUnlockedTemplateId == "58948c8e86f77409493f7266") + if (assortUnlock.QuestId == "64764abcd125ab430a14ccb5") { var x = 2; } @@ -219,106 +220,41 @@ namespace AssortGenerator // Get unlock item details var assortItemDetailsDB = ItemTemplateHelper.Items.FirstOrDefault(x => x.Key == assortUnlock.ItemUnlockedTemplateId); var ItemName = assortItemDetailsDB.Value._name; - var assortItemMods = assortUnlock.Items.Where(x => x.parentId == assortUnlock.ItemUnlockedId); + var assortItemModsFromQuest = assortUnlock.Items.Where(x => x.parentId == assortUnlock.ItemUnlockedId); - //Find assorts that match the quest unlocks item template - List assortItemsThatMatch = assortRoot.items - .Where(x => x._tpl == assortUnlock.ItemUnlockedTemplateId && x.slotId == "hideout") - .ToList(); - - // No assort found for this unlock, log and skip it - if (assortItemsThatMatch == null || assortItemsThatMatch.Count == 0) + // Get matching assorts from trader + var results = GetMatchingTraderAssortsWithScore(assortRoot, assortUnlock, trader, assortItemsThatMatchBlackList); + if (results.Keys.Count == 0) { - LoggingHelpers.LogError($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId}. questId: {assortUnlock.QuestId}. no assort item found. ({ItemName})"); + // no assort found for this unlock, add to questassort with a placeholder value instead of assort id + LoggingHelpers.LogError($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. no assortId found"); + + result.success.Add($"UnknownAssortId{unknownCount}", assortUnlock.QuestId); + unknownCount++; + continue; } - // Iterate over assorts that match. goal is to find assort that fits the best - // (assort has same loyalty level unlock as quest expects) - string assortIdUnlockedByQuest = string.Empty; - foreach (var match in assortItemsThatMatch) - { - // Look up item in Loyalty Level array - var associatedLoyaltyLevelItem = assortRoot.loyal_level_items - .FirstOrDefault(x => x.Key == match._id); + var highestScoringAssortIdMatch = results.OrderByDescending(x => x.Value).First().Key; - if (associatedLoyaltyLevelItem.Key == null) - { - // Skip item if no record found in loyalty level array - LoggingHelpers.LogError($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. no loyalty record found. "); - continue; - } + // Add assort item id to blacklist so it wont be matched again + assortItemsThatMatchBlackList.Add(highestScoringAssortIdMatch); - if (associatedLoyaltyLevelItem.Value != assortUnlock.LoyaltyLevel) - { - // Loyalty level is different to what was expected, skip and try next - LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. no match found in LL array. expected LL{associatedLoyaltyLevelItem.Value}. found: LL{assortUnlock.LoyaltyLevel}"); - continue; - } - - // LoggingHelpers.LogInfo($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} {assortItemName}. MATCH FOUND. LL{assortUnlock.LoyaltyLevel}"); - if (assortItemsThatMatchBlackList.Contains(associatedLoyaltyLevelItem.Key)) - { - // Not the item we want, its already been matched - continue; - } - - // Try matching by mods on item if they exist - if (assortItemMods.Any()) - { - var matchedItemMods = assortRoot.items.Where(x => x.parentId == match._id).ToList(); - if (assortItemMods.Count() != matchedItemMods.Count) - { - LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. mismatch of mod count, skipping"); - continue; - } - var badMatch = false; - foreach (var desiredMod in assortItemMods) - { - var matchingModInCurrentMatch = matchedItemMods.FirstOrDefault(x => x._tpl == desiredMod._tpl); - if (matchingModInCurrentMatch == null) - { - badMatch = true; - LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. mismatch of mods, skipping"); - break; - } - } - - if (badMatch) - { - continue; - } - } - - // Add assort item id to blacklist so it wont be matched again - assortItemsThatMatchBlackList.Add(associatedLoyaltyLevelItem.Key); - - // assign id and break out of loop, We found the one we want - assortIdUnlockedByQuest = associatedLoyaltyLevelItem.Key; - break; - } - - if (result.success.ContainsKey(assortIdUnlockedByQuest)) + if (result.success.ContainsKey(highestScoringAssortIdMatch)) { LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ALREADY EXISTS. SKIPPING"); continue; } - if (assortIdUnlockedByQuest.Length == 0) - { - LoggingHelpers.LogError($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. no assortId found"); - continue; - } - LoggingHelpers.LogSuccess($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ADDING TO QUEST-ASSORT"); if (assortUnlock.Criteria == "Success") { - result.success.Add(assortIdUnlockedByQuest, assortUnlock.QuestId); + result.success.Add(highestScoringAssortIdMatch, assortUnlock.QuestId); } else if (assortUnlock.Criteria == "Started") { - result.started.Add(assortIdUnlockedByQuest, assortUnlock.QuestId); + result.started.Add(highestScoringAssortIdMatch, assortUnlock.QuestId); } else { @@ -341,5 +277,125 @@ namespace AssortGenerator return result; } + + /// + /// Get a list of matching assorts with a score of how well they match the quest assort requirements + /// + /// Traders assort items/barter/loyalty + /// + /// + /// + /// + private static Dictionary GetMatchingTraderAssortsWithScore( + AssortRoot traderAssortRoot, + AssortUnlocks assortUnlock, + Trader trader, + IEnumerable assortItemsThatMatchBlackList) + { + var quests = QuestHelper.GetFinalisedQuestData(); + var assortItemDetailsDB = ItemTemplateHelper.Items.FirstOrDefault(x => x.Key == assortUnlock.ItemUnlockedTemplateId); + var assortItemName = assortItemDetailsDB.Value._name; + var assortItemModsFromQuest = assortUnlock.Items.Where(x => x.parentId == assortUnlock.ItemUnlockedId); + + // assort id + score + var matchScores = new Dictionary(); + List assortItemsThatMatch = traderAssortRoot.items + .Where(x => x._tpl == assortUnlock.ItemUnlockedTemplateId && x.slotId == "hideout") + .ToList(); + + if (assortItemsThatMatch?.Count == 0) + { + LoggingHelpers.LogError($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId}. questId: {assortUnlock.QuestId}. No matches found. "); + return matchScores; + } + + if (assortItemsThatMatch?.Count > 2) + { + var questData = quests.FirstOrDefault(x => x.Key == assortUnlock.QuestId); + LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId}. questId: {questData.Value.QuestName}. multiple matches found. "); + } + + // Only one result, no need to work out score + if (assortItemsThatMatch.Count == 1) + { + matchScores[assortItemsThatMatch[0]._id] = 1; + return matchScores; + } + + foreach (var assortItemMatch in assortItemsThatMatch) + { + matchScores[assortItemMatch._id] = 0; + var itemBarter = traderAssortRoot.barter_scheme[assortItemMatch._id]; + var barterItems = itemBarter.First(); + if (barterItems.Count == 1 && TplIsMoney(barterItems[0]._tpl)) + { + matchScores[assortItemMatch._id] += 2; + } + else + { + matchScores[assortItemMatch._id] += 1; + } + + // Look up item in Loyalty Level array + var associatedLoyaltyLevelItem = traderAssortRoot.loyal_level_items + .FirstOrDefault(x => x.Key == assortItemMatch._id); + + if (associatedLoyaltyLevelItem.Value == assortUnlock.LoyaltyLevel) + { + // Loyalty level matches + matchScores[assortItemMatch._id] += 5; + } + else + { + matchScores[assortItemMatch._id] -= 25; + } + + if (assortItemsThatMatchBlackList.Contains(associatedLoyaltyLevelItem.Key)) + { + // Not the item we want, its already been matched + matchScores[assortItemMatch._id] -= 25; + } + + // Try matching by mods on item if they exist + if (assortItemModsFromQuest.Any()) + { + var matchedItemMods = traderAssortRoot.items.Where(x => x.parentId == assortItemMatch._id).ToList(); + if (assortItemModsFromQuest.Count() == matchedItemMods.Count) + { + // mod count from quest matches trader assort + matchScores[assortItemMatch._id] += 10; + } + else + { + LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({assortItemName}). questId: {assortUnlock.QuestId}. mismatch of mod count, skipping"); + continue; + } + + foreach (var desiredMod in assortItemModsFromQuest) + { + // Compare each mod item in turn + var matchingModInCurrentMatch = matchedItemMods.FirstOrDefault(x => x._tpl == desiredMod._tpl); + if (matchingModInCurrentMatch != null) + { + matchScores[assortItemMatch._id] += 2; + } + else + { + LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({assortItemName}). questId: {assortUnlock.QuestId}. mismatch of mods, skipping"); + matchScores[assortItemMatch._id] -= 2; + } + } + + } + } + + return matchScores; + } + + private static bool TplIsMoney(string tpl) + { + var moneyTpls = new string[] { "5449016a4bdc2d6f028b456f", "569668774bdc2da2298b4568", "5696686a4bdc2da3298b456a" }; + return moneyTpls.Contains(tpl); + } } }