From 7445a1f5d806a04e35b52339e9a4ff55cb50c7b9 Mon Sep 17 00:00:00 2001 From: Kyle Belanger Date: Sun, 18 Feb 2024 23:11:58 -0500 Subject: [PATCH] utils for JSON files and RNG/rolling, abstracted arg parsing for commands --- BLHX.Server.Common/Utils/Config.cs | 25 ++++++++++ BLHX.Server.Common/Utils/JSON.cs | 35 +++++++++++++ BLHX.Server.Common/Utils/RNG.cs | 63 ++++++++++++++++++++++++ BLHX.Server.Common/Utils/Singleton.cs | 17 +++++++ BLHX.Server.Game/Commands/Command.cs | 17 +++++++ BLHX.Server.Game/Commands/TestCommand.cs | 62 +++++++++++++++++++++++ BLHX.Server.Game/GameServer.cs | 2 +- BLHX.Server.Game/Handlers/P10.cs | 19 +++---- BLHX.Server/Program.cs | 2 + 9 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 BLHX.Server.Common/Utils/Config.cs create mode 100644 BLHX.Server.Common/Utils/JSON.cs create mode 100644 BLHX.Server.Common/Utils/RNG.cs create mode 100644 BLHX.Server.Common/Utils/Singleton.cs create mode 100644 BLHX.Server.Game/Commands/TestCommand.cs diff --git a/BLHX.Server.Common/Utils/Config.cs b/BLHX.Server.Common/Utils/Config.cs new file mode 100644 index 0000000..7098fe3 --- /dev/null +++ b/BLHX.Server.Common/Utils/Config.cs @@ -0,0 +1,25 @@ +namespace BLHX.Server.Common.Utils; + +public class Config : Singleton +{ + public string Address { get; set; } = "192.168.1.4"; + public uint Port { get; set; } = 20000; + + public static void Load() + { + Instance = JSON.Load(JSON.ConfigPath); + +#if DEBUG + Logger.c.Log($"Loaded Config:\n{JSON.Stringify(Instance)}"); +#endif + } + + public static void Save() + { + JSON.Save(JSON.ConfigPath, Instance); + +#if DEBUG + Logger.c.Log("Saved Config"); +#endif + } +} diff --git a/BLHX.Server.Common/Utils/JSON.cs b/BLHX.Server.Common/Utils/JSON.cs new file mode 100644 index 0000000..508f5e5 --- /dev/null +++ b/BLHX.Server.Common/Utils/JSON.cs @@ -0,0 +1,35 @@ +using System.Text.Json; + +namespace BLHX.Server.Common.Utils; + +public static class JSON +{ + public static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); + + public static T Load(string path) where T : new() + { + if (!File.Exists(path)) + { + T obj = new T(); + Save(path, obj); + } + + return JsonSerializer.Deserialize(File.ReadAllText(path)); + } + + public static void Save(string path, T obj) + { + File.WriteAllText(path, JsonSerializer.Serialize(obj, new JsonSerializerOptions() + { + WriteIndented = true + })); + } + + public static string Stringify(T obj) + { + return JsonSerializer.Serialize(obj, new JsonSerializerOptions() + { + WriteIndented = true + }); + } +} diff --git a/BLHX.Server.Common/Utils/RNG.cs b/BLHX.Server.Common/Utils/RNG.cs new file mode 100644 index 0000000..3a07b9c --- /dev/null +++ b/BLHX.Server.Common/Utils/RNG.cs @@ -0,0 +1,63 @@ +namespace BLHX.Server.Common.Utils; + +public static class RNG +{ + public static readonly SortedDictionary ShipRarityRates = new() + { + {6, 1.2f}, // UR + {5, 7f}, // SSR + {4, 12f}, // Elite + {2, 28.8f}, // Normal + {3, 51f}, // Rare + }; + + static readonly Random random = new Random((int)DateTime.Now.Ticks); + + public static int Next(int min, int max) + => random.Next(min, max); + + public static int Next(int max) + => random.Next(max); + + public static float NextFloat(float min, float max) + { + double range = (double)max - min; + double sample = random.NextDouble(); + double scaled = (sample * range) + min; + + return (float)scaled; + } + + public static float NextFloat(float max) + => NextFloat(0f, max); + + public static bool NextBool() + => random.Next(2) == 0; + + public static float NextRoll() + => NextFloat(100f); + + public static T NextFromList(IList list) + => list[random.Next(list.Count)]; + + public static U NextFromDict(IDictionary dict) + => dict.ElementAt(random.Next(dict.Count)).Value; + + public static int NextFromRarityDict(SortedDictionary dict) + { + float roll = NextRoll(); + float sum = 0f; + + foreach (var pair in dict) + { + sum += pair.Value; + if (roll <= sum) + return pair.Key; + } + + throw new Exception("NextFromRarityDict() roll failed"); + } + + public static int NextShipRarity() + => NextFromRarityDict(ShipRarityRates); +} diff --git a/BLHX.Server.Common/Utils/Singleton.cs b/BLHX.Server.Common/Utils/Singleton.cs new file mode 100644 index 0000000..aa9ff36 --- /dev/null +++ b/BLHX.Server.Common/Utils/Singleton.cs @@ -0,0 +1,17 @@ +namespace BLHX.Server.Common.Utils; + +public abstract class Singleton where T : new() +{ + static T instance; + public static T Instance + { + get + { + if (instance == null) + instance = new T(); + + return instance; + } + set => instance = value; + } +} diff --git a/BLHX.Server.Game/Commands/Command.cs b/BLHX.Server.Game/Commands/Command.cs index 7409d8a..9492dc4 100644 --- a/BLHX.Server.Game/Commands/Command.cs +++ b/BLHX.Server.Game/Commands/Command.cs @@ -16,6 +16,7 @@ public class commandHandler : Attribute Description = description; Example = example; } + } [AttributeUsage(AttributeTargets.Property)] @@ -48,6 +49,22 @@ public abstract class Command prop.SetValue(this, arg.Value); } } + + protected T Parse(string? value, T fallback = default) + { + var tryParseMethod = typeof(T).GetMethod("TryParse", [typeof(string), typeof(T).MakeByRefType()]); + + if (tryParseMethod != null) + { + var parameters = new object[] { value, null }; + bool success = (bool)tryParseMethod.Invoke(null, parameters); + + if (success) + return (T)parameters[1]; + } + + return fallback; + } } public static class CommandHandler diff --git a/BLHX.Server.Game/Commands/TestCommand.cs b/BLHX.Server.Game/Commands/TestCommand.cs new file mode 100644 index 0000000..04bbc61 --- /dev/null +++ b/BLHX.Server.Game/Commands/TestCommand.cs @@ -0,0 +1,62 @@ +using BLHX.Server.Common.Utils; + +namespace BLHX.Server.Game.Commands; + +[commandHandler("test", "Test command", "test type=gacha")] +public class TestCommand : Command +{ + static readonly string[] RarityStrings = { "Unknown", "Unused", "Normal", "Rare", "Elite", "SSR", "UR" }; + + [Argument("type")] + public string? Type { get; set; } + + [Argument("count")] + public string? Count { get; set; } + + [Argument("verbose")] + public string? Verbose { get; set; } + + public override void Execute(Dictionary args) + { + base.Execute(args); + + switch (Type) + { + case "gacha": + TestGacha(Parse(Count, 1000000), Parse(Verbose, false)); + break; + default: + Logger.c.Warn("Unknown test type"); + break; + } + } + + void TestGacha(int count, bool verbose) + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var counts = new int[7]; + + for (int i = 0; i < count; i++) + { + int rarity = RNG.NextShipRarity(); + + counts[rarity]++; + + if (verbose) + Logger.c.Log($"Roll {i + 1}: {rarity} - {RarityStrings[rarity]}"); + } + + stopwatch.Stop(); + + Logger.c.Log("----------------------------------------"); + Logger.c.Log($"TOTAL ROLLS: {count}"); + Logger.c.Log($"PROCESSING TIME: {stopwatch.Elapsed}"); + + for (int i = 2; i < counts.Length; i++) + { + double percentage = (double)Math.Round(counts[i] / (double)count * 100, 2); + + Logger.c.Log($"{RarityStrings[i]}: {counts[i]} ({percentage}%)"); + } + } +} diff --git a/BLHX.Server.Game/GameServer.cs b/BLHX.Server.Game/GameServer.cs index 5d7c481..741cd98 100644 --- a/BLHX.Server.Game/GameServer.cs +++ b/BLHX.Server.Game/GameServer.cs @@ -16,7 +16,7 @@ namespace BLHX.Server.Game // Preload System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(PacketFactory).TypeHandle); - EndPoint = new(IPAddress.Any, 20000); + EndPoint = new(IPAddress.Any, (int)Config.Instance.Port); listener = new TcpListener(EndPoint); } diff --git a/BLHX.Server.Game/Handlers/P10.cs b/BLHX.Server.Game/Handlers/P10.cs index 398a478..26fdfa9 100644 --- a/BLHX.Server.Game/Handlers/P10.cs +++ b/BLHX.Server.Game/Handlers/P10.cs @@ -1,5 +1,6 @@ using BHXY.Server.Common.Proto.p10; using BLHX.Server.Common.Proto; +using BLHX.Server.Common.Utils; namespace BLHX.Server.Game.Handlers { @@ -11,11 +12,11 @@ namespace BLHX.Server.Game.Handlers var req = packet.Decode(); connection.Send(new Sc10801() { - GatewayIp = "192.168.1.4", - GatewayPort = 20000, - Url = "http://192.168.1.4", - ProxyIp = "192.168.1.4", - ProxyPort = 20000, + GatewayIp = Config.Instance.Address, + GatewayPort = Config.Instance.Port, + Url = "http://" + Config.Instance.Address, + ProxyIp = Config.Instance.Address, + ProxyPort = Config.Instance.Port, Versions = [ "$azhash$7$1$459$470aa097fec844d6", "$cvhash$467$98edcdd4e7dac668", @@ -48,10 +49,10 @@ namespace BLHX.Server.Game.Handlers { Ids = [0], Name = "BLHX.Server", - Ip = "192.168.1.4", - Port = 20000, - ProxyIp = "192.168.1.4", - ProxyPort = 20000 + Ip = Config.Instance.Address, + Port = Config.Instance.Port, + ProxyIp = Config.Instance.Address, + ProxyPort = Config.Instance.Port } ], ServerTicket = req.Arg3 diff --git a/BLHX.Server/Program.cs b/BLHX.Server/Program.cs index c4e0fdd..8989236 100644 --- a/BLHX.Server/Program.cs +++ b/BLHX.Server/Program.cs @@ -9,6 +9,8 @@ internal class Program { Logger.c.Log("Starting..."); + Config.Load(); + Task.Run(GameServer.Start); Task.Run(InputSystem.Start).Wait(); }