Refactor Item tpl generator to handle multiple generators + add quest tpl generator (#493)

This commit is contained in:
Cj
2025-07-20 14:17:29 -04:00
committed by GitHub
parent cbcfa370bc
commit 1d1f872875
15 changed files with 4490 additions and 10649 deletions
+34
View File
@@ -0,0 +1,34 @@
using MongoIdTplGenerator.Generators;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.DI;
using SPTarkov.Server.Core.Models.Utils;
namespace MongoIdTplGenerator;
[Injectable(InjectionType.Singleton)]
public class Application(
ISptLogger<Application> logger,
IEnumerable<IOnLoad> onloadComponents,
IEnumerable<IMongoIdGenerator> generators
)
{
public async Task Run()
{
foreach (var onLoad in onloadComponents)
{
await onLoad.OnLoad();
}
try
{
foreach (var generator in generators)
{
await generator.Run();
}
}
catch (Exception e)
{
logger.Critical("Error running generator(s)", e);
}
}
}
@@ -0,0 +1,6 @@
namespace MongoIdTplGenerator.Generators;
public interface IMongoIdGenerator
{
Task Run();
}
@@ -1,6 +1,6 @@
using MongoIdTplGenerator.Utils;
using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Callbacks;
using SPTarkov.Server.Core.DI;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
@@ -12,31 +12,26 @@ using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using Path = System.IO.Path;
namespace ItemTplGenerator;
namespace MongoIdTplGenerator.Generators;
[Injectable]
public class ItemTplGenerator(
ISptLogger<ItemTplGenerator> _logger,
DatabaseServer _databaseServer,
LocaleService _localeService,
ItemHelper _itemHelper,
FileUtil _fileUtil,
IEnumerable<IOnLoad> _onLoadComponents
)
public class ItemTplMongoIdGenerator(
ISptLogger<ItemTplMongoIdGenerator> logger,
DatabaseServer databaseServer,
LocaleService localeService,
ItemHelper itemHelper,
FileUtil fileUtil,
LocaleUtil localeUtil
) : IMongoIdGenerator
{
private readonly HashSet<string> collidedEnumKeys = [];
private string _enumDir;
private IDictionary<string, string> _itemOverrides;
private Dictionary<MongoId, TemplateItem> _items;
public async Task Run()
public Task Run()
{
_itemOverrides = ItemOverrides.ItemOverridesDictionary;
// Load all onload components, this gives us access to most of SPTs injections
foreach (var onLoad in _onLoadComponents)
{
await onLoad.OnLoad();
}
// Figure out our source and target directories
var projectDir = Directory.GetParent("./").Parent.Parent.Parent.Parent.Parent;
@@ -47,7 +42,7 @@ public class ItemTplGenerator(
"Models",
"Enums"
);
_items = _databaseServer.GetTables().Templates.Items;
_items = databaseServer.GetTables().Templates.Items;
// Generate an object containing all item name to ID associations
var orderedItemsObject = GenerateItemsObject();
@@ -75,7 +70,9 @@ public class ItemTplGenerator(
}
);
_logger.Info("Generating items finished");
logger.Info("Generating items finished");
return Task.CompletedTask;
}
/// <summary>
@@ -129,7 +126,7 @@ public class ItemTplGenerator(
var itemKey = $"{itemParentName}{itemPrefix}{itemName}{itemSuffix}";
// Strip out any remaining special characters
itemKey = SanitizeEnumKey(itemKey);
itemKey = localeUtil.SanitizeEnumKey(itemKey);
// If the key already exists, see if we can add a suffix to both this, and the existing conflicting item
if (itemsObject.ContainsKey(itemKey) || collidedEnumKeys.Contains(itemKey))
@@ -147,18 +144,20 @@ public class ItemTplGenerator(
var oldItemNameSuffix = GetItemNameSuffix(_items[oldItemId]);
if (!string.IsNullOrEmpty(oldItemNameSuffix))
{
var oldItemNewKey = SanitizeEnumKey($"{itemKey}_{oldItemNameSuffix}");
var oldItemNewKey = localeUtil.SanitizeEnumKey(
$"{itemKey}_{oldItemNameSuffix}"
);
itemsObject.Remove(itemKey);
itemsObject[oldItemNewKey] = oldItemId;
}
}
itemKey = SanitizeEnumKey($"{itemKey}_{itemNameSuffix}");
itemKey = localeUtil.SanitizeEnumKey($"{itemKey}_{itemNameSuffix}");
// If we still collide, log an error
if (itemsObject.TryGetValue(itemKey, out var value))
{
_logger.Error(
logger.Error(
$"After rename, itemsObject already contains {itemKey} {value} => {item.Id}"
);
}
@@ -166,7 +165,7 @@ public class ItemTplGenerator(
else
{
var val = itemsObject.GetValueOrDefault(itemKey, itemKey);
_logger.Error(
logger.Error(
$"New itemOverride entry required: itemsObject already contains {itemKey} {val} => {item.Id}"
);
continue;
@@ -191,13 +190,13 @@ public class ItemTplGenerator(
in _items
)
{
if (!_itemHelper.IsOfBaseclass(kv.Key, BaseClasses.WEAPON))
if (!itemHelper.IsOfBaseclass(kv.Key, BaseClasses.WEAPON))
{
continue;
}
var caliber = CleanCaliber(kv.Value.Properties.AmmoCaliber.ToUpper());
var weaponShortName = _localeService.GetLocaleDb()[$"{kv.Key} ShortName"]?.ToUpper();
var weaponShortName = localeService.GetLocaleDb()[$"{kv.Key} ShortName"]?.ToUpper();
// Special case for the weird duplicated grenade launcher
if (kv.Key == "639c3fbbd0446708ee622ee9")
@@ -206,7 +205,7 @@ public class ItemTplGenerator(
}
// Include any bracketed suffixes that exist, handles the case of colored gun variants
var weaponFullName = _localeService.GetLocaleDb()[$"{kv.Key} Name"]?.ToUpper();
var weaponFullName = localeService.GetLocaleDb()[$"{kv.Key} Name"]?.ToUpper();
if (
weaponFullName.RegexMatch(@"\((.+?)\)$", out var itemNameBracketSuffix)
&& !weaponShortName.EndsWith(itemNameBracketSuffix.Groups[1].Value)
@@ -225,7 +224,7 @@ public class ItemTplGenerator(
if (weaponsObject.ContainsKey(weaponName))
{
_logger.Error($"weapon {weaponName} already exists");
logger.Error($"weapon {weaponName} already exists");
continue;
}
@@ -239,16 +238,6 @@ public class ItemTplGenerator(
return orderedWeaponsObject;
}
/// <summary>
/// Clear any non-alpha numeric characters, and fix multiple underscores
/// </summary>
/// <param name="enumKey">The enum key to sanitize</param>
/// <returns>The sanitized enum key</returns>
private string SanitizeEnumKey(string enumKey)
{
return enumKey.ToUpper().RegexReplace("[^A-Z0-9_]", "").RegexReplace("_+", "_");
}
private string GetParentName(TemplateItem item)
{
if (item.Properties?.QuestItem is true)
@@ -256,42 +245,42 @@ public class ItemTplGenerator(
return "QUEST";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.BARTER_ITEM))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.BARTER_ITEM))
{
return "BARTER";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.THROW_WEAPON))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.THROW_WEAPON))
{
return "GRENADE";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.STIMULATOR))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.STIMULATOR))
{
return "STIM";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.MAGAZINE))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.MAGAZINE))
{
return "MAGAZINE";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.KEY_MECHANICAL))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.KEY_MECHANICAL))
{
return "KEY";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.MOB_CONTAINER))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.MOB_CONTAINER))
{
return "SECURE";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.SIMPLE_CONTAINER))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.SIMPLE_CONTAINER))
{
return "CONTAINER";
}
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.PORTABLE_RANGE_FINDER))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.PORTABLE_RANGE_FINDER))
{
return "RANGEFINDER";
}
@@ -339,17 +328,17 @@ public class ItemTplGenerator(
var prefix = "";
// Prefix ammo with its caliber
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO))
{
prefix = GetAmmoPrefix(item);
}
// Prefix ammo boxes with their caliber
else if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO_BOX))
else if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO_BOX))
{
prefix = GetAmmoBoxPrefix(item);
}
// Prefix magazines with their caliber
else if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.MAGAZINE))
else if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.MAGAZINE))
{
prefix = GetMagazinePrefix(item);
}
@@ -368,12 +357,12 @@ public class ItemTplGenerator(
var suffix = "";
// Add mag size for magazines
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.MAGAZINE))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.MAGAZINE))
{
suffix = $"{item.Properties?.Cartridges?[0].MaxCount?.ToString()}RND";
}
// Add pack size for ammo boxes
else if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO_BOX))
else if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO_BOX))
{
suffix = $"{item.Properties.StackSlots[0]?.MaxCount.ToString()}RND";
}
@@ -436,7 +425,7 @@ public class ItemTplGenerator(
private string GetItemName(TemplateItem item)
{
string? itemName = null;
var localeDb = _localeService.GetLocaleDb();
var localeDb = localeService.GetLocaleDb();
// Manual item name overrides
if (_itemOverrides.TryGetValue(item.Id, out var itemNameOverride))
@@ -445,7 +434,7 @@ public class ItemTplGenerator(
}
// For the listed types, user the item's _name property
else if (
_itemHelper.IsOfBaseclasses(
itemHelper.IsOfBaseclasses(
item.Id,
[BaseClasses.RANDOM_LOOT_CONTAINER, BaseClasses.BUILT_IN_INSERTS, BaseClasses.STASH]
)
@@ -455,7 +444,7 @@ public class ItemTplGenerator(
}
// For the listed types, use the short name
else if (
_itemHelper.IsOfBaseclasses(
itemHelper.IsOfBaseclasses(
item.Id,
[BaseClasses.AMMO, BaseClasses.AMMO_BOX, BaseClasses.MAGAZINE]
)
@@ -503,17 +492,17 @@ public class ItemTplGenerator(
private string? GetItemNameSuffix(TemplateItem item)
{
var localeDb = _localeService.GetLocaleDb();
var localeDb = localeService.GetLocaleDb();
localeDb.TryGetValue($"{item.Id} Name", out var itemName);
// Add grid size for lootable containers
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.LOOT_CONTAINER))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.LOOT_CONTAINER))
{
return $"{item.Properties.Grids[0]?.Props.CellsH}X{item.Properties.Grids[0]?.Props.CellsV}";
}
// Add ammo caliber to conflicting weapons
if (_itemHelper.IsOfBaseclass(item.Id, BaseClasses.WEAPON))
if (itemHelper.IsOfBaseclass(item.Id, BaseClasses.WEAPON))
{
var caliber = CleanCaliber(item.Properties.AmmoCaliber.ToUpper());
@@ -565,7 +554,7 @@ public class ItemTplGenerator(
{
if (originalEnumValues.ContainsKey(kv.Value) && originalEnumValues[kv.Value] != kv.Key)
{
_logger.Warning(
logger.Warning(
$"Enum {enumName} key has changed for {kv.Value}, {originalEnumValues[kv.Value]} => {kv.Key}"
);
}
@@ -579,7 +568,7 @@ public class ItemTplGenerator(
{
var enumFileData =
"using SPTarkov.Server.Core.Models.Common;\n\n"
+ "// This is an auto generated file, do not modify. Re-generate by running ItemTplGenerator.exe";
+ "// This is an auto generated file, do not modify. Re-generate by running MongoIdTplGenerator.exe";
foreach (var (enumName, data) in enumEntries)
{
@@ -594,6 +583,6 @@ public class ItemTplGenerator(
enumFileData += "}\n";
}
_fileUtil.WriteFile(outputPath, enumFileData);
fileUtil.WriteFile(outputPath, enumFileData);
}
}
@@ -0,0 +1,99 @@
using MongoIdTplGenerator.Utils;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using Path = System.IO.Path;
namespace MongoIdTplGenerator.Generators;
[Injectable]
public class QuestTplMongoIdGenerator(
ISptLogger<QuestTplMongoIdGenerator> logger,
DatabaseServer databaseServer,
LocaleService localeService,
FileUtil fileUtil,
LocaleUtil localeUtil
) : IMongoIdGenerator
{
private string? _enumDir;
private Dictionary<string, Quest>? _quests;
public Task Run()
{
// Figure out our source and target directories
var projectDir = Directory.GetParent("./").Parent.Parent.Parent.Parent.Parent;
_enumDir = Path.Combine(
projectDir.FullName,
"Libraries",
"SPTarkov.Server.Core",
"Models",
"Enums"
);
_quests = databaseServer.GetTables().Templates.Quests;
var questTplObject = GenerateQuestTplObject();
var questTplOutPath = Path.Combine(_enumDir, "QuestTpl.cs");
WriteEnumToFile(questTplOutPath, questTplObject);
return Task.CompletedTask;
}
private Dictionary<string, string> GenerateQuestTplObject()
{
var result = new Dictionary<string, string>();
foreach (var quest in _quests)
{
var id = quest.Key;
if (QuestOverrides.NameOverridesDictionary.TryGetValue(id, out var nameOverride))
{
if (!result.TryAdd(nameOverride, id))
{
logger.Warning(
$"Duplicate locale name: {nameOverride} with id: {id} in quest list"
);
}
continue;
}
var locale = localeService
.GetLocaleDb()[$"{id} name"]
.Replace(" ", "_")
.Replace("-", "_");
locale = localeUtil.SanitizeEnumKey(locale);
if (!result.TryAdd(locale, id))
{
logger.Warning($"Duplicate locale name: {locale} with id: {id} in quest list");
}
}
return result;
}
private void WriteEnumToFile(string outputPath, Dictionary<string, string> enumEntries)
{
var enumFileData =
"using SPTarkov.Server.Core.Models.Common;\n\n"
+ "// This is an auto generated file, do not modify. Re-generate by running MongoIdTplGenerator.exe";
enumFileData += $"\npublic static class QuestTpl\n{{\n";
foreach (var (enumName, data) in enumEntries)
{
enumFileData +=
$" public static readonly MongoId {enumName} = new MongoId(\"{data}\");\n";
}
enumFileData += "}\n";
fileUtil.WriteFile(outputPath, enumFileData);
}
}
@@ -4,11 +4,11 @@ using SPTarkov.DI;
using SPTarkov.Server.Core.Models.Spt.Mod;
using SPTarkov.Server.Core.Utils;
namespace ItemTplGenerator;
namespace MongoIdTplGenerator;
public class ItemTplGeneratorLauncher
public class Program
{
public static void Main(string[] args)
public static async Task Main(string[] args)
{
try
{
@@ -19,12 +19,13 @@ public class ItemTplGeneratorLauncher
serviceCollection.AddSingleton<IReadOnlyList<SptMod>>([]);
var diHandler = new DependencyInjectionHandler(serviceCollection);
diHandler.AddInjectableTypesFromTypeAssembly(typeof(ItemTplGeneratorLauncher));
diHandler.AddInjectableTypesFromTypeAssembly(typeof(Program));
diHandler.AddInjectableTypesFromTypeAssembly(typeof(App));
diHandler.InjectAll();
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<ItemTplGenerator>().Run().Wait();
await serviceProvider.GetService<Application>()?.Run()!;
}
catch (Exception e)
{
@@ -1,6 +1,6 @@
using System.Collections.ObjectModel;
namespace ItemTplGenerator;
namespace MongoIdTplGenerator.Utils;
public class ItemOverrides
{
@@ -0,0 +1,18 @@
using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations;
namespace MongoIdTplGenerator.Utils;
[Injectable]
public class LocaleUtil
{
/// <summary>
/// Clear any non-alpha numeric characters, and fix multiple underscores
/// </summary>
/// <param name="enumKey">The enum key to sanitize</param>
/// <returns>The sanitized enum key</returns>
public string SanitizeEnumKey(string enumKey)
{
return enumKey.ToUpper().RegexReplace("[^A-Z0-9_]", "").RegexReplace("_+", "_");
}
}
@@ -0,0 +1,26 @@
using System.Collections.ObjectModel;
namespace MongoIdTplGenerator.Utils;
public class QuestOverrides
{
public static readonly ReadOnlyDictionary<string, string> NameOverridesDictionary = new(
new Dictionary<string, string>
{
// Bear duplicates
{ "5e381b0286f77420e3417a74", "TEXTILE_PART_1_BEAR" },
{ "5e4d4ac186f774264f758336", "TEXTILE_PART_2_BEAR" },
{ "6613f3007f6666d56807c929", "DRIP_OUT_PART_1_BEAR" },
{ "6613f307fca4f2f386029409", "DRIP_OUT_PART_2_BEAR" },
// Usec duplicates
{ "5e383a6386f77465910ce1f3", "TEXTILE_PART_1_USEC" },
{ "5e4d515e86f77438b2195244", "TEXTILE_PART_2_USEC" },
{ "66151401efb0539ae10875ae", "DRIP_OUT_PART_1_USEC" },
{ "6615141bfda04449120269a7", "DRIP_OUT_PART_2_USEC" },
// Generic duplicates
{ "6658a15615cbb1b2c6014d5b", "HUSTLE_2" },
{ "6744a9dfef61d56e020b5c4a", "BATTERY_CHANGE_2" },
{ "6745cbee909d2013670a4a55", "THE_PRICE_OF_INDEPENDENCE_2" },
}
);
}
@@ -3,7 +3,7 @@ using SPTarkov.Server.Core.Models.Logging;
using SPTarkov.Server.Core.Models.Spt.Logging;
using SPTarkov.Server.Core.Models.Utils;
namespace ItemTplGenerator;
namespace MongoIdTplGenerator.Utils;
[Injectable]
public class SptBasicLogger<T> : ISptLogger<T>