Added HideoutCraftQuestIdGenerator project
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
using Core.Callbacks;
|
||||
using Core.DI;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace HideoutCraftQuestIdGenerator;
|
||||
|
||||
[Injectable]
|
||||
public class HideoutCraftQuestIdGenerator(
|
||||
ISptLogger<HideoutCraftQuestIdGenerator> _logger,
|
||||
FileUtil _fileUtil,
|
||||
JsonUtil _jsonUtil,
|
||||
DatabaseServer _databaseServer,
|
||||
LocaleService _localeService,
|
||||
ItemHelper _itemHelper,
|
||||
IEnumerable<OnLoad> _onLoadComponents
|
||||
)
|
||||
{
|
||||
private readonly List<QuestProductionOutput> _questProductionOutputList = [];
|
||||
private readonly Dictionary<string, string> _questProductionMap = new();
|
||||
|
||||
private readonly HashSet<string> _blacklistedProductions =
|
||||
[
|
||||
"6617cdb6b24b0ea24505f618", // Old event quest production "Radio Repeater" alt recipe
|
||||
"66140c4a9688754de10dac07", // Old event quest production "Documents with decrypted data"
|
||||
"661e6c26750e453380391f55", // Old event quest production "Documents with decrypted data"
|
||||
"660c2dbaa2a92e70cc074863", // Old event quest production "Decrypted flash drive"
|
||||
"67093210d514d26f8408612b" // Old event quest production "TG-Vi-24 true vaccine"
|
||||
];
|
||||
|
||||
private readonly Dictionary<string, string> _forcedQuestToProductionAssociations = new()
|
||||
{
|
||||
// KEY = PRODUCTION, VALUE = QUEST
|
||||
{ "63a571802116d261d2336cd1", "625d6ffaf7308432be1d44c5" } // Network Provider - Part 2
|
||||
};
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
// We only need the DB for this, other OnLoad events alter the data
|
||||
var dbOnload = _onLoadComponents.FirstOrDefault(x => x.GetRoute() == "spt-database");
|
||||
await dbOnload.OnLoad();
|
||||
|
||||
// Build up our dataset
|
||||
BuildQuestProductionList();
|
||||
UpdateProductionQuests();
|
||||
|
||||
// Figure out our source and target directories
|
||||
var projectDir = Directory.GetParent("./").Parent.Parent.Parent.Parent.Parent;
|
||||
var productionPath = "Libraries\\SptAssets\\Assets\\database\\hideout\\production.json";
|
||||
var productionFilePath = Path.Combine(projectDir.FullName, productionPath);
|
||||
|
||||
var updatedProductionJson = _jsonUtil.Serialize(_databaseServer.GetTables().Hideout.Production, true);
|
||||
_fileUtil.WriteFile(productionFilePath, updatedProductionJson);
|
||||
}
|
||||
|
||||
// Build a list of all quests and what production they unlock
|
||||
private void BuildQuestProductionList()
|
||||
{
|
||||
foreach (var (questId, quest) in _databaseServer.GetTables().Templates.Quests)
|
||||
{
|
||||
var combinedRewards = CombineRewards(quest.Rewards).Where(x => x.Type == RewardType.ProductionScheme).ToList();
|
||||
foreach (var reward in combinedRewards)
|
||||
{
|
||||
// Assume all productions only output a single item template
|
||||
var output = new QuestProductionOutput
|
||||
{
|
||||
QuestId = questId,
|
||||
ItemTemplate = reward.Items[0].Template,
|
||||
Quantity = 0
|
||||
};
|
||||
|
||||
// Loop over root items only, ignore children
|
||||
foreach (var item in reward.Items.Where(x => x.ParentId is null))
|
||||
{
|
||||
if (item.Template != output.ItemTemplate)
|
||||
{
|
||||
_logger.Error(
|
||||
$"Production scheme has multiple output items. " +
|
||||
$"{output.ItemTemplate} != {item.Template}"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
output.Quantity += item.Upd.StackObjectsCount.Value;
|
||||
}
|
||||
|
||||
_questProductionOutputList.Add(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProductionQuests()
|
||||
{
|
||||
// Loop through all productions, and try to associate any with a `QuestComplete` type with its quest
|
||||
foreach (var production in _databaseServer.GetTables().Hideout.Production.Recipes)
|
||||
{
|
||||
// Skip blacklisted productions
|
||||
if (_blacklistedProductions.Contains(production.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for a 'quest completion' requirement
|
||||
var questCompleteRequirements = production.Requirements.Where(req => req.Type == "QuestComplete").ToList();
|
||||
if (questCompleteRequirements.Count == 0)
|
||||
{
|
||||
// Production has no quest requirement
|
||||
continue;
|
||||
}
|
||||
|
||||
if (questCompleteRequirements.Count > 1)
|
||||
{
|
||||
_logger.Error($"Error, production: {production.Id} contains multiple QuestComplete requirements");
|
||||
|
||||
// Production has no multiple quest requirements
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for forced ids
|
||||
if (_forcedQuestToProductionAssociations.TryGetValue(production.Id, out var associatedQuestIdToComplete))
|
||||
{
|
||||
// Found one, move to next production
|
||||
_logger.Success($"FORCED - Updated: {production.Id} {production.EndProduct} ({_itemHelper.GetItemName(production.EndProduct)}) with quantity: {production.Count} to target quest: {associatedQuestIdToComplete}"
|
||||
);
|
||||
questCompleteRequirements[0].QuestId = associatedQuestIdToComplete;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to find the quest that matches this production
|
||||
var questProductionOutputs = _questProductionOutputList.Where(
|
||||
output => output.ItemTemplate == production.EndProduct && output.Quantity == production.Count
|
||||
)
|
||||
.ToList();
|
||||
|
||||
// Make sure we found valid data
|
||||
if (!IsValidQuestProduction(production, questProductionOutputs, questCompleteRequirements[0]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the production quest ID
|
||||
_questProductionMap[questProductionOutputs[0].QuestId] = production.Id;
|
||||
questCompleteRequirements[0].QuestId = questProductionOutputs[0].QuestId;
|
||||
_logger.Success(
|
||||
$"Updated: {production.Id}, {production.EndProduct} with quantity: {production.Count} to target quest: {questProductionOutputs[0].QuestId}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidQuestProduction(HideoutProduction production,
|
||||
List<QuestProductionOutput> questProductionOutputs, Requirement questComplete)
|
||||
{
|
||||
// A lot of error handling for edge cases
|
||||
if (!questProductionOutputs.Any())
|
||||
{
|
||||
_logger.Error(
|
||||
$"Unable to find quest for production: {production.Id}, endProduct: {production.EndProduct} ({_itemHelper.GetItemName(production.EndProduct)}) with quantity: {production.Count}. Potential new or removed quest"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (questProductionOutputs.Count > 1)
|
||||
{
|
||||
_logger.Error(
|
||||
$"Multiple quests match production {production.Id}, endProduct {production.EndProduct} with quantity: {production.Count}"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (questComplete.QuestId is not null && questComplete.QuestId != questProductionOutputs[0].QuestId)
|
||||
{
|
||||
_logger.Error(
|
||||
$"Multiple productions match quest.EndProduct {production.EndProduct} with quantity {production.Count}, existing quest: {questComplete.QuestId}"
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_questProductionMap.ContainsKey(questProductionOutputs[0].QuestId))
|
||||
{
|
||||
_logger.Warning(
|
||||
$"Quest {questProductionOutputs[0].QuestId} is already associated with production: {_questProductionMap[questProductionOutputs[0].QuestId]}. Potential conflict"
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private HashSet<Reward> CombineRewards(QuestRewards? questRewards)
|
||||
{
|
||||
var result = new HashSet<Reward>();
|
||||
questRewards.Started?.ForEach(x => result.Add(x));
|
||||
questRewards.Success?.ForEach(x => result.Add(x));
|
||||
questRewards.AvailableForFinish?.ForEach(x => result.Add(x));
|
||||
questRewards.Expired?.ForEach(x => result.Add(x));
|
||||
questRewards.AvailableForStart?.ForEach(x => result.Add(x));
|
||||
questRewards.Fail?.ForEach(x => result.Add(x));
|
||||
questRewards.FailRestartable?.ForEach(x => result.Add(x));
|
||||
questRewards.Started?.ForEach(x => result.Add(x));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class QuestProductionOutput
|
||||
{
|
||||
public string QuestId { get; set; }
|
||||
public string ItemTemplate { get; set; }
|
||||
public double Quantity { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<EnableDefaultContentItems>false</EnableDefaultContentItems>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Libraries\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\..\Libraries\SptAssets\SptAssets.csproj" />
|
||||
<ProjectReference Include="..\..\Libraries\SptDependencyInjection\SptDependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\..\SptCommon\SptCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,28 @@
|
||||
using Core.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SptDependencyInjection;
|
||||
|
||||
namespace HideoutCraftQuestIdGenerator;
|
||||
|
||||
public class HideoutCraftQuestIdGeneratorLauncher
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
DependencyInjectionRegistrator.RegisterSptComponents(
|
||||
typeof(HideoutCraftQuestIdGeneratorLauncher).Assembly,
|
||||
typeof(App).Assembly,
|
||||
serviceCollection
|
||||
);
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
serviceProvider.GetService<HideoutCraftQuestIdGenerator>().Run().Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Core.Models.Logging;
|
||||
using Core.Models.Spt.Logging;
|
||||
using Core.Models.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace HideoutCraftQuestIdGenerator;
|
||||
|
||||
[Injectable]
|
||||
public class SptBasicLogger<T> : ISptLogger<T>
|
||||
{
|
||||
private readonly string categoryName;
|
||||
public SptBasicLogger()
|
||||
{
|
||||
categoryName = typeof(T).Name;
|
||||
}
|
||||
|
||||
public void LogWithColor(string data, LogTextColor? textColor = null, LogBackgroundColor? backgroundColor = null,
|
||||
Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void Success(string data, Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void Error(string data, Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void Warning(string data, Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void Info(string data, Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void Debug(string data, Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void Critical(string data, Exception? ex = null)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {data}");
|
||||
}
|
||||
|
||||
public void WriteToLogFile(string body)
|
||||
{
|
||||
Console.WriteLine($"{categoryName}: {body}");
|
||||
}
|
||||
|
||||
public bool IsLogEnabled(LogLevel level)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Core.Models.Enums;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
using Path = System.IO.Path;
|
||||
@@ -18,7 +19,7 @@ public class ItemTplGenerator(
|
||||
DatabaseServer _databaseServer,
|
||||
LocaleService _localeService,
|
||||
ItemHelper _itemHelper,
|
||||
// @inject("FileSystemSync") protected fileSystemSync: FileSystemSync,
|
||||
FileUtil _fileUtil,
|
||||
IEnumerable<OnLoad> _onLoadComponents
|
||||
)
|
||||
{
|
||||
@@ -30,7 +31,7 @@ public class ItemTplGenerator(
|
||||
public async Task Run()
|
||||
{
|
||||
itemOverrides = ItemOverrides.ItemOverridesDictionary;
|
||||
// Load all of the onload components, this gives us access to most of SPTs injections
|
||||
// Load all onload components, this gives us access to most of SPTs injections
|
||||
foreach (var onLoad in _onLoadComponents)
|
||||
{
|
||||
if (onLoad is HttpCallbacks)
|
||||
@@ -563,6 +564,7 @@ public class ItemTplGenerator(
|
||||
}
|
||||
|
||||
// TODO: enable once we dont get any more errors
|
||||
throw new NotImplementedException();
|
||||
// this.fileSystemSync.write(outputPath, enumFileData);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -1,5 +1,8 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35527.113 d17.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{1F5ED9C6-8B1F-4776-85AB-B387CBBC5557}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Libraries\Core\Core.csproj", "{AC8643DC-8779-4B4A-BBDA-2D4CC466F765}"
|
||||
@@ -20,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SptCommon", "SptCommon\SptC
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SptAssets", "Libraries\SptAssets\SptAssets.csproj", "{4B973AC0-0C60-4853-9AF7-7CB69127473E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HideoutCraftQuestIdGenerator", "Tools\HideoutCraftQuestIdGenerator\HideoutCraftQuestIdGenerator.csproj", "{C24B1FEB-F8AC-434E-998D-5DA4D1687295}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -58,15 +63,20 @@ Global
|
||||
{4B973AC0-0C60-4853-9AF7-7CB69127473E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B973AC0-0C60-4853-9AF7-7CB69127473E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4B973AC0-0C60-4853-9AF7-7CB69127473E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C24B1FEB-F8AC-434E-998D-5DA4D1687295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C24B1FEB-F8AC-434E-998D-5DA4D1687295}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C24B1FEB-F8AC-434E-998D-5DA4D1687295}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C24B1FEB-F8AC-434E-998D-5DA4D1687295}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{AC8643DC-8779-4B4A-BBDA-2D4CC466F765} = {F084DDFD-89F3-44F9-89C3-5CA11F4CDEEF}
|
||||
{4B4AF50D-B2C6-47D1-B567-EA4560D8CBA1} = {F084DDFD-89F3-44F9-89C3-5CA11F4CDEEF}
|
||||
{00897F10-1AB3-4DC7-8DF9-5EA1D0289ACF} = {587959C2-5AFA-4B77-B327-566610F9A289}
|
||||
{AC8643DC-8779-4B4A-BBDA-2D4CC466F765} = {F084DDFD-89F3-44F9-89C3-5CA11F4CDEEF}
|
||||
{DB049C81-DEC0-490D-AC06-7AF4DC8C0571} = {F084DDFD-89F3-44F9-89C3-5CA11F4CDEEF}
|
||||
{4B973AC0-0C60-4853-9AF7-7CB69127473E} = {F084DDFD-89F3-44F9-89C3-5CA11F4CDEEF}
|
||||
{C24B1FEB-F8AC-434E-998D-5DA4D1687295} = {587959C2-5AFA-4B77-B327-566610F9A289}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Reference in New Issue
Block a user