From f336a5636437e340c611cfe77c268b8c4bcbb8ba Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 2 May 2020 23:41:30 -0700 Subject: [PATCH] Adds settings (#126) --- .../Accounting/AccountAttackLimiter.cs | 6 +- Projects/Scripts/Accounting/AccountHandler.cs | 18 ++- Projects/Scripts/Accounting/IPLimiter.cs | 22 ++-- .../Configuration/ExpansionConfiguration.cs | 39 +++++++ Projects/Scripts/Engines/Ethics/Core/Ethic.cs | 4 +- .../Scripts/Engines/MLQuests/MLQuestSystem.cs | 7 +- .../Scripts/Engines/Pathing/PathFollower.cs | 9 +- .../Engines/VeteranRewards/RewardSystem.cs | 24 ++-- Projects/Scripts/Misc/BuffIcons.cs | 4 +- Projects/Scripts/Misc/ClientVerification.cs | 17 ++- Projects/Scripts/Misc/CurrentExpansion.cs | 41 ------- Projects/Scripts/Misc/ServerList.cs | 11 +- Projects/Scripts/Mobiles/BaseCreature.cs | 7 +- .../Humanoid/Melee/KhaldunRevenant.cs | 2 +- .../SpecialSystems/Engines/TestCenter.cs | 8 +- .../Configuration/ServerConfiguration.cs | 108 +++++++++++++++++- .../Server/JsonConfiguration/JsonConfig.cs | 1 + 17 files changed, 229 insertions(+), 99 deletions(-) create mode 100644 Projects/Scripts/Configuration/ExpansionConfiguration.cs delete mode 100644 Projects/Scripts/Misc/CurrentExpansion.cs diff --git a/Projects/Scripts/Accounting/AccountAttackLimiter.cs b/Projects/Scripts/Accounting/AccountAttackLimiter.cs index ac8ecdd57..23bb338b3 100644 --- a/Projects/Scripts/Accounting/AccountAttackLimiter.cs +++ b/Projects/Scripts/Accounting/AccountAttackLimiter.cs @@ -8,7 +8,11 @@ namespace Server.Accounting { public class AccountAttackLimiter { - public static bool Enabled = true; + public static bool Enabled; + public static void Configure() + { + Enabled = ServerConfiguration.GetOrUpdateSetting("accountAttackLimiter.enabled", true); + } private static readonly List m_List = new List(); diff --git a/Projects/Scripts/Accounting/AccountHandler.cs b/Projects/Scripts/Accounting/AccountHandler.cs index 1e9412f17..f13b6f7c5 100644 --- a/Projects/Scripts/Accounting/AccountHandler.cs +++ b/Projects/Scripts/Accounting/AccountHandler.cs @@ -11,10 +11,18 @@ namespace Server.Misc { public class AccountHandler { - private static readonly int MaxAccountsPerIP = 1; - private static readonly bool AutoAccountCreation = true; - private static readonly bool RestrictDeletion = !TestCenter.Enabled; - private static readonly TimeSpan DeleteDelay = TimeSpan.FromDays(7.0); + private static int MaxAccountsPerIP; + private static bool AutoAccountCreation; + private static bool RestrictDeletion = !TestCenter.Enabled; + private static TimeSpan DeleteDelay = TimeSpan.FromDays(7.0); + private static bool PasswordCommandEnabled; + + public static void Configure() + { + MaxAccountsPerIP = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1); + AutoAccountCreation = ServerConfiguration.GetOrUpdateSetting("accountHandler.autoAccountCreation", true); + PasswordCommandEnabled = ServerConfiguration.GetOrUpdateSetting("accountHandler.enablePlayerPasswordCommand", false); + } private static readonly CityInfo[] StartingCities = { @@ -46,8 +54,6 @@ namespace Server.Misc } */ - private static readonly bool PasswordCommandEnabled = false; - private static Dictionary m_IPTable; private static readonly char[] m_ForbiddenChars = diff --git a/Projects/Scripts/Accounting/IPLimiter.cs b/Projects/Scripts/Accounting/IPLimiter.cs index 8d866b070..e20bcfe4b 100644 --- a/Projects/Scripts/Accounting/IPLimiter.cs +++ b/Projects/Scripts/Accounting/IPLimiter.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using Server.Network; @@ -6,24 +7,23 @@ namespace Server.Misc { public class IPLimiter { - public static bool Enabled = true; - public static bool SocketBlock = true; // true to block at connection, false to block at login request + public static bool Enabled { get; private set; } + public static bool SocketBlock { get; private set; } + public static int MaxAddresses { get; private set; } - public static int MaxAddresses = 10; + public static void Configure() + { + Enabled = ServerConfiguration.GetOrUpdateSetting("ipLImiter.enabled", true); + SocketBlock = ServerConfiguration.GetOrUpdateSetting("ipLImiter.blockAtConnection", true); + MaxAddresses = ServerConfiguration.GetOrUpdateSetting("ipLimiter.maxConnectionsPerIP", 10); + } public static IPAddress[] Exemptions = { // IPAddress.Parse( "127.0.0.1" ), }; - public static bool IsExempt(IPAddress ip) - { - for (int i = 0; i < Exemptions.Length; i++) - if (ip.Equals(Exemptions[i])) - return true; - - return false; - } + public static bool IsExempt(IPAddress ip) => Exemptions.Contains(ip); public static bool Verify(IPAddress ourAddress) { diff --git a/Projects/Scripts/Configuration/ExpansionConfiguration.cs b/Projects/Scripts/Configuration/ExpansionConfiguration.cs new file mode 100644 index 000000000..aaf7d3615 --- /dev/null +++ b/Projects/Scripts/Configuration/ExpansionConfiguration.cs @@ -0,0 +1,39 @@ +using System; +using Server.Accounting; +using Server.Items; +using Server.Network; + +namespace Server +{ + public static class ExpansionConfiguration + { + public static void Configure() + { + Core.Expansion = ServerConfiguration.GetOrUpdateSetting("currentExpansion", Expansion.TOL); + + AccountGold.Enabled = ServerConfiguration.GetSetting("accountGold.enabled", Core.TOL); + AccountGold.ConvertOnBank = ServerConfiguration.GetSetting("accountGold.convertOnBank", true); + AccountGold.ConvertOnTrade = ServerConfiguration.GetSetting("accountGold.convertOnTrade", false); + VirtualCheck.UseEditGump = ServerConfiguration.GetSetting("virtualChecks.useEditGump", true); + + Mobile.InsuranceEnabled = ServerConfiguration.GetSetting("insurance.enabled", Core.AOS); + ObjectPropertyList.Enabled = ServerConfiguration.GetSetting("opl.enabled", Core.AOS); + bool visibleDamage = ServerConfiguration.GetSetting("visibleDamage", Core.AOS); + Mobile.VisibleDamageType = visibleDamage ? VisibleDamageType.Related : VisibleDamageType.None; + Mobile.GuildClickMessage = ServerConfiguration.GetSetting("guildClickMessage", !Core.AOS); + Mobile.AsciiClickMessage = ServerConfiguration.GetSetting("asciiClickMessage", !Core.AOS); + + Mobile.ActionDelay = ServerConfiguration.GetSetting("actionDelay", Core.AOS ? 1000 : 500); + + if (Core.AOS) + { + AOS.DisableStatInfluences(); + + if (ObjectPropertyList.Enabled) + PacketHandlers.SingleClickProps = true; // single click for everything is overridden to check object property list + + Mobile.AOSStatusHandler = AOS.GetStatus; + } + } + } +} diff --git a/Projects/Scripts/Engines/Ethics/Core/Ethic.cs b/Projects/Scripts/Engines/Ethics/Core/Ethic.cs index b75c9658c..b114c3b8f 100644 --- a/Projects/Scripts/Engines/Ethics/Core/Ethic.cs +++ b/Projects/Scripts/Engines/Ethics/Core/Ethic.cs @@ -9,7 +9,7 @@ namespace Server.Ethics { public abstract class Ethic { - public static readonly bool Enabled = false; + public static bool Enabled { get; private set; } public static readonly Ethic Hero = new HeroEthic(); public static readonly Ethic Evil = new EvilEthic(); @@ -98,6 +98,8 @@ namespace Server.Ethics public static void Initialize() { + Enabled = ServerConfiguration.GetOrUpdateSetting("ethics.enabled", false); + if (Enabled) EventSink.Speech += EventSink_Speech; } diff --git a/Projects/Scripts/Engines/MLQuests/MLQuestSystem.cs b/Projects/Scripts/Engines/MLQuests/MLQuestSystem.cs index e9e2f23a8..7cc5e89a7 100644 --- a/Projects/Scripts/Engines/MLQuests/MLQuestSystem.cs +++ b/Projects/Scripts/Engines/MLQuests/MLQuestSystem.cs @@ -22,9 +22,10 @@ namespace Server.Engines.MLQuests public static readonly bool Debug = false; public static readonly List EmptyList = new List(); - private static readonly List m_EligiblePool = new List(); + public static bool Enabled { get; private set; } + static MLQuestSystem() { Quests = new Dictionary(); @@ -94,8 +95,6 @@ namespace Server.Engines.MLQuests } } - public static bool Enabled => Core.ML; - public static Dictionary Quests { get; } public static Dictionary> QuestGivers { get; } @@ -125,6 +124,8 @@ namespace Server.Engines.MLQuests public static void Initialize() { + Enabled = ServerConfiguration.GetOrUpdateSetting("questSystem.enableMLQuests", Core.ML); + if (!Enabled) return; diff --git a/Projects/Scripts/Engines/Pathing/PathFollower.cs b/Projects/Scripts/Engines/Pathing/PathFollower.cs index e63993197..4cba5f5d6 100644 --- a/Projects/Scripts/Engines/Pathing/PathFollower.cs +++ b/Projects/Scripts/Engines/Pathing/PathFollower.cs @@ -5,9 +5,7 @@ namespace Server { public class PathFollower { - // Should we use pathfinding? 'false' for not - private static readonly bool Enabled = true; - + private static bool Enabled; private static readonly TimeSpan RepathDelay = TimeSpan.FromSeconds(2.0); private readonly Mobile m_From; @@ -16,6 +14,11 @@ namespace Server private Point3D m_Next, m_LastGoalLoc; private MovementPath m_Path; + public static void Initialize() + { + Enabled = ServerConfiguration.GetOrUpdateSetting("pathfinding.enabled", true); + } + public PathFollower(Mobile from, IPoint3D goal) { m_From = from; diff --git a/Projects/Scripts/Engines/VeteranRewards/RewardSystem.cs b/Projects/Scripts/Engines/VeteranRewards/RewardSystem.cs index e99de53b4..35aa0e6f2 100644 --- a/Projects/Scripts/Engines/VeteranRewards/RewardSystem.cs +++ b/Projects/Scripts/Engines/VeteranRewards/RewardSystem.cs @@ -11,11 +11,10 @@ namespace Server.Engines.VeteranRewards private static RewardCategory[] m_Categories; private static RewardList[] m_Lists; - public static bool Enabled = true; // change to true to enable vet rewards + public static bool Enabled { get; private set; } - public static bool - SkillCapRewards = - true; // assuming vet rewards are enabled, should total skill cap bonuses be awarded? (720 skills total at 4th level) + // assuming vet rewards are enabled, should total skill cap bonuses be awarded? (720 skills total at 4th level) + public static bool SkillCapRewards { get; private set; } public static TimeSpan RewardInterval = TimeSpan.FromDays(30.0); @@ -173,16 +172,13 @@ namespace Server.Engines.VeteranRewards public static bool CheckIsUsableBy(Mobile from, Item item, object[] args = null) { - if (m_Lists == null) - SetupRewardTables(); - bool isRelaxedRules = item is DyeTub || item is MonsterStatuette; Type type = item.GetType(); - for (int i = 0; i < m_Lists.Length; ++i) + for (int i = 0; i < Lists.Length; ++i) { - RewardList list = m_Lists[i]; + RewardList list = Lists[i]; RewardEntry[] entries = list.Entries; for (int j = 0; j < entries.Length; ++j) @@ -235,14 +231,11 @@ namespace Server.Engines.VeteranRewards public static int GetRewardYear(Item item, object[] args) { - if (m_Lists == null) - SetupRewardTables(); - Type type = item.GetType(); - for (int i = 0; i < m_Lists.Length; ++i) + for (int i = 0; i < Lists.Length; ++i) { - RewardList list = m_Lists[i]; + RewardList list = Lists[i]; RewardEntry[] entries = list.Entries; for (int j = 0; j < entries.Length; ++j) @@ -462,6 +455,9 @@ namespace Server.Engines.VeteranRewards public static void Initialize() { + Enabled = ServerConfiguration.GetOrUpdateSetting("vetRewards.enabled", true); + SkillCapRewards = ServerConfiguration.GetOrUpdateSetting("vetReards.skillCapRewards", true); + if (Enabled) EventSink.Login += EventSink_Login; } diff --git a/Projects/Scripts/Misc/BuffIcons.cs b/Projects/Scripts/Misc/BuffIcons.cs index c2dd80e70..f6dda432c 100644 --- a/Projects/Scripts/Misc/BuffIcons.cs +++ b/Projects/Scripts/Misc/BuffIcons.cs @@ -6,10 +6,12 @@ namespace Server { public class BuffInfo { - public static bool Enabled => Core.ML; + public static bool Enabled { get; private set; } public static void Initialize() { + Enabled = ServerConfiguration.GetOrUpdateSetting("buffIcons.enabled", Core.ML); + if (Enabled) EventSink.ClientVersionReceived += ResendBuffsOnClientVersionReceived; } diff --git a/Projects/Scripts/Misc/ClientVerification.cs b/Projects/Scripts/Misc/ClientVerification.cs index 36896ae85..c2cd9e974 100644 --- a/Projects/Scripts/Misc/ClientVerification.cs +++ b/Projects/Scripts/Misc/ClientVerification.cs @@ -9,11 +9,11 @@ namespace Server.Misc { public class ClientVerification { - private static readonly bool m_DetectClientRequirement = true; - private static readonly OldClientResponse m_OldClientResponse = OldClientResponse.LenientKick; + private static bool m_DetectClientRequirement; + private static OldClientResponse m_OldClientResponse; - private static readonly TimeSpan m_AgeLeniency = TimeSpan.FromDays(10); - private static readonly TimeSpan m_GameTimeLeniency = TimeSpan.FromHours(25); + private static TimeSpan m_AgeLeniency; + private static TimeSpan m_GameTimeLeniency; public static ClientVersion Required { get; set; } @@ -23,10 +23,17 @@ namespace Server.Misc public static bool AllowGod { get; set; } = true; - public static TimeSpan KickDelay { get; set; } = TimeSpan.FromSeconds(20.0); + public static TimeSpan KickDelay { get; set; } public static void Initialize() { + m_DetectClientRequirement = ServerConfiguration.GetOrUpdateSetting("clientVerification.enabled", true); + m_OldClientResponse = + ServerConfiguration.GetOrUpdateSetting("clientVerification.oldClientResponse", OldClientResponse.Kick); + m_AgeLeniency = ServerConfiguration.GetOrUpdateSetting("clientVerification.ageLeniency", TimeSpan.FromDays(10)); + m_GameTimeLeniency = ServerConfiguration.GetOrUpdateSetting("clientVerification.gameTimeLeniency", TimeSpan.FromHours(25)); + KickDelay = ServerConfiguration.GetOrUpdateSetting("clientVerification.kickDelay", TimeSpan.FromSeconds(20.0)); + EventSink.ClientVersionReceived += EventSink_ClientVersionReceived; // ClientVersion.Required = null; diff --git a/Projects/Scripts/Misc/CurrentExpansion.cs b/Projects/Scripts/Misc/CurrentExpansion.cs deleted file mode 100644 index 11c203c84..000000000 --- a/Projects/Scripts/Misc/CurrentExpansion.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Server.Accounting; -using Server.Items; -using Server.Network; - -namespace Server -{ - public class CurrentExpansion - { - private static readonly Expansion Expansion = Expansion.TOL; - - public static void Configure() - { - Core.Expansion = Expansion; - - AccountGold.Enabled = Core.TOL; - AccountGold.ConvertOnBank = true; - AccountGold.ConvertOnTrade = false; - VirtualCheck.UseEditGump = true; - - bool Enabled = Core.AOS; - - Mobile.InsuranceEnabled = Enabled; - ObjectPropertyList.Enabled = Enabled; - Mobile.VisibleDamageType = Enabled ? VisibleDamageType.Related : VisibleDamageType.None; - Mobile.GuildClickMessage = !Enabled; - Mobile.AsciiClickMessage = !Enabled; - - if (Enabled) - { - AOS.DisableStatInfluences(); - - if (ObjectPropertyList.Enabled) - PacketHandlers.SingleClickProps = - true; // single click for everything is overridden to check object property list - - Mobile.ActionDelay = 1000; - Mobile.AOSStatusHandler = AOS.GetStatus; - } - } - } -} diff --git a/Projects/Scripts/Misc/ServerList.cs b/Projects/Scripts/Misc/ServerList.cs index e2a1c6141..6636b205f 100644 --- a/Projects/Scripts/Misc/ServerList.cs +++ b/Projects/Scripts/Misc/ServerList.cs @@ -36,18 +36,21 @@ namespace Server.Misc * IP address or hostname, whichever is applicable. Loopback clients will be directed to loopback. * * If you would like to listen on additional ports (i.e. 22, 23, 80, for clients behind highly restrictive egress - * firewalls) or specific IP adddresses you can do so by modifying the file SocketOptions.cs found in this directory. + * firewalls) or specific IP addresses you can do so by modifying the file SocketOptions.cs found in this directory. */ - public static readonly string Address = null; - public const string ServerName = "ModernUO TC"; + public static string Address { get; private set; } + public static string ServerName { get; private set; } - public const bool AutoDetect = true; + public static bool AutoDetect { get; private set; } private static IPAddress m_PublicAddress; public static void Initialize() { + Address = ServerConfiguration.GetOrUpdateSetting("serverListing.address", "(-null-)"); + AutoDetect = ServerConfiguration.GetOrUpdateSetting("serverListing.autoDetect", true); + if (Address == null) { if (AutoDetect) diff --git a/Projects/Scripts/Mobiles/BaseCreature.cs b/Projects/Scripts/Mobiles/BaseCreature.cs index 1bbe6ae1b..f23258d50 100644 --- a/Projects/Scripts/Mobiles/BaseCreature.cs +++ b/Projects/Scripts/Mobiles/BaseCreature.cs @@ -3266,7 +3266,12 @@ namespace Server.Mobiles { } - public const bool BondingEnabled = true; + public static void Initialize() + { + BondingEnabled = ServerConfiguration.GetOrUpdateSetting("taming.enableBonding", true); + } + + public static bool BondingEnabled { get; private set; } public virtual bool IsBondable => BondingEnabled && !Summoned; public virtual TimeSpan BondingDelay => TimeSpan.FromDays(7.0); diff --git a/Projects/Scripts/Mobiles/Monsters/Humanoid/Melee/KhaldunRevenant.cs b/Projects/Scripts/Mobiles/Monsters/Humanoid/Melee/KhaldunRevenant.cs index 4076f48ec..553d4408b 100644 --- a/Projects/Scripts/Mobiles/Monsters/Humanoid/Melee/KhaldunRevenant.cs +++ b/Projects/Scripts/Mobiles/Monsters/Humanoid/Melee/KhaldunRevenant.cs @@ -67,7 +67,7 @@ namespace Server.Mobiles public override bool BardImmune => true; public override Poison PoisonImmune => Poison.Lethal; - public static void Initialize() + public static new void Initialize() { EventSink.PlayerDeath += EventSink_PlayerDeath; } diff --git a/Projects/Scripts/SpecialSystems/Engines/TestCenter.cs b/Projects/Scripts/SpecialSystems/Engines/TestCenter.cs index ff002d41d..fa673b32a 100644 --- a/Projects/Scripts/SpecialSystems/Engines/TestCenter.cs +++ b/Projects/Scripts/SpecialSystems/Engines/TestCenter.cs @@ -8,8 +8,12 @@ namespace Server.Misc { public class TestCenter { - private const bool m_Enabled = false; - public static bool Enabled => m_Enabled; + public static bool Enabled { get; private set; } + + public static void Configure() + { + Enabled = ServerConfiguration.GetOrUpdateSetting("testCenter.enabled", false); + } public static void Initialize() { diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs index b3647481a..fafff15bc 100644 --- a/Projects/Server/Configuration/ServerConfiguration.cs +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -34,8 +34,104 @@ namespace Server private static ServerSettings m_Settings; public static List DataDirectories => m_Settings.dataDirectories; - public static Dictionary Settings => m_Settings.settings; - public static Dictionary Metadata => m_Settings.metadata; + + public static string GetSetting(string key, string defaultValue) + { + m_Settings.settings.TryGetValue(key, out string value); + return value == "(-null-)" ? null : value ?? defaultValue; + } + + public static int GetSetting(string key, int defaultValue) + { + m_Settings.settings.TryGetValue(key, out string strValue); + return int.TryParse(strValue, out int value) ? value : defaultValue; + } + + public static bool GetSetting(string key, bool defaultValue) + { + m_Settings.settings.TryGetValue(key, out string strValue); + return bool.TryParse(strValue, out bool value) ? value : defaultValue; + } + + public static T GetSetting(string key, T defaultValue) where T : struct, Enum + { + m_Settings.settings.TryGetValue(key, out string strValue); + return Enum.TryParse(strValue, out T value) ? value : defaultValue; + } + + public static string GetOrUpdateSetting(string key, string defaultValue) + { + if (m_Settings.settings.TryGetValue(key, out string value)) + return value; + + SetSetting(key, value = defaultValue); + return value; + } + + public static int GetOrUpdateSetting(string key, int defaultValue) + { + int value; + + if (m_Settings.settings.TryGetValue(key, out string strValue)) + value = int.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static bool GetOrUpdateSetting(string key, bool defaultValue) + { + bool value; + + if (m_Settings.settings.TryGetValue(key, out string strValue)) + value = bool.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static TimeSpan GetOrUpdateSetting(string key, TimeSpan defaultValue) + { + TimeSpan value; + + if (m_Settings.settings.TryGetValue(key, out string strValue)) + value = TimeSpan.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static T GetOrUpdateSetting(string key, T defaultValue) where T : struct, Enum + { + T value; + + if (m_Settings.settings.TryGetValue(key, out string strValue)) + value = Enum.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static void SetSetting(string key, string value) + { + m_Settings.settings[key] = value; + SaveConfiguration(); + } + + public static T GetMetadata(string key) where T : class + { + m_Settings.metadata.TryGetValue(key, out object value); + return value as T; + } + + public static void SetMetadata(string key, object value) + { + m_Settings.metadata[key] = value; + } public static void LoadConfiguration() { @@ -70,7 +166,12 @@ namespace Server } if (updated) + { SaveConfiguration(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Core: Configuration saved to {m_RelPath}."); + Console.ResetColor(); + } } internal class ServerSettings @@ -102,9 +203,6 @@ namespace Server public static void SaveConfiguration() { JsonConfig.Serialize(m_FilePath, m_Settings); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"Core: Configuration saved to {m_RelPath}."); - Console.ResetColor(); } } } diff --git a/Projects/Server/JsonConfiguration/JsonConfig.cs b/Projects/Server/JsonConfiguration/JsonConfig.cs index 8cf2071a1..b5988aeb4 100644 --- a/Projects/Server/JsonConfiguration/JsonConfig.cs +++ b/Projects/Server/JsonConfiguration/JsonConfig.cs @@ -30,6 +30,7 @@ namespace Server.Json ReadCommentHandling = JsonCommentHandling.Skip, WriteIndented = true, AllowTrailingCommas = true, + IgnoreNullValues = true }; public static T Deserialize(string filePath, JsonSerializerOptions options = null)