runnable app!

This commit is contained in:
Alex
2025-01-07 17:28:49 +00:00
parent b1d8eaded9
commit c751918ff6
34 changed files with 1263 additions and 577 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
namespace Core.Annotations;
[AttributeUsage(AttributeTargets.Class)]
public class Injectable(InjectionType injectionType = InjectionType.Scoped, Type? type = null) : Attribute
public class Injectable(InjectionType injectionType = InjectionType.Scoped, Type? type = null, int typePriority = Int32.MaxValue) : Attribute
{
public InjectionType InjectionType { get; set; } = injectionType;
public Type? InjectableTypeOverride { get; set; } = type;
public int TypePriority { get; set; } = typePriority;
}
public enum InjectionType
+14 -6
View File
@@ -1,26 +1,34 @@
using Core.DI;
using Core.Annotations;
using Core.Context;
using Core.DI;
using Core.Servers;
namespace Core.Callbacks;
[Injectable(InjectionType.Singleton, typePriority: 1)]
public class HttpCallbacks : OnLoad
{
public HttpCallbacks()
private readonly HttpServer _httpServer;
private readonly ApplicationContext _applicationContext;
public HttpCallbacks(HttpServer httpServer, ApplicationContext applicationContext)
{
_httpServer = httpServer;
_applicationContext = applicationContext;
}
public async Task OnLoad()
{
throw new NotImplementedException();
_httpServer.Load((WebApplicationBuilder) _applicationContext.GetLatestValue(ContextVariableType.APP_BUILDER).Value);
_applicationContext.ClearValues(ContextVariableType.APP_BUILDER);
}
public string GetRoute()
{
throw new NotImplementedException();
return "spt-http";
}
public string GetImage()
{
throw new NotImplementedException();
return "";
}
}
+1
View File
@@ -13,4 +13,5 @@ public enum ContextVariableType
RAID_ADJUSTMENTS = 4,
/** Data returned from client request object from endLocalRaid() */
TRANSIT_INFO = 5,
APP_BUILDER = 6
}
+2
View File
@@ -10,10 +10,12 @@ public class DialogueController
///
/// </summary>
/// <param name="chatBot"></param>
/*
public void RegisterChatBot(DialogueChatBot chatBot) // TODO: this is in with the helper types
{
throw new NotImplementedException();
}
*/
/// <summary>
/// Handle onUpdate spt event
+2
View File
@@ -70,10 +70,12 @@ public class GameController
/// </summary>
/// <param name="sessionId"></param>
/// <returns></returns>
/*
public CurrentGroupResponse GetCurrentGroup(string sessionId)
{
throw new NotImplementedException();
}
*/
/// <summary>
/// Handle client/checkVersion
+1 -1
View File
@@ -125,7 +125,7 @@ public class TradeController
private void MailMoneyToPlayer(
string sessionId,
int roublesToSend,
Traders trader) // TODO: This is a static class now and cannot be passed as a param.
string trader)
{
throw new NotImplementedException();
}
File diff suppressed because it is too large Load Diff
+4
View File
@@ -9,9 +9,13 @@ public class Item
public string Id { get; set; }
[JsonPropertyName("_tpl")]
public string Template { get; set; }
[JsonPropertyName("parentId")]
public string? ParentId { get; set; }
[JsonPropertyName("slotId")]
public string? SlotId { get; set; }
[JsonPropertyName("location")]
public object? Location { get; set; } // TODO: Can be IItemLocation or number
[JsonPropertyName("upd")]
public Upd? Update { get; set; }
}
+26 -1
View File
@@ -1,7 +1,32 @@
using System.Text.Json.Serialization;
using Core.Models.Eft.Match;
namespace Core.Models.Eft.Ws;
public class WsGroupMatchInviteAccept : WsNotificationEvent, GroupCharacter // TODO: trying to inherit multiTypes
public class WsGroupMatchInviteAccept : WsNotificationEvent // TODO: trying to inherit multiTypes
{
// Copy pasted properties from GroupCharacter to resolve multitype
[JsonPropertyName("_id")]
public string Id { get; set; }
[JsonPropertyName("aid")]
public int Aid { get; set; }
[JsonPropertyName("Info")]
public CharacterInfo Info { get; set; }
[JsonPropertyName("PlayerVisualRepresentation")]
public PlayerVisualRepresentation? VisualRepresentation { get; set; }
[JsonPropertyName("isLeader")]
public bool IsLeader { get; set; }
[JsonPropertyName("isReady")]
public bool? IsReady { get; set; }
[JsonPropertyName("region")]
public string? Region { get; set; }
[JsonPropertyName("lookingGroup")]
public bool? LookingGroup { get; set; }
}
+11
View File
@@ -0,0 +1,11 @@
namespace Core.Models.Enums;
public enum ModSpawn
{
/** Chosen mod should be the tpl from the default weapon template */
DEFAULT_MOD = 0,
/** Normal behaviour */
SPAWN = 1,
/** Item should not be chosen */
SKIP = 2,
}
@@ -19,7 +19,7 @@ public class GenerateEquipmentProperties
public Dictionary<string, int> RootEquipmentPool { get; set; }
[JsonPropertyName("modPool")]
public Mods ModPool { get; set; }
public GlobalMods ModPool { get; set; }
/// <summary>
/// Dictionary of mod items and their chance to spawn for this bot type
@@ -34,7 +34,7 @@ public class GenerateEquipmentProperties
public BotData BotData { get; set; }
[JsonPropertyName("inventory")]
public PmcInventory Inventory { get; set; }
public BotBaseInventory Inventory { get; set; }
[JsonPropertyName("botEquipmentConfig")]
public EquipmentFilters BotEquipmentConfig { get; set; }
+28 -1
View File
@@ -11,7 +11,7 @@ public class GenerateWeaponRequest
/** Pool of compatible mods to attach to weapon */
[JsonPropertyName("modPool")]
public Mods ModPool { get; set; }
public GlobalMods ModPool { get; set; }
/** ParentId of weapon */
[JsonPropertyName("weaponId")]
@@ -71,4 +71,31 @@ public class WeaponStats
[JsonPropertyName("hasRearIronSight")]
public bool? HasRearIronSight { get; set; }
}
public class BotModLimits
{
[JsonPropertyName("scope")]
public ItemCount Scope { get; set; }
[JsonPropertyName("scopeMax")]
public int ScopeMax { get; set; }
[JsonPropertyName("scopeBaseTypes")]
public string[] ScopeBaseTypes { get; set; }
[JsonPropertyName("flashlightLaser")]
public ItemCount FlashlightLaser { get; set; }
[JsonPropertyName("flashlightLaserMax")]
public int FlashlightLaserMax { get; set; }
[JsonPropertyName("flashlightLaserBaseTypes")]
public string[] FlashlightLaserBaseTypes { get; set; }
}
public class ItemCount
{
[JsonPropertyName("count")]
public int Count { get; set; }
}
+1 -1
View File
@@ -15,7 +15,7 @@ public class GenerateWeaponResult
public string ChosenUbglAmmoTemplate { get; set; }
[JsonPropertyName("weaponMods")]
public Mods WeaponMods { get; set; }
public GlobalMods WeaponMods { get; set; }
[JsonPropertyName("weaponTemplate")]
public TemplateItem WeaponTemplate { get; set; }
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using Core.Models.Eft.Common.Tables;
using Core.Models.Enums;
using Core.Models.Spt.Config;
namespace Core.Models.Spt.Bots;
+1 -1
View File
@@ -23,7 +23,7 @@ public class HttpConfig : BaseConfig
public string BackendIp { get; set; }
[JsonPropertyName("backendPort")]
public string BackendPort { get; set; }
public int BackendPort { get; set; }
[JsonPropertyName("webSocketPingDelayMs")]
public int WebSocketPingDelayMs { get; set; }
+17 -17
View File
@@ -205,55 +205,55 @@ public class BotTypeLimit : MinMax
public class LootMultiplier
{
[JsonPropertyName("bigmap")]
public int BigMap { get; set; }
public double BigMap { get; set; }
[JsonPropertyName("develop")]
public int Develop { get; set; }
public double Develop { get; set; }
[JsonPropertyName("factory4_day")]
public int Factory4Day { get; set; }
public double Factory4Day { get; set; }
[JsonPropertyName("factory4_night")]
public int Factory4Night { get; set; }
public double Factory4Night { get; set; }
[JsonPropertyName("interchange")]
public int Interchange { get; set; }
public double Interchange { get; set; }
[JsonPropertyName("laboratory")]
public int Laboratory { get; set; }
public double Laboratory { get; set; }
[JsonPropertyName("rezervbase")]
public int RezervBase { get; set; }
public double RezervBase { get; set; }
[JsonPropertyName("shoreline")]
public int Shoreline { get; set; }
public double Shoreline { get; set; }
[JsonPropertyName("woods")]
public int Woods { get; set; }
public double Woods { get; set; }
[JsonPropertyName("hideout")]
public int Hideout { get; set; }
public double Hideout { get; set; }
[JsonPropertyName("lighthouse")]
public int Lighthouse { get; set; }
public double Lighthouse { get; set; }
[JsonPropertyName("privatearea")]
public int PrivateArea { get; set; }
public double PrivateArea { get; set; }
[JsonPropertyName("suburbs")]
public int Suburbs { get; set; }
public double Suburbs { get; set; }
[JsonPropertyName("tarkovstreets")]
public int TarkovStreets { get; set; }
public double TarkovStreets { get; set; }
[JsonPropertyName("terminal")]
public int Terminal { get; set; }
public double Terminal { get; set; }
[JsonPropertyName("town")]
public int Town { get; set; }
public double Town { get; set; }
[JsonPropertyName("sandbox")]
public int Sandbox { get; set; }
public double Sandbox { get; set; }
}
public class ContainerRandomisationSettings
+7 -5
View File
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using Core.Models.Common;
using Core.Models.Enums;
using Core.Utils.Json.Converters;
namespace Core.Models.Spt.Config;
@@ -77,7 +78,8 @@ public class EventQuestData
public long StartTimestamp { get; set; }
[JsonPropertyName("endTimestamp")]
public long EndTimestamp { get; set; }
[JsonConverter(typeof(NullableObjectToLongConverter))]
public long? EndTimestamp { get; set; }
[JsonPropertyName("yearly")]
public bool Yearly { get; set; }
@@ -157,13 +159,13 @@ public class RewardScaling
public List<int> Items { get; set; }
[JsonPropertyName("reputation")]
public List<int> Reputation { get; set; }
public List<double> Reputation { get; set; }
[JsonPropertyName("rewardSpread")]
public int RewardSpread { get; set; }
public double RewardSpread { get; set; }
[JsonPropertyName("skillRewardChance")]
public List<int> SkillRewardChance { get; set; }
public List<double> SkillRewardChance { get; set; }
[JsonPropertyName("skillPointReward")]
public List<int> SkillPointReward { get; set; }
@@ -217,7 +219,7 @@ public class Exploration : BaseQuestConfig
public class SpecificExits
{
[JsonPropertyName("probability")]
public int Probability { get; set; }
public double Probability { get; set; }
[JsonPropertyName("passageRequirementWhitelist")]
public List<string> PassageRequirementWhitelist { get; set; }
+1 -1
View File
@@ -59,7 +59,7 @@ public class Chance
/** Value to multiply the sell chance by */
[JsonPropertyName("sellMultiplier")]
public int SellMultiplier { get; set; }
public double SellMultiplier { get; set; }
/** Max possible sell chance % for a player listed offer */
[JsonPropertyName("maxSellChancePercent")]
+7 -2
View File
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using Core.Models.Common;
using Core.Models.Enums;
using Core.Utils.Json.Converters;
namespace Core.Models.Spt.Config;
@@ -31,15 +32,19 @@ public class SeasonDateTimes
public string Name { get; set; }
[JsonPropertyName("startDay")]
[JsonConverter(typeof(NotNullObjectToIntConverter))]
public int StartDay { get; set; }
[JsonPropertyName("startMonth")]
[JsonConverter(typeof(NotNullObjectToIntConverter))]
public int StartMonth { get; set; }
[JsonPropertyName("endDay")]
[JsonConverter(typeof(NotNullObjectToIntConverter))]
public int EndDay { get; set; }
[JsonPropertyName("endMonth")]
[JsonConverter(typeof(NotNullObjectToIntConverter))]
public int EndMonth { get; set; }
}
@@ -60,7 +65,7 @@ public class WeatherValues
public class SeasonalValues
{
[JsonPropertyName("clouds")]
public WeatherSettings<string> Clouds { get; set; }
public WeatherSettings<double> Clouds { get; set; }
[JsonPropertyName("windSpeed")]
public WeatherSettings<double> WindSpeed { get; set; }
@@ -78,7 +83,7 @@ public class SeasonalValues
public MinMax RainIntensity { get; set; }
[JsonPropertyName("fog")]
public WeatherSettings<string> Fog { get; set; }
public WeatherSettings<double> Fog { get; set; }
[JsonPropertyName("temp")]
public TempDayNight Temp { get; set; }
+1 -1
View File
@@ -89,7 +89,7 @@ public class ProfileChangeEvent
public ProfileChangeEventType Type { get; set; }
[JsonPropertyName("value")]
public int Value { get; set; }
public double Value { get; set; }
[JsonPropertyName("entity")]
public string? Entity { get; set; }
+10 -10
View File
@@ -5,14 +5,14 @@ namespace Core.Models.Spt.Server;
public class DatabaseTables
{
public Bots.Bots? bots { get; }
public Hideout.Hideout? hideout { get; }
public LocaleBase? locales { get; }
public Locations? locations { get; }
public Match? match { get; }
public Templates.Templates? templates { get; }
public Dictionary<string, Trader>? traders { get; }
public Globals? globals { get; }
public ServerBase? server { get; }
public SettingsBase? settings { get; }
public Bots.Bots? bots { get; set; }
public Hideout.Hideout? hideout { get; set; }
public LocaleBase? locales { get; set; }
public Locations? locations { get; set; }
public Match? match { get; set; }
public Templates.Templates? templates { get; set; }
public Dictionary<string, Trader>? traders { get; set; }
public Globals? globals { get; set; }
public ServerBase? server { get; set; }
public SettingsBase? settings { get; set; }
}
+5 -4
View File
@@ -1,5 +1,6 @@
using System.Runtime.InteropServices.JavaScript;
using System.Text.Json;
using System.Text.Json.Serialization;
using Core.Annotations;
using Core.Models.Enums;
using Core.Models.Spt.Config;
@@ -11,8 +12,8 @@ namespace Core.Servers;
public class ConfigServer
{
private ILogger _logger;
protected Dictionary<string, object> configs;
protected readonly string[] acceptableFileExtensions = ["json", "jsonc"];
protected Dictionary<string, object> configs = new();
protected readonly string[] acceptableFileExtensions = [".json", ".jsonc"];
public ConfigServer(
ILogger logger
@@ -53,7 +54,7 @@ public class ConfigServer
{
var fileContent = File.ReadAllText(file);
var type = GetConfigTypeByFilename(file);
var deserializedContent = JsonSerializer.Deserialize(fileContent, type);
var deserializedContent = JsonSerializer.Deserialize(fileContent, type, options: new JsonSerializerOptions() {Converters = { new JsonStringEnumConverter() }});
if (deserializedContent == null)
{
@@ -61,7 +62,7 @@ public class ConfigServer
throw new Exception($"Server will not run until the: {file} config error mentioned above is fixed");
}
this.configs[$"spt-{Path.GetFileNameWithoutExtension(file)}"] = deserializedContent;
configs[$"spt-{Path.GetFileNameWithoutExtension(file)}"] = deserializedContent;
}
}
+3 -1
View File
@@ -1,7 +1,9 @@
using Core.Models.Spt.Server;
using Core.Annotations;
using Core.Models.Spt.Server;
namespace Core.Servers;
[Injectable(InjectionType.Singleton)]
public class DatabaseServer
{
protected DatabaseTables tableData = new();
+1 -1
View File
@@ -36,7 +36,7 @@ public class LocalisationService
public string GetText(string key, object? args = null)
{
throw new NotImplementedException();
return _i18nService.GetLocalised(key, args);
}
public ICollection<string> GetKeys()
+80
View File
@@ -1,4 +1,5 @@
using Core.Annotations;
using Core.DI;
using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Servers;
@@ -46,4 +47,83 @@ public class App
_coreConfig = configServer.GetConfig<CoreConfig>(ConfigTypes.CORE);
}
public async Task Load()
{
// execute onLoad callbacks
_logger.Info(_localisationService.GetText("executing_startup_callbacks"));
/*
_logger.Debug($"OS: {os.arch()} | {os.version()} | {process.platform}");
_logger.Debug($"CPU: {os.cpus()[0]?.model} cores: {os.cpus().length}");
_logger.Debug($"RAM: {(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB");
_logger.Debug($"PATH: {this.encodingUtil.toBase64(process.argv[0])}");
_logger.Debug($"PATH: {this.encodingUtil.toBase64(process.execPath)}");
_logger.Debug($"Server: {ProgramStatics.SPT_VERSION || this.coreConfig.sptVersion}");
const nodeVersion = process.version.replace(/^v/, "");
if (ProgramStatics.EXPECTED_NODE && nodeVersion !== ProgramStatics.EXPECTED_NODE) {
this.logger.error(
"Node version mismatch. Required: ${ProgramStatics.EXPECTED_NODE} | Current: ${nodeVersion}",
);
process.exit(1);
}
_logger.Debug("Node: ${nodeVersion}");
if (ProgramStatics.BUILD_TIME) {
_logger.Debug("Date: ${ProgramStatics.BUILD_TIME}");
}
if (ProgramStatics.COMMIT) {
_logger.Debug("Commit: ${ProgramStatics.COMMIT}");
}
*/
foreach (var onLoad in _onLoad)
{
await onLoad.OnLoad();
}
var timer = new Timer(_ =>
{
update(_onUpdate);
}, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(5000));
}
protected async Task update(IEnumerable<OnUpdate> onUpdateComponents)
{
// If the server has failed to start, skip any update calls
if (!_httpServer.IsStarted() || !_databaseService.IsDatabaseValid()) {
return;
}
foreach (var updateable in onUpdateComponents)
{
var success = false;
if (!_onUpdateLastRun.TryGetValue(updateable.GetRoute(), out var lastRunTimeTimestamp))
lastRunTimeTimestamp = 0;
var secondsSinceLastRun = _timeUtil.GetTimeStamp() - lastRunTimeTimestamp;
try {
success = await updateable.OnUpdate(secondsSinceLastRun);
} catch (Exception err) {
LogUpdateException(err, updateable);
}
if (success) {
_onUpdateLastRun[updateable.GetRoute()] = _timeUtil.GetTimeStamp();
} else {
/* temporary for debug */
var warnTime = 20 * 60;
if (secondsSinceLastRun % warnTime == 0) {
_logger.Debug(_localisationService.GetText("route_onupdate_no_response", updateable.GetRoute()));
}
}
}
}
protected void LogUpdateException(Exception err, OnUpdate updateable) {
_logger.Error(_localisationService.GetText("scheduled_event_failed_to_run", updateable.GetRoute()));
_logger.Error(err.ToString());
}
}
+209
View File
@@ -0,0 +1,209 @@
using Core.Annotations;
using Core.DI;
using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Models.Spt.Server;
using Core.Servers;
using Core.Services;
using ILogger = Core.Models.Utils.ILogger;
namespace Core.Utils;
[Injectable(InjectionType.Singleton, typePriority: 0)]
public class DatabaseImporter : OnLoad
{
private object hashedFile;
private ValidationResult valid = ValidationResult.UNDEFINED;
private string filepath;
protected HttpConfig httpConfig;
protected readonly ILogger _logger;
protected readonly LocalisationService _localisationService;
protected readonly DatabaseServer _databaseServer;
//protected readonly ImageRouter _imageRouter;
protected readonly EncodingUtil _encodingUtil;
protected readonly HashUtil _hashUtil;
protected readonly ImporterUtil _importerUtil;
protected readonly ConfigServer _configServer;
public DatabaseImporter(
ILogger logger,
// TODO: are we gonna use this? @inject("JsonUtil") protected jsonUtil: JsonUtil,
LocalisationService localisationService,
DatabaseServer databaseServer,
//ImageRouter imageRouter,
EncodingUtil encodingUtil,
HashUtil hashUtil,
ImporterUtil importerUtil,
ConfigServer configServer
) {
_logger = logger;
_localisationService = localisationService;
_databaseServer = databaseServer;
_encodingUtil = encodingUtil;
_hashUtil = hashUtil;
_importerUtil = importerUtil;
_configServer = configServer;
httpConfig = _configServer.GetConfig<HttpConfig>(ConfigTypes.HTTP);
}
/**
* Get path to spt data
* @returns path to data
*/
public string GetSptDataPath() {
return "./Assets/";
}
public async Task OnLoad()
{
filepath = GetSptDataPath();
/*
if (ProgramStatics.COMPILED) {
try {
// Reading the dynamic SHA1 file
const file = "checks.dat";
const fileWithPath = `${this.filepath}${file}`;
if (this.vfs.exists(fileWithPath)) {
this.hashedFile = this.jsonUtil.deserialize(
this.encodingUtil.fromBase64(this.vfs.readFile(fileWithPath)),
file,
);
} else {
this.valid = ValidationResult.NOT_FOUND;
this.logger.debug(this.localisationService.getText("validation_not_found"));
}
} catch (e) {
this.valid = ValidationResult.FAILED;
this.logger.warning(this.localisationService.getText("validation_error_decode"));
}
}
*/
await HydrateDatabase(filepath);
var imageFilePath = $"${filepath}images/";
/*
var directories = this.vfs.getDirs(imageFilePath);
this.loadImages(imageFilePath, directories, [
"/files/achievement/",
"/files/CONTENT/banners/",
"/files/handbook/",
"/files/Hideout/",
"/files/launcher/",
"/files/prestige/",
"/files/quest/icon/",
"/files/trader/avatar/",
]);
*/
}
/**
* Read all json files in database folder and map into a json object
* @param filepath path to database folder
*/
protected async Task HydrateDatabase(string filepath)
{
_logger.Info(_localisationService.GetText("importing_database"));
var dataToImport = await _importerUtil.LoadRecursiveAsync(
$"{filepath}database/",
typeof(DatabaseTables),
OnReadValidate
);
var validation = valid == ValidationResult.FAILED || this.valid == ValidationResult.NOT_FOUND ? "." : "";
_logger.Info($"{_localisationService.GetText("importing_database_finish")}{validation}");
_databaseServer.SetTables((DatabaseTables) dataToImport);
}
protected void OnReadValidate(string fileWithPath, string data)
{
// Validate files
//if (ProgramStatics.COMPILED && hashedFile && !ValidateFile(fileWithPath, data)) {
// this.valid = ValidationResult.FAILED;
//}
}
public string GetRoute()
{
return "spt-database";
}
protected bool ValidateFile(string filePathAndName, object fileData)
{
/*
try {
const finalPath = filePathAndName.replace(this.filepath, "").replace(".json", "");
let tempObject: any;
for (const prop of finalPath.split("/")) {
if (!tempObject) {
tempObject = this.hashedFile[prop];
} else {
tempObject = tempObject[prop];
}
}
if (tempObject !== this.hashUtil.generateSha1ForData(fileData)) {
this.logger.debug(this.localisationService.getText("validation_error_file", filePathAndName));
return false;
}
} catch (e) {
this.logger.warning(this.localisationService.getText("validation_error_exception", filePathAndName));
this.logger.warning(e);
return false;
}
return true;
*/
return true;
}
/**
* Find and map files with image router inside a designated path
* @param filepath Path to find files in
*/
public void LoadImages(string filepath, List<string> directories, List<string> routes)
{
/*
for (const directoryIndex in directories) {
// Get all files in directory
const filesInDirectory = this.vfs.getFiles(`${filepath}${directories[directoryIndex]}`);
for (const file of filesInDirectory) {
// Register each file in image router
const filename = this.vfs.stripExtension(file);
const routeKey = `${routes[directoryIndex]}${filename}`;
let imagePath = `${filepath}${directories[directoryIndex]}/${file}`;
const pathOverride = this.getImagePathOverride(imagePath);
if (pathOverride) {
this.logger.debug(`overrode route: ${routeKey} endpoint: ${imagePath} with ${pathOverride}`);
imagePath = pathOverride;
}
this.imageRouter.addRoute(routeKey, imagePath);
}
}
// Map icon file separately
this.imageRouter.addRoute("/favicon.ico", `${filepath}icon.ico`);
*/
}
/**
* Check for a path override in the http json config file
* @param imagePath Key
* @returns override for key
*/
protected string GetImagePathOverride(string imagePath)
{
return httpConfig.ServerImagePathOverride[imagePath];
}
}
enum ValidationResult {
SUCCESS = 0,
FAILED = 1,
NOT_FOUND = 2,
UNDEFINED = 3,
}
+29
View File
@@ -0,0 +1,29 @@
using Core.Annotations;
namespace Core.Utils;
[Injectable]
public class FileUtil
{
public List<string> GetFiles(string path, bool recursive = false)
{
var files = new List<string>(Directory.GetFiles(path));
if (recursive)
files.AddRange(Directory.GetDirectories(path).SelectMany(d => GetFiles(d, recursive)));
return files;
}
public string[] GetDirectories(string path)
{
return Directory.GetDirectories(path);
}
public string GetFileExtension(string path)
{
return Path.GetExtension(path).Replace(".", "");
}
}
+181
View File
@@ -0,0 +1,181 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Core.Annotations;
namespace Core.Utils;
[Injectable(InjectionType.Singleton)]
public class ImporterUtil
{
private readonly FileUtil _fileUtil;
public ImporterUtil(FileUtil fileUtil)
{
_fileUtil = fileUtil;
}
/**
* Load files into js objects recursively (asynchronous)
* @param filepath Path to folder with files
* @returns Promise<T> return T type associated with this class
*/
public async Task<object> LoadRecursiveAsync(
string filepath,
Type loadedType,
Action<string, string>? onReadCallback = null,
Action<string, object>? onObjectDeserialized = null
) {
var result = Activator.CreateInstance(loadedType);
// get all filepaths
var files = _fileUtil.GetFiles(filepath);
var directories = _fileUtil.GetDirectories(filepath);
// add file content to result
foreach (var file in files)
{
if (_fileUtil.GetFileExtension(file) != "json") continue;
// const filename = this.vfs.stripExtension(file);
// const filePathAndName = `${filepath}${file}`;
var fileData = await File.ReadAllTextAsync(file);
if (onReadCallback != null)
onReadCallback(file, fileData);
var matchedProperty = loadedType.GetProperties().FirstOrDefault(prop => prop.Name.ToLower() == Path.GetFileNameWithoutExtension(file).ToLower());
if (matchedProperty == null)
throw new Exception($"Unable to find property '{Path.GetFileNameWithoutExtension(file)}' for type '{loadedType.Name}'");
var propertyType = matchedProperty.PropertyType;
var fileDeserialized = JsonSerializer.Deserialize(fileData, propertyType, new JsonSerializerOptions { UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow });
if (onObjectDeserialized != null)
onObjectDeserialized(file, fileDeserialized);
matchedProperty.SetValue(result, fileDeserialized);
}
// deep tree search
foreach (var directory in directories)
{
var matchedProperty = loadedType.GetProperties().FirstOrDefault(prop => prop.Name.ToLower() == directory.ToLower());
if (matchedProperty == null)
throw new Exception($"Unable to find property '{directory}' for type '{loadedType.Name}'");
matchedProperty.GetSetMethod().Invoke(result, [await LoadRecursiveAsync($"{filepath}{directory}/", matchedProperty.PropertyType)]);
}
// return the result of all async fetch
return result;
}
/**
* Load files into js objects recursively (synchronous)
* @param filepath Path to folder with files
* @returns
*/
public object LoadRecursive(
string filepath,
Type loadedType,
Action<string, string>? onReadCallback = null,
Action<string, object>? onObjectDeserialized = null
)
{
var result = Activator.CreateInstance(loadedType);
// get all filepaths
var files = Directory.GetFiles(filepath);
var directories = Directory.GetDirectories(filepath);
foreach (var file in files)
{
if (Path.GetExtension(file) == "json")
{
// const filename = this.vfs.stripExtension(file);
// const filePathAndName = `${filepath}${file}`;
var fileData = File.ReadAllText(file);
onReadCallback(file, fileData);
var matchedProperty = loadedType.GetProperties().FirstOrDefault(prop => prop.Name.ToLower() == Path.GetFileNameWithoutExtension(file).ToLower());
if (matchedProperty == null)
throw new Exception($"Unable to find property '{Path.GetFileNameWithoutExtension(file)}' for type '{loadedType.Name}'");
var propertyType = matchedProperty.PropertyType;
var fileDeserialized = JsonSerializer.Deserialize(fileData, propertyType);
onObjectDeserialized(file, fileDeserialized);
matchedProperty.GetSetMethod().Invoke(result, [fileDeserialized]);
}
}
// deep tree search
foreach (var directory in directories)
{
var matchedProperty = loadedType.GetProperties().FirstOrDefault(prop => prop.Name.ToLower() == directory.ToLower());
if (matchedProperty == null)
throw new Exception($"Unable to find property '{directory}' for type '{loadedType.Name}'");
matchedProperty.GetSetMethod().Invoke(result, [LoadRecursive($"{filepath}{directory}/", matchedProperty.PropertyType)]);
}
// return the result of all async fetch
return result;
}
public async Task<object> LoadAsync(
string filepath,
Type loadedType,
string strippablePath = "",
Action<string, string>? onReadCallback = null,
Action<string, object>? onObjectDeserialized = null
)
{
var result = Activator.CreateInstance(loadedType);
var promises = new List<Task<object>>();
var filesToProcess = new Queue<string>(_fileUtil.GetFiles(filepath, true));
while (filesToProcess.Count != 0) {
var fileNode = filesToProcess.Dequeue();
if (fileNode == null || _fileUtil.GetFileExtension(fileNode) != "json") {
continue;
}
promises.Add(File.ReadAllTextAsync(fileNode).ContinueWith(fd =>
{
onReadCallback(fileNode, fd.Result);
return JsonSerializer.Deserialize(fd.Result, typeof(object));
}));
/*
this.vfs
.readFileAsync(filePathAndName)
.then(async (fileData) => {
onReadCallback(filePathAndName, fileData);
return this.jsonUtil.deserializeWithCacheCheckAsync<any>(fileData, filePathAndName);
})
.then(async (fileDeserialized) => {
onObjectDeserialized(filePathAndName, fileDeserialized);
const strippedFilePath = this.vfs.stripExtension(filePathAndName).replace(filepath, "");
this.placeObject(fileDeserialized, strippedFilePath, result, strippablePath);
})
.then(() => progressWriter.increment()),
);*/
}
//await JSType.Promise<>.all(promises).catch((e) => console.error(e));
return result;
}
/*
protected placeObject<T>(fileDeserialized: any, strippedFilePath: string, result: T, strippablePath: string): void {
const strippedFinalPath = strippedFilePath.replace(strippablePath, "");
let temp = result;
const propertiesToVisit = strippedFinalPath.split("/");
for (let i = 0; i < propertiesToVisit.length; i++) {
const property = propertiesToVisit[i];
if (i === propertiesToVisit.length - 1) {
temp[property] = fileDeserialized;
} else {
if (!temp[property]) {
temp[property] = {};
}
temp = temp[property];
}
}
}*/
}
@@ -0,0 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Core.Utils.Json.Converters;
public class NotNullObjectToIntConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
int result;
switch (reader.TokenType)
{
case JsonTokenType.String:
var value = reader.GetString();
if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, out result))
return 0;
break;
case JsonTokenType.Number:
result = reader.GetInt32();
break;
case JsonTokenType.Null:
return 0;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteStringValue("");
else
writer.WriteStringValue($"{value}");
}
}
@@ -0,0 +1,38 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Core.Utils.Json.Converters;
public class NullableObjectToLongConverter : JsonConverter<long?>
{
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
long result;
switch (reader.TokenType)
{
case JsonTokenType.String:
var value = reader.GetString();
if (string.IsNullOrWhiteSpace(value) || !long.TryParse(value, out result))
return null;
break;
case JsonTokenType.Number:
result = reader.GetInt64();
break;
case JsonTokenType.Null:
return null;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteStringValue("");
else if (value is long longValue)
writer.WriteNumberValue(longValue);
else
throw new Exception("Cannot convert the object valur to a long.");
}
}
+5 -5
View File
@@ -1156,7 +1156,7 @@
"5": 3,
"10": 1
},
"whitelist": []
"whitelist": {}
},
"pocketLoot": {
"weights": {
@@ -1165,7 +1165,7 @@
"2": 2,
"3": 1
},
"whitelist": []
"whitelist": {}
},
"vestLoot": {
"weights": {
@@ -1175,7 +1175,7 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
@@ -1184,14 +1184,14 @@
"2": 4,
"3": 3
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 10,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"equipment": {
+84 -84
View File
@@ -43,38 +43,38 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
}
},
"labsAccessCardChancePercent": 0,
@@ -123,20 +123,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 1,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -146,21 +146,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 2,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -208,20 +208,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -231,21 +231,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 2,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -293,20 +293,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -316,21 +316,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 2,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -378,20 +378,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -401,21 +401,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 2,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -463,20 +463,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -486,21 +486,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 2,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -548,20 +548,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -571,21 +571,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 2,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -633,20 +633,20 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -656,21 +656,21 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"1": 1,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {}
@@ -718,7 +718,7 @@
"0": 2,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
@@ -726,13 +726,13 @@
"1": 1,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -741,7 +741,7 @@
"3": 1,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
@@ -749,7 +749,7 @@
"2": 2,
"3": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
@@ -757,7 +757,7 @@
"1": 1,
"2": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {
@@ -808,42 +808,42 @@
"1": 1,
"2": 2
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"1": 1,
"2": 2
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"0": 1,
"1": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
"2": 2,
"3": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"2": 2,
"3": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"1": 1,
"2": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {
@@ -895,21 +895,21 @@
"1": 1,
"2": 2
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"1": 1,
"2": 2
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"1": 5,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -918,7 +918,7 @@
"4": 1,
"5": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
@@ -926,14 +926,14 @@
"3": 2,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"1": 1,
"2": 2
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {
@@ -984,21 +984,21 @@
"weights": {
"2": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"2": 1,
"3": 2
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"1": 1,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -1007,7 +1007,7 @@
"5": 1,
"6": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
@@ -1015,14 +1015,14 @@
"3": 2,
"4": 1
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"2": 1,
"3": 2
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {
@@ -1074,21 +1074,21 @@
"2": 5,
"3": 1
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"2": 1,
"3": 2
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"1": 1,
"2": 1
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -1097,21 +1097,21 @@
"5": 2,
"6": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"3": 2,
"4": 2
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"2": 2,
"3": 1
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {
@@ -1180,21 +1180,21 @@
"2": 1,
"3": 2
},
"whitelist": []
"whitelist": {}
},
"drugs": {
"weights": {
"3": 1,
"4": 2
},
"whitelist": []
"whitelist": {}
},
"stims": {
"weights": {
"2": 1,
"3": 2
},
"whitelist": []
"whitelist": {}
},
"looseLoot": {
"weights": {
@@ -1202,21 +1202,21 @@
"6": 2,
"7": 1
},
"whitelist": []
"whitelist": {}
},
"magazines": {
"weights": {
"3": 1,
"4": 4
},
"whitelist": []
"whitelist": {}
},
"grenades": {
"weights": {
"3": 1,
"4": 2
},
"whitelist": []
"whitelist": {}
}
},
"lootItemsToAddChancePercent": {
+44 -24
View File
@@ -1,5 +1,7 @@
using Core.Annotations;
using Core.Context;
using Core.Servers;
using Core.Utils;
namespace Server;
@@ -8,22 +10,32 @@ public static class Program
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
RegisterSptComponents(builder.Services);
// TODO: deal with modding overriding services here!
var httpServer = builder.Services.BuildServiceProvider().GetService<HttpServer>();
httpServer.Load(builder);
try
{
var serviceProvider = builder.Services.BuildServiceProvider();
var app = serviceProvider.GetService<App>();
var appContext = serviceProvider.GetService<ApplicationContext>();
appContext.AddValue(ContextVariableType.APP_BUILDER, builder);
app.Load().Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static void RegisterComponents(IServiceCollection builderServices, IEnumerable<Type> types)
{
// We get all injectable services to register them on our services
foreach (var injectableType in types)
var groupedTypes = types.Select(t =>
{
var attribute = (Injectable) Attribute.GetCustomAttribute(injectableType, typeof(Injectable))!;
var registerableType = injectableType;
var attribute = (Injectable)Attribute.GetCustomAttribute(t, typeof(Injectable))!;
var registerableType = t;
// if we have a type override this takes priority
if (attribute.InjectableTypeOverride != null)
{
@@ -34,28 +46,36 @@ public static class Program
{
registerableType = registerableType.GetInterfaces()[0];
}
switch (attribute.InjectionType)
return (registerableInterface: registerableType, typeToRegister: t, injectableAttribute: attribute);
}).GroupBy(t => t.registerableInterface.FullName);
// We get all injectable services to register them on our services
foreach (var groupedInjectables in groupedTypes)
{
foreach (var valueTuple in groupedInjectables.OrderBy(t => t.injectableAttribute.TypePriority))
{
case InjectionType.Singleton:
builderServices.AddSingleton(registerableType, injectableType);
break;
case InjectionType.Transient:
builderServices.AddTransient(registerableType, injectableType);
break;
case InjectionType.Scoped:
builderServices.AddScoped(registerableType, injectableType);
break;
default:
throw new ArgumentOutOfRangeException();
switch (valueTuple.injectableAttribute.InjectionType)
{
case InjectionType.Singleton:
builderServices.AddSingleton(valueTuple.registerableInterface, valueTuple.typeToRegister);
break;
case InjectionType.Transient:
builderServices.AddTransient(valueTuple.registerableInterface, valueTuple.typeToRegister);
break;
case InjectionType.Scoped:
builderServices.AddScoped(valueTuple.registerableInterface, valueTuple.typeToRegister);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}
private static void RegisterSptComponents(IServiceCollection builderServices)
{
// We get all the services from this assembly first, since mods will override them later
RegisterComponents(builderServices, typeof(Program).Assembly.GetTypes()
RegisterComponents(builderServices, typeof(App).Assembly.GetTypes()
.Where(type => Attribute.IsDefined(type, typeof(Injectable))));
}
}
+1
View File
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<EnableDefaultContentItems>false</EnableDefaultContentItems>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>