Rework assorting finding code with score-based system

This commit is contained in:
Dev 2023-07-09 11:36:00 +01:00
parent 4e20e6a03b
commit 309b882796
4 changed files with 206 additions and 100 deletions

View File

@ -23,6 +23,30 @@ namespace AssortGenerator.Common.Helpers
return _questData; return _questData;
} }
public static Dictionary<string, Quest> GetFinalisedQuestData()
{
var questFilePath = InputFileHelper.GetInputFilePaths().FirstOrDefault(x => x.Contains("quests.json"));
var questDataJson = File.ReadAllText(questFilePath);
var finalisedQuestData = JsonSerializer.Deserialize<Dictionary<string, Quest>> (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<AssortUnlocks> GetAssortUnlocks() public static List<AssortUnlocks> GetAssortUnlocks()
{ {
if (_assortUnlocks == null) if (_assortUnlocks == null)
@ -30,6 +54,12 @@ namespace AssortGenerator.Common.Helpers
_assortUnlocks = new List<AssortUnlocks>(); _assortUnlocks = new List<AssortUnlocks>();
foreach (var quest in GetQuestData().data) foreach (var quest in GetQuestData().data)
{ {
// debut
if (quest._id == "5936d90786f7742b1420ba5b")
{
var x = 2;
}
foreach (var reward in quest.rewards.Success) foreach (var reward in quest.rewards.Success)
{ {
if (string.Equals(reward.type, "assortmentunlock", System.StringComparison.OrdinalIgnoreCase)) if (string.Equals(reward.type, "assortmentunlock", System.StringComparison.OrdinalIgnoreCase))

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using AssortGenerator.Models.Output;
using System.Collections.Generic;
namespace AssortGenerator.Models.Input namespace AssortGenerator.Models.Input
{ {
@ -11,7 +12,18 @@ namespace AssortGenerator.Models.Input
public class Quest public class Quest
{ {
public string QuestName { get; set; }
public string _id { 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 traderId { get; set; }
public string location { get; set; } public string location { get; set; }
public string image { get; set; } public string image { get; set; }
@ -20,9 +32,11 @@ namespace AssortGenerator.Models.Input
public bool restartable { get; set; } public bool restartable { get; set; }
public bool instantComplete { get; set; } public bool instantComplete { get; set; }
public bool secretQuest { get; set; } public bool secretQuest { get; set; }
public int min_level { get; set; } public string startedMessageText { get; set; }
public bool canShowNotificationsInGame { get; set; } public string successMessageText { get; set; }
public string templateId { get; set; }
public Rewards rewards { get; set; } public Rewards rewards { get; set; }
public string side { get; set; }
} }
public class Rewards public class Rewards
@ -42,13 +56,15 @@ namespace AssortGenerator.Models.Input
public List<QuestRewardItem> items { get; set; } public List<QuestRewardItem> items { get; set; }
public int? loyaltyLevel { get; set; } public int? loyaltyLevel { get; set; }
public string traderId { get; set; } public string traderId { get; set; }
public bool? findInRaid { get; set; }
public bool? unknown { get; set; }
} }
public class QuestRewardItem public class QuestRewardItem
{ {
public string _id { get; set; } public string _id { get; set; }
public string _tpl { get; set; } public string _tpl { get; set; }
public QuestRewardUpd upd { get; set; } public object upd { get; set; }
public string parentId { get; set; } public string parentId { get; set; }
public string slotId { get; set; } public string slotId { get; set; }
} }

View File

@ -5,7 +5,7 @@ namespace AssortGenerator.Models.Output
public class AssortRoot public class AssortRoot
{ {
public List<Item> items { get; set; } public List<Item> items { get; set; }
public Dictionary<string, object> barter_scheme { get; set; } public Dictionary<string, List<List<BarterObject>>> barter_scheme { get; set; }
public Dictionary<string, int> loyal_level_items { get; set; } public Dictionary<string, int> loyal_level_items { get; set; }
} }
@ -31,7 +31,11 @@ namespace AssortGenerator.Models.Output
public class BarterObject 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 string _tpl { get; set; }
public int? level { get; set; }
public string side { get; set; }
public bool? onlyFunctional { get; set; }
} }
} }

View File

@ -6,6 +6,7 @@ using AssortGenerator.Models.Output;
using Common; using Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@ -21,15 +22,12 @@ namespace AssortGenerator
// Get trader assort files from assorts input folder // Get trader assort files from assorts input folder
var traderAssortFilePaths = InputFileHelper.GetInputFilePaths().Where(x => TraderHelper.GetTraders().Values.Any(x.Contains)).ToList(); var traderAssortFilePaths = InputFileHelper.GetInputFilePaths().Where(x => TraderHelper.GetTraders().Values.Any(x.Contains)).ToList();
var finalisedQuestData = QuestHelper.GetFinalisedQuestData();
foreach (var trader in TraderHelper.GetTraders()) foreach (var trader in TraderHelper.GetTraders())
{ {
// Get relevant trader dump // Get relevant trader dump
var assortDumpPath = traderAssortFilePaths.Find(x => x.Contains($"getTraderAssort.{trader.Value}")); 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 // Convert input dump json into object
var json = File.ReadAllText(assortDumpPath); var json = File.ReadAllText(assortDumpPath);
@ -49,14 +47,16 @@ namespace AssortGenerator
// Find barter scheme node and parse into dictionary // Find barter scheme node and parse into dictionary
var barterSchemeJson = data.GetProperty("barter_scheme").ToString(); var barterSchemeJson = data.GetProperty("barter_scheme").ToString();
var barterSchemeItems = JsonSerializer.Deserialize<Dictionary<string, object>>(barterSchemeJson); var barterSchemeItems = JsonSerializer.Deserialize<Dictionary<string, List<List<BarterObject>>>>(barterSchemeJson);
// Find loyalty level node and parse into dictionary // Find loyalty level node and parse into dictionary
var loyaltyLevelItemsJson = data.GetProperty("loyal_level_items").ToString(); var loyaltyLevelItemsJson = data.GetProperty("loyal_level_items").ToString();
var loyaltyLevelItems = JsonSerializer.Deserialize<Dictionary<string, int>>(loyaltyLevelItemsJson); var loyaltyLevelItems = JsonSerializer.Deserialize<Dictionary<string, int>>(loyaltyLevelItemsJson);
WriteOutputFilesForTrader(trader, items, barterSchemeItems, loyaltyLevelItems); WriteOutputFilesForTrader(trader, items, barterSchemeItems, loyaltyLevelItems, finalisedQuestData);
} }
JsonWriter.WriteJson(finalisedQuestData, "", Directory.GetCurrentDirectory(), "quests");
} }
private static void FixZeroSizedStackAssorts(List<Item> items, int defaultStackSize) private static void FixZeroSizedStackAssorts(List<Item> items, int defaultStackSize)
@ -129,9 +129,9 @@ namespace AssortGenerator
private static void WriteOutputFilesForTrader( private static void WriteOutputFilesForTrader(
KeyValuePair<Trader, string> trader, KeyValuePair<Trader, string> trader,
List<Item> items, List<Item> items,
Dictionary<string, object> Dictionary<string, List<List<BarterObject>>> barterSchemeItems,
barterSchemeItems, Dictionary<string, int> loyaltyLevelItems,
Dictionary<string, int> loyaltyLevelItems) Dictionary<string, Quest> finalisedQuestData)
{ {
var workingPath = Directory.GetCurrentDirectory(); var workingPath = Directory.GetCurrentDirectory();
@ -151,7 +151,7 @@ namespace AssortGenerator
var outputBaseFile = traderData.data.Find(x => x._id == trader.Value); var outputBaseFile = traderData.data.Find(x => x._id == trader.Value);
JsonWriter.WriteJson(outputBaseFile, traderFolderPath, workingPath, "base"); JsonWriter.WriteJson(outputBaseFile, traderFolderPath, workingPath, "base");
QuestAssort questAssort = GenerateQuestAssort(trader.Key, outputAssortFile); QuestAssort questAssort = GenerateQuestAssortForTrader(trader.Key, outputAssortFile);
JsonWriter.WriteJson(questAssort, traderFolderPath, workingPath, "questassort"); JsonWriter.WriteJson(questAssort, traderFolderPath, workingPath, "questassort");
// create suits file for ragman // create suits file for ragman
@ -199,7 +199,7 @@ namespace AssortGenerator
/// <param name="trader"></param> /// <param name="trader"></param>
/// <param name="assortRoot"></param> /// <param name="assortRoot"></param>
/// <returns></returns> /// <returns></returns>
private static QuestAssort GenerateQuestAssort(Trader trader, AssortRoot assortRoot) private static QuestAssort GenerateQuestAssortForTrader(Trader trader, AssortRoot assortRoot)
{ {
var result = new QuestAssort(); var result = new QuestAssort();
var questData = QuestHelper.GetQuestData(); var questData = QuestHelper.GetQuestData();
@ -209,9 +209,10 @@ namespace AssortGenerator
// Store already matched items // Store already matched items
var assortItemsThatMatchBlackList = new List<string>(); var assortItemsThatMatchBlackList = new List<string>();
int unknownCount = 1;
foreach (var assortUnlock in assortUnlocks.Where(x => x.TraderType == trader)) foreach (var assortUnlock in assortUnlocks.Where(x => x.TraderType == trader))
{ {
if (assortUnlock.ItemUnlockedTemplateId == "58948c8e86f77409493f7266") if (assortUnlock.QuestId == "64764abcd125ab430a14ccb5")
{ {
var x = 2; var x = 2;
} }
@ -219,106 +220,41 @@ namespace AssortGenerator
// Get unlock item details // Get unlock item details
var assortItemDetailsDB = ItemTemplateHelper.Items.FirstOrDefault(x => x.Key == assortUnlock.ItemUnlockedTemplateId); var assortItemDetailsDB = ItemTemplateHelper.Items.FirstOrDefault(x => x.Key == assortUnlock.ItemUnlockedTemplateId);
var ItemName = assortItemDetailsDB.Value._name; 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 // Get matching assorts from trader
List<Item> assortItemsThatMatch = assortRoot.items var results = GetMatchingTraderAssortsWithScore(assortRoot, assortUnlock, trader, assortItemsThatMatchBlackList);
.Where(x => x._tpl == assortUnlock.ItemUnlockedTemplateId && x.slotId == "hideout") if (results.Keys.Count == 0)
.ToList();
// No assort found for this unlock, log and skip it
if (assortItemsThatMatch == null || assortItemsThatMatch.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; continue;
} }
// Iterate over assorts that match. goal is to find assort that fits the best var highestScoringAssortIdMatch = results.OrderByDescending(x => x.Value).First().Key;
// (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);
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;
}
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 // Add assort item id to blacklist so it wont be matched again
assortItemsThatMatchBlackList.Add(associatedLoyaltyLevelItem.Key); assortItemsThatMatchBlackList.Add(highestScoringAssortIdMatch);
// assign id and break out of loop, We found the one we want if (result.success.ContainsKey(highestScoringAssortIdMatch))
assortIdUnlockedByQuest = associatedLoyaltyLevelItem.Key;
break;
}
if (result.success.ContainsKey(assortIdUnlockedByQuest))
{ {
LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ALREADY EXISTS. SKIPPING"); LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ALREADY EXISTS. SKIPPING");
continue; 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"); LoggingHelpers.LogSuccess($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ADDING TO QUEST-ASSORT");
if (assortUnlock.Criteria == "Success") if (assortUnlock.Criteria == "Success")
{ {
result.success.Add(assortIdUnlockedByQuest, assortUnlock.QuestId); result.success.Add(highestScoringAssortIdMatch, assortUnlock.QuestId);
} }
else if (assortUnlock.Criteria == "Started") else if (assortUnlock.Criteria == "Started")
{ {
result.started.Add(assortIdUnlockedByQuest, assortUnlock.QuestId); result.started.Add(highestScoringAssortIdMatch, assortUnlock.QuestId);
} }
else else
{ {
@ -341,5 +277,125 @@ namespace AssortGenerator
return result; return result;
} }
/// <summary>
/// Get a list of matching assorts with a score of how well they match the quest assort requirements
/// </summary>
/// <param name="traderAssortRoot">Traders assort items/barter/loyalty</param>
/// <param name="assortUnlock"></param>
/// <param name="trader"></param>
/// <param name="assortItemsThatMatchBlackList"></param>
/// <returns></returns>
private static Dictionary<string, int> GetMatchingTraderAssortsWithScore(
AssortRoot traderAssortRoot,
AssortUnlocks assortUnlock,
Trader trader,
IEnumerable<string> 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<string, int>();
List<Item> 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);
}
} }
} }