more work
This commit is contained in:
@@ -1,12 +1,26 @@
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Launcher;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
[Injectable]
|
||||
public class LauncherCallbacks
|
||||
{
|
||||
public LauncherCallbacks()
|
||||
protected HttpResponseUtil _httpResponseUtil;
|
||||
protected LauncherController _launcherController;
|
||||
protected SaveServer _saveServer;
|
||||
protected Watermark _watermark;
|
||||
public LauncherCallbacks(
|
||||
HttpResponseUtil httpResponse,
|
||||
LauncherController launcherController,
|
||||
SaveServer saveServer,
|
||||
Watermark watermark)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string Connect()
|
||||
@@ -73,4 +87,4 @@ public class LauncherCallbacks
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Launcher;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Spt.Mod;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
[Injectable]
|
||||
public class LauncherController
|
||||
{
|
||||
/// <summary>
|
||||
@@ -144,4 +146,4 @@ public class LauncherController
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-11
@@ -1,7 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Utils;
|
||||
using Core.Utils;
|
||||
|
||||
namespace Core.DI;
|
||||
|
||||
@@ -39,22 +41,27 @@ public abstract class Router
|
||||
.Where((r) => !r.dynamic)
|
||||
.Any((r) => r.route == url);
|
||||
}
|
||||
|
||||
public abstract Type? GetBodyDeserializationType();
|
||||
}
|
||||
|
||||
public abstract class StaticRouter : Router
|
||||
{
|
||||
private List<RouteAction<IRequestData>> actions;
|
||||
private List<RouteAction> actions;
|
||||
private JsonUtil _jsonUtil;
|
||||
|
||||
public StaticRouter(List<RouteAction<IRequestData>> routes) : base()
|
||||
public StaticRouter(JsonUtil jsonUtil, List<RouteAction> routes) : base()
|
||||
{
|
||||
actions = routes;
|
||||
_jsonUtil = jsonUtil;
|
||||
}
|
||||
|
||||
public object HandleStatic(string url, IRequestData? info, string sessionID, string output)
|
||||
public object HandleStatic(string url, string? body, string sessionID, string output)
|
||||
{
|
||||
return actions.Single(route => route.url == url).action(url, info, sessionID, output);
|
||||
var action = actions.Single(route => route.url == url);
|
||||
var type = action.bodyType;
|
||||
IRequestData? info = null;
|
||||
if (type != null && !string.IsNullOrEmpty(body))
|
||||
info = (IRequestData?) _jsonUtil.Deserialize(body, type);
|
||||
return action.action(url, info, sessionID, output);
|
||||
}
|
||||
|
||||
protected override List<HandledRoute> GetHandledRoutes()
|
||||
@@ -65,16 +72,23 @@ public abstract class StaticRouter : Router
|
||||
|
||||
public abstract class DynamicRouter : Router
|
||||
{
|
||||
private List<RouteAction<IRequestData>> actions;
|
||||
private List<RouteAction> actions;
|
||||
private JsonUtil _jsonUtil;
|
||||
|
||||
public DynamicRouter(List<RouteAction<IRequestData>> routes) : base()
|
||||
public DynamicRouter(JsonUtil jsonUtil, List<RouteAction> routes) : base()
|
||||
{
|
||||
actions = routes;
|
||||
_jsonUtil = jsonUtil;
|
||||
}
|
||||
|
||||
public object HandleDynamic(string url, IRequestData? info, string sessionID, string output)
|
||||
public object HandleDynamic(string url, string? body, string sessionID, string output)
|
||||
{
|
||||
return actions.First(r => url.Contains(r.url)).action(url, info, sessionID, output);
|
||||
var action = actions.First(r => url.Contains(r.url));
|
||||
var type = action.bodyType;
|
||||
IRequestData? info = null;
|
||||
if (type != null && !string.IsNullOrEmpty(body))
|
||||
info = (IRequestData?) _jsonUtil.Deserialize(body, type);
|
||||
return action.action(url, info, sessionID, output);
|
||||
}
|
||||
|
||||
protected override List<HandledRoute> GetHandledRoutes()
|
||||
@@ -103,5 +117,9 @@ public abstract class SaveLoadRouter : Router
|
||||
|
||||
public record HandledRoute(string route, bool dynamic);
|
||||
|
||||
public record RouteAction<T>(string url, Func<string, T?, string?, string?, object> action) where T : IRequestData;
|
||||
public record RouteAction(
|
||||
string url,
|
||||
Func<string, IRequestData?, string?, string?, object> action,
|
||||
Type? bodyType = null
|
||||
);
|
||||
//public action: (url: string, info: any, sessionID: string, output: string) => Promise<any>,
|
||||
|
||||
@@ -4,9 +4,11 @@ namespace Core.Models.Common;
|
||||
|
||||
public class MinMax
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; set; }
|
||||
[JsonPropertyName("max")]
|
||||
public double? Max { get; set; }
|
||||
|
||||
[JsonPropertyName("min")]
|
||||
public double? Min { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ public class Item
|
||||
|
||||
[JsonPropertyName("location")]
|
||||
public object? Location { get; set; } // TODO: Can be IItemLocation or number
|
||||
|
||||
[JsonPropertyName("desc")]
|
||||
public string? Desc { get; set; }
|
||||
|
||||
[JsonPropertyName("upd")]
|
||||
public Upd? Update { get; set; }
|
||||
@@ -254,4 +257,4 @@ public class UpdCultistAmulet
|
||||
{
|
||||
[JsonPropertyName("NumberOfUsages")]
|
||||
public double? NumberOfUsages { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Utils;
|
||||
|
||||
namespace Core.Models.Eft.Launcher;
|
||||
|
||||
public class LoginRequestData
|
||||
public class LoginRequestData : IRequestData
|
||||
{
|
||||
[JsonPropertyName("username")]
|
||||
public string? Username { get; set; }
|
||||
|
||||
[JsonPropertyName("password")]
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ public class AirdropChancePercent
|
||||
/// </summary>
|
||||
public class AirdropLoot
|
||||
{
|
||||
[JsonPropertyName("Icon")]
|
||||
[JsonPropertyName("icon")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public AirdropTypeEnum Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -126,4 +127,4 @@ public class AirdropLoot
|
||||
|
||||
[JsonPropertyName("forcedLoot")]
|
||||
public Dictionary<string, MinMax>? ForcedLoot { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,15 @@ public class PresetBatch
|
||||
[JsonPropertyName("bossKnight")]
|
||||
public int BossKnight { get; set; }
|
||||
|
||||
[JsonPropertyName("bossZryachiy")]
|
||||
public int BossZryachiy { get; set; }
|
||||
|
||||
[JsonPropertyName("bossKolontay")]
|
||||
public int BossKolontay { get; set; }
|
||||
|
||||
[JsonPropertyName("bossPartisan")]
|
||||
public int BossPartisan { get; set; }
|
||||
|
||||
[JsonPropertyName("bossTest")]
|
||||
public int BossTest { get; set; }
|
||||
|
||||
@@ -180,6 +189,21 @@ public class PresetBatch
|
||||
[JsonPropertyName("followerBoar")]
|
||||
public int FollowerBoar { get; set; }
|
||||
|
||||
[JsonPropertyName("followerBoarClose1")]
|
||||
public int FollowerBoarClose1 { get; set; }
|
||||
|
||||
[JsonPropertyName("followerBoarClose2")]
|
||||
public int FollowerBoarClose2 { get; set; }
|
||||
|
||||
[JsonPropertyName("followerZryachiy")]
|
||||
public int FollowerZryachiy { get; set; }
|
||||
|
||||
[JsonPropertyName("followerKolontayAssault")]
|
||||
public int FollowerKolontayAssault { get; set; }
|
||||
|
||||
[JsonPropertyName("followerKolontaySecurity")]
|
||||
public int FollowerKolontaySecurity { get; set; }
|
||||
|
||||
[JsonPropertyName("marksman")]
|
||||
public int Marksman { get; set; }
|
||||
|
||||
@@ -221,6 +245,12 @@ public class PresetBatch
|
||||
|
||||
[JsonPropertyName("pmcBEAR")]
|
||||
public int PmcBEAR { get; set; }
|
||||
|
||||
[JsonPropertyName("shooterBTR")]
|
||||
public int ShooterBTR { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, object> AdditionalData { get; set; }
|
||||
}
|
||||
|
||||
public class WalletLootSettings
|
||||
@@ -288,7 +318,7 @@ public class EquipmentFilters
|
||||
/// <summary>
|
||||
/// Chance NODS are down/active during the day
|
||||
/// </summary>
|
||||
[JsonPropertyName("NvgIsActiveChanceDayPercent")]
|
||||
[JsonPropertyName("nvgIsActiveChanceDayPercent")]
|
||||
public float? NvgIsActiveChanceDayPercent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -522,4 +552,4 @@ public class RandomisedResourceValues
|
||||
/// </summary>
|
||||
[JsonPropertyName("chanceMaxResourcePercent")]
|
||||
public float ChanceMaxResourcePercent { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,43 +11,43 @@ public class BotDurability
|
||||
public PmcDurability Pmc { get; set; }
|
||||
|
||||
[JsonPropertyName("boss")]
|
||||
public BotDurability Boss { get; set; }
|
||||
public PmcDurability Boss { get; set; }
|
||||
|
||||
[JsonPropertyName("follower")]
|
||||
public BotDurability Follower { get; set; }
|
||||
public PmcDurability Follower { get; set; }
|
||||
|
||||
[JsonPropertyName("assault")]
|
||||
public BotDurability Assault { get; set; }
|
||||
public PmcDurability Assault { get; set; }
|
||||
|
||||
[JsonPropertyName("cursedassault")]
|
||||
public BotDurability CursedAssault { get; set; }
|
||||
public PmcDurability CursedAssault { get; set; }
|
||||
|
||||
[JsonPropertyName("marksman")]
|
||||
public BotDurability Marksman { get; set; }
|
||||
public PmcDurability Marksman { get; set; }
|
||||
|
||||
[JsonPropertyName("pmcbot")]
|
||||
public BotDurability PmcBot { get; set; }
|
||||
public PmcDurability PmcBot { get; set; }
|
||||
|
||||
[JsonPropertyName("arenafighterevent")]
|
||||
public BotDurability ArenaFighterEvent { get; set; }
|
||||
public PmcDurability ArenaFighterEvent { get; set; }
|
||||
|
||||
[JsonPropertyName("arenafighter")]
|
||||
public BotDurability ArenaFighter { get; set; }
|
||||
public PmcDurability ArenaFighter { get; set; }
|
||||
|
||||
[JsonPropertyName("crazyassaultevent")]
|
||||
public BotDurability CrazyAssaultEvent { get; set; }
|
||||
public PmcDurability CrazyAssaultEvent { get; set; }
|
||||
|
||||
[JsonPropertyName("exusec")]
|
||||
public BotDurability Exusec { get; set; }
|
||||
public PmcDurability Exusec { get; set; }
|
||||
|
||||
[JsonPropertyName("gifter")]
|
||||
public BotDurability Gifter { get; set; }
|
||||
public PmcDurability Gifter { get; set; }
|
||||
|
||||
[JsonPropertyName("sectantpriest")]
|
||||
public BotDurability SectantPriest { get; set; }
|
||||
public PmcDurability SectantPriest { get; set; }
|
||||
|
||||
[JsonPropertyName("sectantwarrior")]
|
||||
public BotDurability SectantWarrior { get; set; }
|
||||
public PmcDurability SectantWarrior { get; set; }
|
||||
}
|
||||
|
||||
/** Durability values to be used when a more specific bot type can't be found */
|
||||
@@ -82,6 +82,9 @@ public class PmcDurabilityArmor
|
||||
|
||||
[JsonPropertyName("minDelta")]
|
||||
public double MinDelta { get; set; }
|
||||
|
||||
[JsonPropertyName("minLimitPercent")]
|
||||
public double MinLimitPercent { get; set; }
|
||||
}
|
||||
|
||||
public class ArmorDurability
|
||||
@@ -112,4 +115,4 @@ public class WeaponDurability
|
||||
|
||||
[JsonPropertyName("minLimitPercent")]
|
||||
public double MinLimitPercent { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,4 +73,8 @@ public class Gift
|
||||
|
||||
[JsonPropertyName("maxToSendPlayer")]
|
||||
public int? MaxToSendPlayer { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[JsonPropertyName("maxToSendToPlayer")]
|
||||
public int? MaxToSendToPlayer { get; set; }
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ public class InventoryConfig : BaseConfig
|
||||
|
||||
public class RewardDetails
|
||||
{
|
||||
[JsonPropertyName("_type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("rewardCount")]
|
||||
public int RewardCount { get; set; }
|
||||
|
||||
@@ -69,4 +72,4 @@ public class SealedAirdropContainerSettings
|
||||
|
||||
[JsonPropertyName("allowBossItems")]
|
||||
public bool AllowBossItems { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +254,9 @@ public class LootMultiplier
|
||||
|
||||
[JsonPropertyName("sandbox")]
|
||||
public double Sandbox { get; set; }
|
||||
|
||||
[JsonPropertyName("sandbox_high")]
|
||||
public double SandboxHigh { get; set; }
|
||||
}
|
||||
|
||||
public class ContainerRandomisationSettings
|
||||
@@ -316,4 +319,4 @@ public class ScavRaidTimeLocationSettings
|
||||
/** Should bot waves be removed / spawn times be adjusted */
|
||||
[JsonPropertyName("adjustWaves")]
|
||||
public bool AdjustWaves { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,10 @@ public class LostEquipment
|
||||
|
||||
[JsonPropertyName("Scabbard")]
|
||||
public bool Scabbard { get; set; }
|
||||
}
|
||||
|
||||
[JsonPropertyName("Compass")]
|
||||
public bool Compass { get; set; }
|
||||
|
||||
[JsonPropertyName("SecuredContainer")]
|
||||
public bool SecuredContainer { get; set; }
|
||||
}
|
||||
|
||||
@@ -9,4 +9,7 @@ public class MatchConfig : BaseConfig
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
[JsonPropertyName("randomiseMapContainers")]
|
||||
public Dictionary<string, bool> RandomiseMapContainers { get; set; }
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ public class KarmaLevel
|
||||
[JsonPropertyName("equipmentBlacklist")]
|
||||
public Dictionary<string, string[]> EquipmentBlacklist { get; set; }
|
||||
|
||||
[JsonPropertyName("labsAccessCardChancePercent")]
|
||||
public double? LabsAccessCardChancePercent { get; set; }
|
||||
|
||||
[JsonPropertyName("lootItemsToAddChancePercent")]
|
||||
public Dictionary<string, double> LootItemsToAddChancePercent { get; set; }
|
||||
}
|
||||
@@ -58,4 +61,4 @@ public class ItemLimits
|
||||
|
||||
[JsonPropertyName("grenades")]
|
||||
public GenerationData Grenades { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ public class PmcConfig : BaseConfig
|
||||
[JsonPropertyName("looseWeaponInBackpackLootMinMax")]
|
||||
public MinMax LooseWeaponInBackpackLootMinMax { get; set; }
|
||||
|
||||
[JsonPropertyName("_isUsec")]
|
||||
public string? IsUsecDescription { get; set; }
|
||||
|
||||
/** Percentage chance PMC will be USEC */
|
||||
[JsonPropertyName("isUsec")]
|
||||
public double IsUsec { get; set; }
|
||||
@@ -65,6 +68,9 @@ public class PmcConfig : BaseConfig
|
||||
[JsonPropertyName("bearType")]
|
||||
public string BearType { get; set; }
|
||||
|
||||
[JsonPropertyName("_pmcType")]
|
||||
public string? PmcTypeDescription { get; set; }
|
||||
|
||||
/** What 'brain' does a PMC use, keyed by map and side (USEC/BEAR) key: map location, value: type for usec/bear */
|
||||
[JsonPropertyName("pmcType")]
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType { get; set; }
|
||||
@@ -106,6 +112,9 @@ public class PmcConfig : BaseConfig
|
||||
/** Should secure container loot from usec.json/bear.json be added to pmc bots secure */
|
||||
[JsonPropertyName("addSecureContainerLootFromBotConfig")]
|
||||
public bool AddSecureContainerLootFromBotConfig { get; set; }
|
||||
|
||||
[JsonPropertyName("addPrefixToSameNamePMCAsPlayerChance")]
|
||||
public int? AddPrefixToSameNamePMCAsPlayerChance { get; set; }
|
||||
}
|
||||
|
||||
public class HostilitySettings
|
||||
@@ -159,4 +168,4 @@ public class IMinMaxLootValue : MinMax
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public double Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,14 +56,17 @@ public class PlayerTypeQuestIds
|
||||
|
||||
public class QuestTypeIds
|
||||
{
|
||||
[JsonPropertyName("Elimination")]
|
||||
[JsonPropertyName("elimination")]
|
||||
public string Elimination { get; set; }
|
||||
|
||||
[JsonPropertyName("Completion")]
|
||||
[JsonPropertyName("completion")]
|
||||
public string Completion { get; set; }
|
||||
|
||||
[JsonPropertyName("Exploration")]
|
||||
[JsonPropertyName("exploration")]
|
||||
public string Exploration { get; set; }
|
||||
|
||||
[JsonPropertyName("pickup")]
|
||||
public string Pickup { get; set; }
|
||||
}
|
||||
|
||||
public class EventQuestData
|
||||
@@ -173,6 +176,8 @@ public class RewardScaling
|
||||
|
||||
public class TraderWhitelist
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("traderId")]
|
||||
public string TraderId { get; set; }
|
||||
|
||||
@@ -253,6 +258,11 @@ public class Pickup : BaseQuestConfig
|
||||
{
|
||||
[JsonPropertyName("ItemTypeToFetchWithMaxCount")]
|
||||
public List<PickupTypeWithMaxCount> ItemTypeToFetchWithMaxCount { get; set; }
|
||||
|
||||
public List<string> ItemTypesToFetch { get; set; }
|
||||
|
||||
[JsonPropertyName("maxItemFetchCount")]
|
||||
public int? MaxItemFetchCount { get; set; }
|
||||
}
|
||||
|
||||
public class PickupTypeWithMaxCount
|
||||
@@ -370,4 +380,4 @@ public class ProbabilityObject
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public object Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class Dynamic
|
||||
|
||||
[JsonPropertyName("condition")]
|
||||
/** Settings to control the durability range of item items listed on flea */
|
||||
public Condition Condition { get; set; }
|
||||
public Dictionary<string, Condition> Condition { get; set; }
|
||||
|
||||
[JsonPropertyName("stackablePercent")]
|
||||
/** Size stackable items should be listed for in percent of max stack size */
|
||||
@@ -138,6 +138,9 @@ public class Dynamic
|
||||
/** A multipler to apply to individual tpls price just prior to item quality adjustment */
|
||||
public Dictionary<string, double> ItemPriceMultiplier { get; set; }
|
||||
|
||||
[JsonPropertyName("_currencies")]
|
||||
public string? CurrenciesDescription { get; set; }
|
||||
|
||||
[JsonPropertyName("currencies")]
|
||||
/** Percentages to sell offers in each currency */
|
||||
public Dictionary<string, double> Currencies { get; set; }
|
||||
@@ -265,6 +268,9 @@ public class Condition
|
||||
|
||||
[JsonPropertyName("max")]
|
||||
public MinMax Max { get; set; }
|
||||
|
||||
[JsonPropertyName("_name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class RagfairBlacklist
|
||||
@@ -352,6 +358,9 @@ public class UnreasonableModPrices
|
||||
/// </summary>
|
||||
[JsonPropertyName("newPriceHandbookMultiplier")]
|
||||
public int NewPriceHandbookMultiplier { get; set; }
|
||||
|
||||
[JsonPropertyName("itemType")]
|
||||
public string ItemType { get; set; }
|
||||
}
|
||||
|
||||
public class ArmorSettings
|
||||
@@ -386,8 +395,9 @@ public class TieredFlea
|
||||
[JsonPropertyName("unlocksType")]
|
||||
public Dictionary<string, int> UnlocksType { get; set; }
|
||||
|
||||
public bool AmmoTiersEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("ammoTplUnlocks")]
|
||||
public Dictionary<string, int> AmmoTplUnlocks { get; set; }
|
||||
}
|
||||
|
||||
[JsonPropertyName("ammoTiersEnabled")]
|
||||
public bool AmmoTiersEnabled { get; set; }
|
||||
}
|
||||
|
||||
@@ -82,6 +82,12 @@ public class RepairKit
|
||||
|
||||
[JsonPropertyName("weapon")]
|
||||
public BonusSettings Weapon { get; set; }
|
||||
|
||||
[JsonPropertyName("vest")]
|
||||
public BonusSettings Vest { get; set; }
|
||||
|
||||
[JsonPropertyName("headwear")]
|
||||
public BonusSettings Headwear { get; set; }
|
||||
}
|
||||
|
||||
public class BonusSettings
|
||||
@@ -92,10 +98,10 @@ public class BonusSettings
|
||||
[JsonPropertyName("bonusTypeWeight")]
|
||||
public Dictionary<string, double> BonusTypeWeight { get; set; }
|
||||
|
||||
[JsonPropertyName("common")]
|
||||
[JsonPropertyName("Common")]
|
||||
public Dictionary<string, BonusValues> Common { get; set; }
|
||||
|
||||
[JsonPropertyName("rare")]
|
||||
[JsonPropertyName("Rare")]
|
||||
public Dictionary<string, BonusValues> Rare { get; set; }
|
||||
}
|
||||
|
||||
@@ -107,4 +113,4 @@ public class BonusValues
|
||||
/** What dura is buff active between (min max of current max) */
|
||||
[JsonPropertyName("activeDurabilityPercentMinMax")]
|
||||
public MinMax ActiveDurabilityPercentMinMax { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Enums;
|
||||
using Core.Utils.Json.Converters;
|
||||
|
||||
namespace Core.Models.Spt.Config;
|
||||
|
||||
@@ -20,6 +21,7 @@ public class SeasonalEventConfig : BaseConfig
|
||||
[JsonPropertyName("eventLoot")]
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, int>>>> EventLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("events")]
|
||||
public List<SeasonalEvent> Events { get; set; }
|
||||
|
||||
[JsonPropertyName("eventBotMapping")]
|
||||
@@ -59,19 +61,26 @@ public class SeasonalEvent
|
||||
public SeasonalEventType Type { get; set; }
|
||||
|
||||
[JsonPropertyName("startDay")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int StartDay { get; set; }
|
||||
|
||||
[JsonPropertyName("startMonth")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int StartMonth { get; set; }
|
||||
|
||||
[JsonPropertyName("endDay")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int EndDay { get; set; }
|
||||
|
||||
[JsonPropertyName("endMonth")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int EndMonth { get; set; }
|
||||
|
||||
[JsonPropertyName("settings")]
|
||||
public Dictionary<string, object> Settings { get; set; } // TODO: Type was Record<string, ISeasonalEventSettings | IZombieSettings>
|
||||
|
||||
[JsonPropertyName("setting")]
|
||||
public Dictionary<string, object> SettingsDoNOTUse { set => Settings = value; }
|
||||
}
|
||||
|
||||
public class SeasonalEventSettings
|
||||
@@ -102,4 +111,4 @@ public class GifterSetting
|
||||
|
||||
[JsonPropertyName("spawnChance")]
|
||||
public int SpawnChance { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ public class TraderConfig : BaseConfig
|
||||
|
||||
public class UpdateTime
|
||||
{
|
||||
[JsonPropertyName("_name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("traderId")]
|
||||
public string TraderId { get; set; }
|
||||
|
||||
@@ -139,6 +142,9 @@ public class CoopExtractReward : LootRequest
|
||||
{
|
||||
[JsonPropertyName("sendGift")]
|
||||
public bool SendGift { get; set; }
|
||||
|
||||
[JsonPropertyName("useRewarditemBlacklist")]
|
||||
public bool UseRewarditemBlacklist { get; set; }
|
||||
|
||||
[JsonPropertyName("messageLocaleIds")]
|
||||
public List<string> MessageLocaleIds { get; set; }
|
||||
@@ -171,4 +177,4 @@ public class ModdedTraders
|
||||
/** Trader Ids to enable the clothing service for */
|
||||
[JsonPropertyName("clothingService")]
|
||||
public List<string> ClothingService { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ public class ProfileChangeEvent
|
||||
|
||||
[JsonPropertyName("entity")]
|
||||
public string? Entity { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public string? Data { get; set; }
|
||||
}
|
||||
|
||||
public enum ProfileChangeEventType
|
||||
@@ -105,4 +108,4 @@ public enum ProfileChangeEventType
|
||||
UnlockTrader,
|
||||
AssortmentUnlockRule,
|
||||
HideoutAreaLevel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Core.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Utils;
|
||||
|
||||
namespace Core.Routers.Dynamic;
|
||||
|
||||
[Injectable(InjectableTypeOverride = typeof(DynamicRouter))]
|
||||
public class HttpDynamicRouter : DynamicRouter
|
||||
{
|
||||
public HttpDynamicRouter(ImageRouter imageRouter) : base(
|
||||
public HttpDynamicRouter(ImageRouter imageRouter, JsonUtil jsonUtil) : base(
|
||||
jsonUtil,
|
||||
[
|
||||
new(".jpg", (_, _, _, _) => imageRouter.GetImage()),
|
||||
new(".png", (_, _, _, _) => imageRouter.GetImage()),
|
||||
@@ -15,9 +17,4 @@ public class HttpDynamicRouter : DynamicRouter
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public override Type? GetBodyDeserializationType()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,10 @@ public class HttpRouter
|
||||
foreach (var route in routers)
|
||||
{
|
||||
if (route.CanHandle(url, dynamic)) {
|
||||
var type = route.GetBodyDeserializationType();
|
||||
if (type != null && !string.IsNullOrEmpty(body))
|
||||
deserializedObject = JsonSerializer.Deserialize(body, type);
|
||||
if (dynamic) {
|
||||
wrapper.Output = (route as DynamicRouter).HandleDynamic(url, deserializedObject, sessionID, wrapper.Output) as string;
|
||||
wrapper.Output = (route as DynamicRouter).HandleDynamic(url, body, sessionID, wrapper.Output) as string;
|
||||
} else {
|
||||
wrapper.Output = (route as StaticRouter).HandleStatic(url, deserializedObject, sessionID, wrapper.Output) as string;
|
||||
wrapper.Output = (route as StaticRouter).HandleStatic(url, body, sessionID, wrapper.Output) as string;
|
||||
}
|
||||
matched = true;
|
||||
}
|
||||
|
||||
@@ -2,59 +2,68 @@ using Core.Annotations;
|
||||
using Core.Callbacks;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Launcher;
|
||||
using Core.Utils;
|
||||
|
||||
namespace Core.Routers.Static;
|
||||
|
||||
[Injectable(InjectableTypeOverride = typeof(StaticRouter))]
|
||||
public class LauncherStaticRouter : StaticRouter {
|
||||
|
||||
public LauncherStaticRouter(LauncherCallbacks launcherCallbacks) : base([
|
||||
new RouteAction<EmptyRequestData?>(
|
||||
"/launcher/ping",
|
||||
(url, _, sessionID, _) => launcherCallbacks.Ping(url, null, sessionID)),
|
||||
new RouteAction<EmptyRequestData?>(
|
||||
"/launcher/server/connect",
|
||||
(_, _, _, _) => launcherCallbacks.Connect()),
|
||||
new RouteAction(
|
||||
"/launcher/profile/login",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Login(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/register",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Register(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/get",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Get(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/change/username",
|
||||
(url, info, sessionID, _) => launcherCallbacks.ChangeUsername(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/change/password",
|
||||
(url, info, sessionID, _) => launcherCallbacks.ChangePassword(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/change/wipe",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Wipe(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/remove",
|
||||
(url, info, sessionID, _) => launcherCallbacks.RemoveProfile(url, info, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/compatibleTarkovVersion",
|
||||
(_, _, _, _) => launcherCallbacks.GetCompatibleTarkovVersion()),
|
||||
new RouteAction(
|
||||
"/launcher/server/version",
|
||||
(_, _, _, _) => launcherCallbacks.GetServerVersion()),
|
||||
new RouteAction(
|
||||
"/launcher/server/loadedServerMods",
|
||||
(_, _, _, _) => launcherCallbacks.GetLoadedServerMods()),
|
||||
new RouteAction(
|
||||
"/launcher/server/serverModsUsedByProfile",
|
||||
(url, info, sessionID, _) => launcherCallbacks.GetServerModsProfileUsed(url, info, sessionID)),
|
||||
])
|
||||
public class LauncherStaticRouter : StaticRouter
|
||||
{
|
||||
public LauncherStaticRouter(LauncherCallbacks launcherCallbacks, JsonUtil jsonUtil) : base(
|
||||
jsonUtil,
|
||||
[
|
||||
new RouteAction(
|
||||
"/launcher/ping",
|
||||
(url, _, sessionID, _) => launcherCallbacks.Ping(url, null, sessionID)),
|
||||
new RouteAction(
|
||||
"/launcher/server/connect",
|
||||
(_, _, _, _) => launcherCallbacks.Connect()),
|
||||
new RouteAction(
|
||||
"/launcher/profile/login",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Login(url, info as LoginRequestData, sessionID),
|
||||
typeof(LoginRequestData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/register",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Register(url, info as RegisterData, sessionID),
|
||||
typeof(RegisterData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/get",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Get(url, info as LoginRequestData, sessionID),
|
||||
typeof(LoginRequestData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/change/username",
|
||||
(url, info, sessionID, _) =>
|
||||
launcherCallbacks.ChangeUsername(url, info as ChangeRequestData, sessionID),
|
||||
typeof(ChangeRequestData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/change/password",
|
||||
(url, info, sessionID, _) =>
|
||||
launcherCallbacks.ChangePassword(url, info as ChangeRequestData, sessionID),
|
||||
typeof(ChangeRequestData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/change/wipe",
|
||||
(url, info, sessionID, _) => launcherCallbacks.Wipe(url, info as RegisterData, sessionID),
|
||||
typeof(RegisterData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/remove",
|
||||
(url, info, sessionID, _) => launcherCallbacks.RemoveProfile(url, info as RemoveProfileData, sessionID),
|
||||
typeof(RemoveProfileData)),
|
||||
new RouteAction(
|
||||
"/launcher/profile/compatibleTarkovVersion",
|
||||
(_, _, _, _) => launcherCallbacks.GetCompatibleTarkovVersion()),
|
||||
new RouteAction(
|
||||
"/launcher/server/version",
|
||||
(_, _, _, _) => launcherCallbacks.GetServerVersion()),
|
||||
new RouteAction(
|
||||
"/launcher/server/loadedServerMods",
|
||||
(_, _, _, _) => launcherCallbacks.GetLoadedServerMods()),
|
||||
new RouteAction(
|
||||
"/launcher/server/serverModsUsedByProfile",
|
||||
(url, info, sessionID, _) =>
|
||||
launcherCallbacks.GetServerModsProfileUsed(url, info as EmptyRequestData, sessionID),
|
||||
typeof(EmptyRequestData))
|
||||
])
|
||||
{
|
||||
}
|
||||
|
||||
public override Type? GetBodyDeserializationType()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text.Json.Serialization;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Servers;
|
||||
@@ -11,16 +12,18 @@ namespace Core.Servers;
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class ConfigServer
|
||||
{
|
||||
private ILogger _logger;
|
||||
protected ILogger _logger;
|
||||
protected JsonUtil _jsonUtil;
|
||||
protected Dictionary<string, object> configs = new();
|
||||
protected readonly string[] acceptableFileExtensions = [".json", ".jsonc"];
|
||||
|
||||
public ConfigServer(
|
||||
ILogger logger
|
||||
// TODO: We need JsonUtil here => JsonUtil jsonUtil,
|
||||
ILogger logger,
|
||||
JsonUtil jsonUtil
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_jsonUtil = jsonUtil;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
@@ -50,8 +53,7 @@ public class ConfigServer
|
||||
{
|
||||
var fileContent = File.ReadAllText(file);
|
||||
var type = GetConfigTypeByFilename(file);
|
||||
var deserializedContent =
|
||||
JsonSerializer.Deserialize(fileContent, type, new JsonSerializerOptions() { Converters = { new JsonStringEnumConverter() } });
|
||||
var deserializedContent = _jsonUtil.Deserialize(fileContent, type);
|
||||
|
||||
if (deserializedContent == null)
|
||||
{
|
||||
@@ -78,4 +80,4 @@ public class ConfigServer
|
||||
.First(en => en.GetValue().Contains(Path.GetFileNameWithoutExtension(filename)));
|
||||
return type.GetConfigType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,13 @@ public class SptHttpListener : IHttpListener
|
||||
protected readonly ILogger _logger;
|
||||
protected readonly HttpResponseUtil _httpResponseUtil;
|
||||
protected readonly LocalisationService _localisationService;
|
||||
protected readonly JsonUtil _jsonUtil;
|
||||
public SptHttpListener(
|
||||
HttpRouter httpRouter, // TODO: delay required
|
||||
IEnumerable<ISerializer> serializers,
|
||||
ILogger logger,
|
||||
// TODO: requestsLogger: ILogger,
|
||||
// TODO: JsonUtil jsonUtil,
|
||||
JsonUtil jsonUtil,
|
||||
HttpResponseUtil httpHttpResponseUtil,
|
||||
LocalisationService localisationService
|
||||
)
|
||||
@@ -34,6 +35,7 @@ public class SptHttpListener : IHttpListener
|
||||
_logger = logger;
|
||||
_httpResponseUtil = httpHttpResponseUtil;
|
||||
_localisationService = localisationService;
|
||||
_jsonUtil = jsonUtil;
|
||||
}
|
||||
|
||||
private static readonly ImmutableHashSet<string> SupportedMethods = ["GET", "PUT", "POST"];
|
||||
@@ -98,8 +100,8 @@ public class SptHttpListener : IHttpListener
|
||||
)
|
||||
{
|
||||
if (body == null)
|
||||
body = "{}";
|
||||
var bodyInfo = JsonSerializer.Serialize(body);
|
||||
body = new object();
|
||||
var bodyInfo = _jsonUtil.Serialize(body);
|
||||
|
||||
if (IsDebugRequest(req)) {
|
||||
// Send only raw response without transformation
|
||||
@@ -160,7 +162,7 @@ public class SptHttpListener : IHttpListener
|
||||
/* route doesn't exist or response is not properly set up */
|
||||
if (string.IsNullOrEmpty(output)) {
|
||||
_logger.Error(_localisationService.GetText("unhandled_response", req.Path));
|
||||
_logger.Info(JsonSerializer.Serialize(deserializedObject));
|
||||
_logger.Info(_jsonUtil.Serialize(deserializedObject));
|
||||
output = _httpResponseUtil.GetBody<object?>(null, 404, $"UNHANDLED RESPONSE: {req.Path}");
|
||||
}
|
||||
return output;
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
using Core.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Servers;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class SaveServer
|
||||
{
|
||||
protected string profileFilepath = "user/profiles/";
|
||||
|
||||
protected Dictionary<string, SptProfile> profiles = new();
|
||||
|
||||
// onLoad = require("../bindings/SaveLoad");
|
||||
protected readonly Dictionary<string, Func<SptProfile, SptProfile>> onBeforeSaveCallbacks = new();
|
||||
protected Dictionary<string, string> saveMd5 = new();
|
||||
|
||||
protected readonly FileUtil _vfs;
|
||||
protected readonly IEnumerable<SaveLoadRouter> _saveLoadRouters;
|
||||
protected readonly JsonUtil _jsonUtil;
|
||||
protected readonly HashUtil _hashUtil;
|
||||
protected readonly LocalisationService _localisationService;
|
||||
protected readonly ILogger _logger;
|
||||
protected readonly ConfigServer _configServer;
|
||||
|
||||
public SaveServer(
|
||||
FileUtil vfs,
|
||||
IEnumerable<SaveLoadRouter> saveLoadRouters,
|
||||
JsonUtil jsonUtil,
|
||||
HashUtil hashUtil,
|
||||
LocalisationService localisationService,
|
||||
ILogger logger,
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_vfs = vfs;
|
||||
_saveLoadRouters = saveLoadRouters;
|
||||
_jsonUtil = jsonUtil;
|
||||
_hashUtil = hashUtil;
|
||||
_localisationService = localisationService;
|
||||
_logger = logger;
|
||||
_configServer = configServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add callback to occur prior to saving profile changes
|
||||
* @param id Id for save callback
|
||||
* @param callback Callback to execute prior to running SaveServer.saveProfile()
|
||||
*/
|
||||
public void AddBeforeSaveCallback(string id, Func<SptProfile, SptProfile> callback)
|
||||
{
|
||||
onBeforeSaveCallbacks[id] = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback from being executed prior to saving profile in SaveServer.saveProfile()
|
||||
* @param id Id of callback to remove
|
||||
*/
|
||||
public void RemoveBeforeSaveCallback(string id)
|
||||
{
|
||||
if (onBeforeSaveCallbacks.ContainsKey(id))
|
||||
onBeforeSaveCallbacks.Remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all profiles in /user/profiles folder into memory (this.profiles)
|
||||
*/
|
||||
public void Load()
|
||||
{
|
||||
// get files to load
|
||||
if (!_vfs.DirectoryExists(profileFilepath))
|
||||
{
|
||||
_vfs.CreateDirectory(profileFilepath);
|
||||
}
|
||||
|
||||
var files = _vfs.GetFiles(profileFilepath).Where((item) => _vfs.GetFileExtension(item) == "json");
|
||||
|
||||
// load profiles
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
foreach (var file in files) {
|
||||
LoadProfile(_vfs.StripExtension(file));
|
||||
}
|
||||
stopwatch.Stop();
|
||||
_logger.Debug($"{files.Count()} Profiles took: {stopwatch.ElapsedMilliseconds}ms to load.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes for each profile from memory into user/profiles json
|
||||
*/
|
||||
public void Save()
|
||||
{
|
||||
// Save every profile
|
||||
var totalTime = 0L;
|
||||
foreach ( var sessionID in profiles) {
|
||||
totalTime += SaveProfile(sessionID.Key);
|
||||
}
|
||||
|
||||
_logger.Debug($"Saved {profiles.Count} profiles, took: {totalTime}ms", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a player profile from memory
|
||||
* @param sessionId Session id
|
||||
* @returns ISptProfile
|
||||
*/
|
||||
public SptProfile GetProfile(string sessionId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sessionId))
|
||||
{
|
||||
throw new Exception("session id provided was empty, did you restart the server while the game was running?");
|
||||
}
|
||||
|
||||
if (profiles == null || profiles.Count == 0)
|
||||
{
|
||||
throw new Exception($"no profiles found in saveServer with id: ${sessionId}");
|
||||
}
|
||||
|
||||
if (!profiles.TryGetValue(sessionId, out var sptProfile))
|
||||
throw new Exception($"no profile found for sessionId: {sessionId}");
|
||||
|
||||
return sptProfile;
|
||||
}
|
||||
|
||||
public bool ProfileExists(string id)
|
||||
{
|
||||
return profiles.ContainsKey(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all profiles from memory
|
||||
* @returns Dictionary of ISptProfile
|
||||
*/
|
||||
public Dictionary<string, SptProfile> GetProfiles() => profiles;
|
||||
|
||||
/**
|
||||
* Delete a profile by id
|
||||
* @param sessionID Id of profile to remove
|
||||
* @returns true when deleted, false when profile not found
|
||||
*/
|
||||
public bool DeleteProfileById(string sessionID)
|
||||
{
|
||||
if (profiles.ContainsKey(sessionID))
|
||||
{
|
||||
profiles.Remove(sessionID);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new profile in memory with empty pmc/scav objects
|
||||
* @param profileInfo Basic profile data
|
||||
*/
|
||||
public void CreateProfile(Info profileInfo)
|
||||
{
|
||||
if (profiles.ContainsKey(profileInfo.ProfileId))
|
||||
{
|
||||
throw new Exception($"profile already exists for sessionId: {profileInfo.ProfileId}");
|
||||
}
|
||||
|
||||
profiles.Add(profileInfo.ProfileId,
|
||||
new SptProfile()
|
||||
{
|
||||
ProfileInfo = profileInfo,
|
||||
CharacterData = new Characters() { PmcData = new PmcData(), ScavData = new PmcData() }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add full profile in memory by key (info.id)
|
||||
* @param profileDetails Profile to save
|
||||
*/
|
||||
public void AddProfile(SptProfile profileDetails)
|
||||
{
|
||||
profiles.Add(profileDetails.ProfileInfo.ProfileId, profileDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up profile json in user/profiles by id and store in memory
|
||||
* Execute saveLoadRouters callbacks after being loaded into memory
|
||||
* @param sessionID Id of profile to store in memory
|
||||
*/
|
||||
public void LoadProfile(string sessionID)
|
||||
{
|
||||
var filename = $"{sessionID}.json";
|
||||
var filePath = $"{profileFilepath}{filename}";
|
||||
if (_vfs.FileExists(filePath))
|
||||
{
|
||||
// File found, store in profiles[]
|
||||
profiles[sessionID] = _jsonUtil.Deserialize<SptProfile>(_vfs.ReadFile(filePath));
|
||||
}
|
||||
|
||||
// Run callbacks
|
||||
foreach (var callback in _saveLoadRouters) {
|
||||
profiles[sessionID] = callback.HandleLoad(GetProfile(sessionID));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes from in-memory profile to user/profiles json
|
||||
* Execute onBeforeSaveCallbacks callbacks prior to being saved to json
|
||||
* @param sessionID profile id (user/profiles/id.json)
|
||||
* @returns time taken to save in MS
|
||||
*/
|
||||
public long SaveProfile(string sessionID)
|
||||
{
|
||||
var filePath = $"{profileFilepath}{sessionID}.json";
|
||||
|
||||
// Run pre-save callbacks before we save into json
|
||||
foreach ( var callback in onBeforeSaveCallbacks)
|
||||
{
|
||||
var previous = profiles[sessionID];
|
||||
try
|
||||
{
|
||||
profiles[sessionID] = onBeforeSaveCallbacks[callback.Key](profiles[sessionID]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("profile_save_callback_error", new { callback, error = e }));
|
||||
profiles[sessionID] = previous;
|
||||
}
|
||||
}
|
||||
|
||||
var start = Stopwatch.StartNew();
|
||||
var jsonProfile = _jsonUtil.Serialize(profiles[sessionID], !_configServer.GetConfig<CoreConfig>(ConfigTypes.CORE).Features.CompressProfile);
|
||||
var fmd5 = _hashUtil.GenerateMd5ForData(jsonProfile);
|
||||
if (!saveMd5.TryGetValue(sessionID, out var currentMd5) || currentMd5 != fmd5) {
|
||||
saveMd5[sessionID] = fmd5;
|
||||
// save profile to disk
|
||||
_vfs.WriteFile(filePath, jsonProfile);
|
||||
}
|
||||
|
||||
start.Stop();
|
||||
return start.ElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a physical profile json from user/profiles
|
||||
* @param sessionID Profile id to remove
|
||||
* @returns true if file no longer exists
|
||||
*/
|
||||
public bool RemoveProfile(string sessionID)
|
||||
{
|
||||
var file = $"{profileFilepath}{sessionID}.json";
|
||||
if (profiles.ContainsKey(sessionID))
|
||||
{
|
||||
profiles.Remove(sessionID);
|
||||
_vfs.DeleteFile(file);
|
||||
}
|
||||
return !_vfs.FileExists(file);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using Core.Utils;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
@@ -8,17 +9,18 @@ public class I18nService
|
||||
private Dictionary<string, string> _fallbacks;
|
||||
private string _defaultLocale;
|
||||
private string _directory;
|
||||
|
||||
private JsonUtil _jsonUtil;
|
||||
private string _setLocale;
|
||||
|
||||
private Dictionary<string, Dictionary<string, string>> _loadedLocales = new();
|
||||
|
||||
public I18nService(List<string> locales, Dictionary<string, string> fallbacks, string defaultLocale, string directory)
|
||||
public I18nService(JsonUtil jsonUtil, List<string> locales, Dictionary<string, string> fallbacks, string defaultLocale, string directory)
|
||||
{
|
||||
_locales = locales;
|
||||
_fallbacks = fallbacks;
|
||||
_defaultLocale = defaultLocale;
|
||||
_directory = directory;
|
||||
_jsonUtil = jsonUtil;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
@@ -30,7 +32,7 @@ public class I18nService
|
||||
throw new Exception($"Localisation files in directory {_directory} not found.");
|
||||
foreach (var file in files)
|
||||
_loadedLocales.Add(Path.GetFileNameWithoutExtension(file),
|
||||
JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(file)) ?? new Dictionary<string, string>());
|
||||
_jsonUtil.Deserialize<Dictionary<string, string>>(File.ReadAllText(file)) ?? new Dictionary<string, string>());
|
||||
|
||||
if (!_loadedLocales.ContainsKey(_defaultLocale))
|
||||
throw new Exception($"The default locale '{_defaultLocale}' does not exist on the loaded locales.");
|
||||
@@ -76,4 +78,4 @@ public class I18nService
|
||||
// TODO: Deal with arguments
|
||||
return GetLocalised(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ public class LocalisationService
|
||||
ILogger logger,
|
||||
RandomUtil randomUtil,
|
||||
DatabaseServer databaseServer,
|
||||
LocaleService localeService
|
||||
LocaleService localeService,
|
||||
JsonUtil jsonUtil
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -26,6 +27,7 @@ public class LocalisationService
|
||||
_databaseServer = databaseServer;
|
||||
_localeService = localeService;
|
||||
_i18nService = new I18nService(
|
||||
jsonUtil,
|
||||
localeService.GetServerSupportedLocales(),
|
||||
localeService.GetLocaleFallbacks(),
|
||||
"en",
|
||||
@@ -48,4 +50,4 @@ public class LocalisationService
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,42 @@ public class FileUtil
|
||||
{
|
||||
return keepPath ? path.Split('.').First() : Path.GetFileNameWithoutExtension(path);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
public string ReadFile(string path)
|
||||
{
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public void WriteFile(string filePath, string jsonProfile)
|
||||
{
|
||||
if (!FileExists(filePath))
|
||||
CreateFile(filePath);
|
||||
File.WriteAllText(filePath, jsonProfile);
|
||||
}
|
||||
|
||||
private void CreateFile(string filePath)
|
||||
{
|
||||
File.Create(filePath);
|
||||
}
|
||||
|
||||
public void DeleteFile(string filePath)
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,15 @@ namespace Core.Utils;
|
||||
public class HttpResponseUtil
|
||||
{
|
||||
protected readonly LocalisationService _localisationService;
|
||||
protected readonly JsonUtil _jsonUtil;
|
||||
|
||||
public HttpResponseUtil(
|
||||
// JsonUtil jsonUtil,
|
||||
JsonUtil jsonUtil,
|
||||
LocalisationService localisationService
|
||||
)
|
||||
{
|
||||
_localisationService = localisationService;
|
||||
_jsonUtil = jsonUtil;
|
||||
}
|
||||
|
||||
private readonly ImmutableList<Regex> _cleanupRegexList =
|
||||
@@ -49,7 +51,7 @@ public class HttpResponseUtil
|
||||
*/
|
||||
public string NoBody<T>(T data)
|
||||
{
|
||||
return ClearString(JsonSerializer.Serialize(data));
|
||||
return ClearString(_jsonUtil.Serialize(data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +70,7 @@ public class HttpResponseUtil
|
||||
|
||||
public string GetUnclearedBody<T>(T? data, int err = 0, string? errmsg = null)
|
||||
{
|
||||
return JsonSerializer.Serialize(new GetBodyResponseData<T> { Err = err, ErrMsg = errmsg, Data = data });
|
||||
return _jsonUtil.Serialize(new GetBodyResponseData<T> { Err = err, ErrMsg = errmsg, Data = data });
|
||||
}
|
||||
|
||||
public string EmptyResponse()
|
||||
|
||||
@@ -10,17 +10,14 @@ namespace Core.Utils;
|
||||
public class ImporterUtil
|
||||
{
|
||||
private readonly FileUtil _fileUtil;
|
||||
private readonly JsonUtil _jsonUtil;
|
||||
|
||||
private readonly HashSet<string> filesToIgnore = ["bearsuits.json", "usecsuits.json", "archivedquests.json"];
|
||||
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, Converters = { new ListOrTConverterFactory(), new DictionaryOrListConverter() }
|
||||
};
|
||||
|
||||
public ImporterUtil(FileUtil fileUtil)
|
||||
public ImporterUtil(FileUtil fileUtil, JsonUtil jsonUtil)
|
||||
{
|
||||
_fileUtil = fileUtil;
|
||||
_jsonUtil = jsonUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +61,7 @@ public class ImporterUtil
|
||||
);
|
||||
try
|
||||
{
|
||||
var fileDeserialized = JsonSerializer.Deserialize(fileData, propertyType, jsonSerializerOptions);
|
||||
var fileDeserialized = _jsonUtil.Deserialize(fileData, propertyType);
|
||||
if (onObjectDeserialized != null)
|
||||
onObjectDeserialized(file, fileDeserialized);
|
||||
|
||||
@@ -159,7 +156,7 @@ public class ImporterUtil
|
||||
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);
|
||||
var fileDeserialized = _jsonUtil.Deserialize(fileData, propertyType);
|
||||
onObjectDeserialized(file, fileDeserialized);
|
||||
|
||||
matchedProperty.GetSetMethod().Invoke(result, [fileDeserialized]);
|
||||
@@ -198,7 +195,7 @@ public class ImporterUtil
|
||||
promises.Add(File.ReadAllTextAsync(fileNode).ContinueWith(fd =>
|
||||
{
|
||||
onReadCallback(fileNode, fd.Result);
|
||||
return JsonSerializer.Deserialize(fd.Result, typeof(object));
|
||||
return _jsonUtil.Deserialize(fd.Result, typeof(object));
|
||||
}));
|
||||
/*
|
||||
this.vfs
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Utils.Json.Converters;
|
||||
|
||||
public class EftEnumConverter<T> : JsonConverter<T>
|
||||
{
|
||||
private static readonly JsonSerializerOptions _options = new() {Converters = { new JsonStringEnumConverter() }};
|
||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String || reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var str = reader.GetString();
|
||||
return (T)Enum.Parse(typeof(T), str, true);
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonTokenType.Number)
|
||||
{
|
||||
var str = reader.GetInt32().ToString();
|
||||
return (T)Enum.Parse(typeof(T), str, true);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, _options);
|
||||
}
|
||||
|
||||
public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return Read(ref reader, typeToConvert, options);
|
||||
}
|
||||
|
||||
public override void WriteAsPropertyName(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, _options);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -39,7 +40,7 @@ public class StringToNumberFactoryConverter : JsonConverterFactory
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Tried to convert {value} into {type.Name} but failed to parse it, null value will be used instead.");
|
||||
Debug.WriteLine($"Tried to convert {value} into {type.Name} but failed to parse it, null value will be used instead.");
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
+42
-1
@@ -1,8 +1,49 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Dialog;
|
||||
using Core.Utils.Json.Converters;
|
||||
|
||||
namespace Core.Utils;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class JsonUtil
|
||||
{
|
||||
}
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions = new()
|
||||
{
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||
Converters =
|
||||
{
|
||||
new ListOrTConverterFactory(),
|
||||
new DictionaryOrListConverter(),
|
||||
new EftEnumConverter<SptAirdropTypeEnum>(),
|
||||
new EftEnumConverter<GiftSenderType>(),
|
||||
new EftEnumConverter<SeasonalEventType>(),
|
||||
new EftEnumConverter<ProfileChangeEventType>()
|
||||
}
|
||||
};
|
||||
|
||||
public T? Deserialize<T>(string? json)
|
||||
{
|
||||
return string.IsNullOrEmpty(json) ? default : JsonSerializer.Deserialize<T>(json, jsonSerializerOptions);
|
||||
}
|
||||
|
||||
public object? Deserialize(string? json, Type type)
|
||||
{
|
||||
return string.IsNullOrEmpty(json) ? null : JsonSerializer.Deserialize(json, type, jsonSerializerOptions);
|
||||
}
|
||||
|
||||
|
||||
public string? Serialize<T>(T? obj, bool indented = false)
|
||||
{
|
||||
jsonSerializerOptions.WriteIndented = indented;
|
||||
return obj == null ? null : JsonSerializer.Serialize(obj, jsonSerializerOptions);
|
||||
}
|
||||
|
||||
public string? Serialize(object? obj, Type type, bool indented = false)
|
||||
{
|
||||
jsonSerializerOptions.WriteIndented = indented;
|
||||
return obj == null ? null : JsonSerializer.Serialize(obj, type, jsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Logging;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
|
||||
namespace Core.Utils;
|
||||
|
||||
[Injectable]
|
||||
public class WatermarkLocale {
|
||||
protected List<string> description;
|
||||
protected List<string> warning;
|
||||
protected List<string> modding;
|
||||
|
||||
public WatermarkLocale(LocalisationService localisationService) {
|
||||
description = [
|
||||
localisationService.GetText("watermark-discord_url"),
|
||||
"",
|
||||
localisationService.GetText("watermark-free_of_charge"),
|
||||
localisationService.GetText("watermark-paid_scammed"),
|
||||
localisationService.GetText("watermark-commercial_use_prohibited"),
|
||||
];
|
||||
warning = [
|
||||
"",
|
||||
localisationService.GetText("watermark-testing_build"),
|
||||
localisationService.GetText("watermark-no_support"),
|
||||
"",
|
||||
$"{localisationService.GetText("watermark-report_issues_to")}:",
|
||||
localisationService.GetText("watermark-issue_tracker_url"),
|
||||
"",
|
||||
localisationService.GetText("watermark-use_at_own_risk"),
|
||||
];
|
||||
modding = [
|
||||
"",
|
||||
localisationService.GetText("watermark-modding_disabled"),
|
||||
"",
|
||||
localisationService.GetText("watermark-not_an_issue"),
|
||||
localisationService.GetText("watermark-do_not_report"),
|
||||
];
|
||||
}
|
||||
|
||||
public List<string> GetDescription() => description;
|
||||
public List<string> GetWarning() => warning;
|
||||
public List<string> GetModding() => modding;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public class Watermark {
|
||||
protected CoreConfig sptConfig;
|
||||
protected List<string> text = [];
|
||||
protected string versionLabel = "";
|
||||
|
||||
protected Models.Utils.ILogger _logger;
|
||||
protected ConfigServer _configServer;
|
||||
protected LocalisationService _localisationService;
|
||||
protected WatermarkLocale _watermarkLocale;
|
||||
public Watermark(
|
||||
Models.Utils.ILogger logger,
|
||||
ConfigServer configServer,
|
||||
LocalisationService localisationService,
|
||||
WatermarkLocale watermarkLocale
|
||||
) {
|
||||
_logger = logger;
|
||||
_configServer = configServer;
|
||||
_localisationService = localisationService;
|
||||
_watermarkLocale = watermarkLocale;
|
||||
sptConfig = _configServer.GetConfig<CoreConfig>(ConfigTypes.CORE);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
var description = _watermarkLocale.GetDescription();
|
||||
var warning = _watermarkLocale.GetWarning();
|
||||
var modding = _watermarkLocale.GetModding();
|
||||
var versionTag = GetVersionTag();
|
||||
|
||||
versionLabel = $"{sptConfig.ProjectName} {versionTag}";
|
||||
|
||||
text = [versionLabel];
|
||||
text = [..text, ..description];
|
||||
|
||||
/*
|
||||
if (ProgramStatics.DEBUG) {
|
||||
text = text.concat([...warning]);
|
||||
}
|
||||
if (!ProgramStatics.MODS) {
|
||||
text = text.concat([...modding]);
|
||||
}
|
||||
*/
|
||||
|
||||
if (sptConfig.CustomWatermarkLocaleKeys?.Count > 0) {
|
||||
foreach (var key in sptConfig.CustomWatermarkLocaleKeys) {
|
||||
text.AddRange(["", _localisationService.GetText(key)]);
|
||||
}
|
||||
}
|
||||
|
||||
SetTitle();
|
||||
ResetCursor();
|
||||
Draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a version string (x.x.x) or (x.x.x-BLEEDINGEDGE) OR (X.X.X (18xxx))
|
||||
* @param withEftVersion Include the eft version this spt version was made for
|
||||
* @returns string
|
||||
*/
|
||||
public string GetVersionTag(bool withEftVersion = false) {
|
||||
var sptVersion = /*ProgramStatics.SPT_VERSION ||*/ sptConfig.SptVersion;
|
||||
var versionTag = /*ProgramStatics.DEBUG&*/ $"{sptVersion} - {_localisationService.GetText("bleeding_edge_build")}";
|
||||
|
||||
if (withEftVersion) {
|
||||
var tarkovVersion = sptConfig.CompatibleTarkovVersion.Split(".").First();
|
||||
return $"{versionTag} ({tarkovVersion})";
|
||||
}
|
||||
|
||||
return versionTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle singleplayer/settings/version
|
||||
* Get text shown in game on screen, can't be translated as it breaks bsgs client when certian characters are used
|
||||
* @returns string
|
||||
*/
|
||||
public string GetInGameVersionLabel()
|
||||
{
|
||||
var sptVersion = /*ProgramStatics.SPT_VERSION ||*/ sptConfig.SptVersion;
|
||||
var versionTag = /*ProgramStatics.DEBUG ? */
|
||||
$"{sptVersion} - BLEEDINGEDGE { /*ProgramStatics.COMMIT?.slice(0, 6) ?? */""}";
|
||||
//: `${sptVersion} - ${ProgramStatics.COMMIT?.slice(0, 6) ?? ""}`;
|
||||
|
||||
return $"{sptConfig.ProjectName} {versionTag}";
|
||||
}
|
||||
|
||||
/** Set window title */
|
||||
protected void SetTitle()
|
||||
{
|
||||
Console.Title = versionLabel;
|
||||
}
|
||||
|
||||
/** Reset console cursor to top */
|
||||
protected void ResetCursor()
|
||||
{
|
||||
/*
|
||||
if (!ProgramStatics.COMPILED) {
|
||||
process.stdout.write("\u001B[2J\u001B[0;0f");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/** Draw the watermark */
|
||||
protected void Draw()
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
// Calculate size, add 10% for spacing to the right
|
||||
var longestLength = text.Aggregate((a, b) => a.Length > b.Length ? a : b).Length * 1.1;
|
||||
|
||||
// Create line of - to add top/bottom of watermark
|
||||
var line = "";
|
||||
for (var i = 0; i < longestLength; ++i) {
|
||||
line += "─";
|
||||
}
|
||||
|
||||
// Opening line
|
||||
result.Add($"┌─{line}─┐");
|
||||
|
||||
// Add content of watermark to screen
|
||||
foreach (var watermarkText in text) {
|
||||
var spacingSize = longestLength - watermarkText.Length;
|
||||
var textWithRightPadding = watermarkText;
|
||||
|
||||
for (var i = 0; i < spacingSize; ++i) {
|
||||
textWithRightPadding += " ";
|
||||
}
|
||||
|
||||
result.Add($"│ {textWithRightPadding} │");
|
||||
}
|
||||
|
||||
// Closing line
|
||||
result.Add($"└─{line}─┘");
|
||||
|
||||
// Log watermark to screen
|
||||
foreach (var text in result) {
|
||||
_logger.LogWithColor(text, LogTextColor.Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -19,6 +19,9 @@ public static class Program
|
||||
{
|
||||
|
||||
var serviceProvider = builder.Services.BuildServiceProvider();
|
||||
var watermark = serviceProvider.GetService<Watermark>();
|
||||
watermark.Initialize();
|
||||
// TODO: var preSptModLoader = serviceProvider.GetService<PreSptModLoader>();
|
||||
var app = serviceProvider.GetService<App>();
|
||||
var appContext = serviceProvider.GetService<ApplicationContext>();
|
||||
appContext.AddValue(ContextVariableType.APP_BUILDER, builder);
|
||||
@@ -78,4 +81,4 @@ public static class Program
|
||||
RegisterComponents(builderServices, typeof(App).Assembly.GetTypes()
|
||||
.Where(type => Attribute.IsDefined(type, typeof(Injectable))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user