536 lines
24 KiB
C#
Raw Normal View History

using AssortGenerator.Common;
using AssortGenerator.Common.Helpers;
using AssortGenerator.Models.Input;
using AssortGenerator.Models.Other;
using AssortGenerator.Models.Output;
using Common;
2023-01-08 15:06:37 +00:00
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace AssortGenerator
{
2021-09-18 22:32:56 +01:00
public class Program
{
static void Main(string[] args)
{
var assortsPath = CreateWorkingFolders();
InputFileHelper.SetInputFiles(assortsPath);
// 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
2023-01-03 10:46:33 +00:00
var assortDumpPath = traderAssortFilePaths.Find(x => x.Contains($"getTraderAssort.{trader.Value}"));
assortDumpPath ??= traderAssortFilePaths.Find(x => x.Contains($"{trader.Value}") && x.Contains("getTraderAssort"));
2023-01-03 10:46:33 +00:00
// Convert input dump json into object
var json = File.ReadAllText(assortDumpPath);
var jsonObject = JsonDocument.Parse(json);
2023-01-03 10:46:33 +00:00
// Find root data node
var data = jsonObject.RootElement;
2023-01-03 10:46:33 +00:00
// Find assort items node and parse into list
string itemsJson = data.GetProperty("items").ToString();
List<Item> items = JsonSerializer.Deserialize<List<Item>>(itemsJson);
2023-01-03 10:46:33 +00:00
// Fix items that have ran out of stock in the dump and give stack size
FixZeroSizedStackAssorts(items, 100);
2023-01-08 15:06:37 +00:00
FixFullyPurchasedStackLimits(items);
2023-01-03 10:46:33 +00:00
// Find barter scheme node and parse into dictionary
var barterSchemeJson = data.GetProperty("barter_scheme").ToString();
var barterSchemeItems = JsonSerializer.Deserialize<Dictionary<string, List<List<BarterObject>>>>(barterSchemeJson);
2023-01-03 10:46:33 +00:00
// Find loyalty level node and parse into dictionary
var loyaltyLevelItemsJson = data.GetProperty("loyal_level_items").ToString();
var loyaltyLevelItems = JsonSerializer.Deserialize<Dictionary<string, int>>(loyaltyLevelItemsJson);
WriteOutputFilesForTrader(trader, items, barterSchemeItems, loyaltyLevelItems, finalisedQuestData);
}
JsonWriter.WriteJson(finalisedQuestData, "", Directory.GetCurrentDirectory(), "quests");
}
2023-01-03 10:46:33 +00:00
private static void FixZeroSizedStackAssorts(List<Item> items, int defaultStackSize)
{
foreach (var item in items
.Where(item => item.upd?.StackObjectsCount == 0 && item.slotId == "hideout"))
{
LoggingHelpers.LogError($"item {item._tpl} found with stack count of 0, changing to {defaultStackSize}");
if (item.upd?.BuyRestrictionMax != null)
{
var parsedRestrictionMax = int.Parse(item.upd?.BuyRestrictionMax.ToString());
if (parsedRestrictionMax > defaultStackSize)
{
item.upd.StackObjectsCount = parsedRestrictionMax;
2023-01-08 16:32:31 +00:00
continue;
}
}
2023-01-08 16:32:31 +00:00
item.upd.StackObjectsCount = defaultStackSize;
2023-01-03 10:46:33 +00:00
}
}
2023-01-08 15:06:37 +00:00
private static void FixFullyPurchasedStackLimits(List<Item> items)
{
foreach (var item in items
.Where(item => item.upd?.BuyRestrictionCurrent > 0 && item.slotId == "hideout"))
{
LoggingHelpers.LogError($"item {item._tpl} found with stack count > 0, changing to 0");
item.upd.BuyRestrictionCurrent = 0;
}
}
2023-01-03 10:46:33 +00:00
/// <summary>
/// Create input/assorts/output/traders folders
/// </summary>
private static string CreateWorkingFolders()
{
var workingPath = Directory.GetCurrentDirectory();
// create input folder
var inputPath = $"{workingPath}//input";
DiskHelpers.CreateDirIfDoesntExist(inputPath);
// create sub folder in input called assorts
var assortsPath = $"{inputPath}//assorts";
DiskHelpers.CreateDirIfDoesntExist(assortsPath);
// create output folder
var outputPath = $"{workingPath}//output";
DiskHelpers.CreateDirIfDoesntExist(outputPath);
// create traders sub-folder
var tradersPath = $"{outputPath}//traders";
DiskHelpers.CreateDirIfDoesntExist(tradersPath);
return assortsPath;
}
2023-01-03 10:46:33 +00:00
/// <summary>
/// Parse raw tradersettings dump file into BaseRoot object
/// </summary>
/// <param name="filesInAssortsFolder">list of file paths</param>
/// <returns>BaseRoot</returns>
private static BaseRoot GetTraderData(IEnumerable<string> filesInAssortsFolder)
{
var traderDataPath = filesInAssortsFolder.FirstOrDefault(x => x.Contains("resp.client.trading.api.traderSettings"));
var traderDataJson = File.ReadAllText(traderDataPath);
return JsonSerializer.Deserialize<BaseRoot>(traderDataJson);
}
2023-01-03 10:46:33 +00:00
private static void WriteOutputFilesForTrader(
KeyValuePair<Trader, string> trader,
List<Item> items,
Dictionary<string, List<List<BarterObject>>> barterSchemeItems,
Dictionary<string, int> loyaltyLevelItems,
Dictionary<string, Quest> finalisedQuestData)
{
var workingPath = Directory.GetCurrentDirectory();
var traderData = GetTraderData(InputFileHelper.GetInputFilePaths());
var traderFolderPath = $"traders\\{trader.Value}";
2023-01-03 10:46:33 +00:00
// Create assort file, serialise into json and save into output folder
var outputAssortFile = new AssortRoot
{
items = items,
barter_scheme = barterSchemeItems,
loyal_level_items = loyaltyLevelItems
};
// create base file, serialise into json and save into output folder
var outputBaseFile = traderData.data.Find(x => x._id == trader.Value);
QuestAssort questAssort = GenerateQuestAssortForTrader(trader.Key, outputAssortFile, out List<AssortUnlocks> missingQuestAssorts);
AttemptToAddMissingQuestAssorts(outputAssortFile, questAssort, missingQuestAssorts);
JsonWriter.WriteJson(outputBaseFile, traderFolderPath, workingPath, "base");
JsonWriter.WriteJson(outputAssortFile, traderFolderPath, workingPath, "assort");
JsonWriter.WriteJson(questAssort, traderFolderPath, workingPath, "questassort");
//UpdateQuestAssortUnlockIds(trader.Value, questAssort, finalisedQuestData, outputAssortFile);
// create suits file for ragman
if (trader.Key == Trader.Ragman)
{
2023-01-03 10:46:33 +00:00
CreateRagmanSuitsFile(workingPath, traderFolderPath);
}
}
private static void AttemptToAddMissingQuestAssorts(AssortRoot outputAssortFile, QuestAssort questAssort, List<AssortUnlocks> missingQuestAssorts)
{
// iterate over each missing assort
foreach (var missingQuestAssort in missingQuestAssorts)
{
// Single item, maybe we can add one in (skip complex items like guns/ammo boxes etc)
if (missingQuestAssort.Items.Count == 1)
{
// Check isn't a weapon
var itemTemplate = ItemTemplateHelper.GetTemplateById(missingQuestAssort.Items[0]._tpl);
if (itemTemplate._parent == "5447b5f14bdc2d61278b4567")
{
continue;
}
if (outputAssortFile.items.Any(x => x._id == missingQuestAssort.Items[0]._id))
{
LoggingHelpers.LogError("OH NO, DUPE ID FOUND");
}
// We can add it
var item = new Item
{
_tpl = missingQuestAssort.Items[0]._tpl,
_id = missingQuestAssort.Items[0]._id,
parentId = "hideout",
slotId = "hideout",
upd = new Upd
{
StackObjectsCount = 10
}
};
outputAssortFile.items.Add(item);
var barterItemListInner = new List<BarterObject>
{
// 10,000 Roubles
new BarterObject() { _tpl = "5449016a4bdc2d6f028b456f", count = 25000 }
};
var barterItemListOuter = new List<List<BarterObject>>
{
barterItemListInner
};
outputAssortFile.barter_scheme.Add(missingQuestAssort.Items[0]._id, barterItemListOuter);
outputAssortFile.loyal_level_items[missingQuestAssort.Items[0]._id] = missingQuestAssort.LoyaltyLevel;
var associatedQuestAssort = questAssort.success.FirstOrDefault(x => x.Value == missingQuestAssort.QuestId && x.Key.StartsWith("UnknownAssortId"));
if (associatedQuestAssort.Key != null)
{
LoggingHelpers.LogWarning($"Able to replace missing quest: {missingQuestAssort.QuestId} assort with placeholder");
questAssort.success.Remove(associatedQuestAssort.Key);
questAssort.success.Add(missingQuestAssort.Items[0]._id, missingQuestAssort.QuestId);
}
}
}
}
private static void UpdateQuestAssortUnlockIds(string traderId, QuestAssort traderQuestAssort, Dictionary<string, Quest> finalisedQuestData, AssortRoot traderAssortRoot)
{
var alreadyMatchedAssortIds = new List<string>();
var assortUnlocks = QuestHelper.GetAssortUnlocks().Where(x => x.TraderId == traderId);
foreach (var assort in assortUnlocks)
{
// Find quest that matches quest assort key
Quest matchingQuest = finalisedQuestData.FirstOrDefault(x => x.Key == assort.QuestId).Value;
if (matchingQuest == null)
{
continue;
}
if (matchingQuest.QuestName == "Debut")
{
var x = 2;
}
RewardStatus matchingReward = null;
switch (assort.Criteria)
{
case "Success":
matchingReward = matchingQuest.rewards.Success.Single(x => x.id == assort.AssortUnlockId);
break;
case "Started":
matchingReward = matchingQuest.rewards.Started.Single(x => x.id == assort.AssortUnlockId);
break;
}
// Try to find assort with an _id of the quests target value
var matchingAssortInTrader = traderAssortRoot.items.SingleOrDefault(x => x._id == matchingReward.target);
if (matchingAssortInTrader == null)
{
// Mismatch! quest reward has a target that doesnt exist in trader assort
// Update to match quest
Dictionary<string,string> matching = null;
switch (assort.Criteria)
{
case "Success":
matching = traderQuestAssort.success;
break;
case "Started":
matching = traderQuestAssort.started;
break;
}
var kvp = matching.FirstOrDefault(x => x.Value == matchingQuest._id && !alreadyMatchedAssortIds.Contains(x.Value));
matchingReward.target = kvp.Key;
// Quests can have multiple unlocks per quest, keep track of processed assort ids
alreadyMatchedAssortIds.Add(kvp.Key);
}
}
}
2023-01-03 10:46:33 +00:00
/// <summary>
/// merges bear and usec ragman clothing dumps into one file
/// </summary>
/// <param name="trader"></param>
/// <param name="workingPath"></param>
/// <param name="traderFolderPath"></param>
private static void CreateRagmanSuitsFile(string workingPath, string traderFolderPath)
{
var suitFileNames = new List<string>();
suitFileNames.Add("usec.resp.client.trading.customization.");
suitFileNames.Add("bear.resp.client.trading.customization.");
var outputSuitData = new List<Suit>();
foreach (var suitSideFilename in suitFileNames)
{
var customisationFilePath = InputFileHelper.GetInputFilePaths().FirstOrDefault(x => x.Contains(suitSideFilename));
if (string.IsNullOrEmpty(customisationFilePath))
{
LoggingHelpers.LogWarning($"no suit file found: {suitSideFilename}, skipped");
continue;
}
2023-01-03 10:46:33 +00:00
var traderDataJson = File.ReadAllText(customisationFilePath);
if (traderDataJson != null)
{
var suitData = JsonSerializer.Deserialize<CustomisationRoot>(traderDataJson);
outputSuitData.AddRange(suitData.data);
}
}
2023-01-03 10:46:33 +00:00
JsonWriter.WriteJson(outputSuitData, traderFolderPath, workingPath, "suits");
}
2023-01-03 10:46:33 +00:00
/// <summary>
/// Create quest assort file that links quest completions to trader assort unlocks
/// </summary>
/// <param name="trader"></param>
/// <param name="assortRoot"></param>
/// <returns></returns>
private static QuestAssort GenerateQuestAssortForTrader(Trader trader, AssortRoot assortRoot, out List<AssortUnlocks> missingQuestAssorts)
{
missingQuestAssorts = new List<AssortUnlocks>();
var result = new QuestAssort();
var questData = QuestHelper.GetQuestData();
2023-01-03 10:46:33 +00:00
// Find assort unlocks
List<AssortUnlocks> assortUnlocks = QuestHelper.GetAssortUnlocks();
// Store already matched items
var matchedAssortItemIds = new List<string>();
int unknownCount = 1;
foreach (var assortUnlock in assortUnlocks.Where(x => x.TraderType == trader))
{
if (assortUnlock.QuestId == "60e71ce009d7c801eb0c0ec6")
2023-01-03 10:46:33 +00:00
{
var x = 2;
}
// Get unlock item details
var assortItemDetailsDB = ItemTemplateHelper.Items.FirstOrDefault(x => x.Key == assortUnlock.ItemUnlockedTemplateId);
var ItemName = assortItemDetailsDB.Value._name;
var assortItemModsFromQuest = assortUnlock.Items.Where(x => x.parentId == assortUnlock.ItemUnlockedId);
// Get matching assorts from trader
var results = GetMatchingTraderAssortsWithScore(assortRoot, assortUnlock, trader, matchedAssortItemIds);
// No matches, add a placeholder to output file
if (results.Keys.Count == 0)
{
// 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++;
missingQuestAssorts.Add(assortUnlock);
continue;
}
// All the assorts found have a matching score below 0 - very bad
if (results.Values.All( x => x < 0))
{
LoggingHelpers.LogError($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. Only negative scored matches found");
result.success.Add($"UnknownAssortId{unknownCount}", assortUnlock.QuestId);
unknownCount++;
missingQuestAssorts.Add(assortUnlock);
continue;
}
// get highest scoring match
var highestScoringAssortIdMatch = results.OrderByDescending(x => x.Value).First().Key;
// Add assort item id to blacklist so it wont be matched again
matchedAssortItemIds.Add(highestScoringAssortIdMatch);
if (result.success.ContainsKey(highestScoringAssortIdMatch))
{
LoggingHelpers.LogWarning($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ALREADY EXISTS. SKIPPING");
continue;
}
2023-01-03 10:46:33 +00:00
LoggingHelpers.LogSuccess($"{trader} item templateId: {assortUnlock.ItemUnlockedTemplateId} ({ItemName}). questId: {assortUnlock.QuestId}. ADDING TO QUEST-ASSORT");
if (assortUnlock.Criteria == "Success")
{
result.success.Add(highestScoringAssortIdMatch, assortUnlock.QuestId);
}
else if (assortUnlock.Criteria == "Started")
2023-01-03 10:46:33 +00:00
{
result.started.Add(highestScoringAssortIdMatch, assortUnlock.QuestId);
2023-01-03 10:46:33 +00:00
}
else
{
LoggingHelpers.LogError($"{assortUnlock.Criteria} quest criteria not handled");
}
//}
//if (assortUnlock.Criteria.ToLower() == "fail")
//{
// LoggingHelpers.LogError("Fail quest criteria not handled");
//}
//if (assortUnlock.Criteria.ToLower() == "started")
//{
// LoggingHelpers.LogError("started quest criteria not handled");
//}
}
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. ");
}
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);
}
}
}