using System.Security.Cryptography; using Core.Annotations; namespace Core.Utils; // TODO: Finish porting this class [Injectable(InjectionType.Singleton)] public class RandomUtil { private readonly Random _random = new(); /// /// Generates a random integer between the specified minimum and maximum values, inclusive. /// /// The minimum value (inclusive). /// The maximum value (inclusive). /// A random integer between the specified minimum and maximum values. public int GetInt(int min, int max) { // Prevents a potential integer overflow. if (max == int.MaxValue) { max -= 1; } // maxVal is exclusive of the passed value, so add 1 return max > min ? _random.Next(min, max + 1) : min; } /// /// Generates a random integer between 1 (inclusive) and the specified maximum value (exclusive). /// If the maximum value is less than or equal to 1, it returns 1. /// /// The upper bound (exclusive) for the random integer generation. /// A random integer between 1 and max - 1, or 1 if max is less than or equal to 1. public int GetIntEx(int max) { return max > 2 ? _random.Next(1, max - 1) : 1; } /// /// Generates a random floating-point number within the specified range. /// /// The minimum value of the range (inclusive). /// The maximum value of the range (exclusive). /// A random floating-point number between `min` (inclusive) and `max` (exclusive). public float GetFloat(float min, float max) { return (float)GetSecureRandomNumber() * (max - min) + min; } /// /// Generates a random boolean value. /// /// A random boolean value, where the probability of `true` and `false` is approximately equal. public bool GetBool() { return GetSecureRandomNumber() < 0.5; } /// /// Calculates the percentage of a given number and returns the result. /// /// The percentage to calculate. /// The number to calculate the percentage of. /// The number of decimal places to round the result to (default is 2). /// The calculated percentage of the given number, rounded to the specified number of decimal places. public float GetPercentOfValue(float percent, float number, int toFixed = 2) { var num = percent * number / 100; return (float)Math.Round(num, toFixed); } /// /// Reduces a given number by a specified percentage. /// /// The original number to be reduced. /// The percentage by which to reduce the number. /// The reduced number after applying the percentage reduction. public float ReduceValueByPercent(float number, float percentage) { var reductionAmount = number * percentage / 100; return number - reductionAmount; } /// /// Determines if a random event occurs based on the given chance percentage. /// /// The percentage chance (0-100) that the event will occur. /// `true` if the event occurs, `false` otherwise. public bool GetChance100(float chancePercent) { chancePercent = Math.Clamp(chancePercent, 0f, 100f); return GetIntEx(100) <= chancePercent; } /// /// Returns a random string from the provided collection of strings. /// /// This method is separate from GetCollectionValue so we can use a generic inference with GetCollectionValue. /// /// The collection of strings to select a random value from. /// A randomly selected string from the array. public string GetStringCollectionValue(IEnumerable collection) { return collection.ElementAt(GetInt(0, collection.Count() - 1)); } /// /// Returns a random string from the provided collection of strings. /// /// /// The type of elements in the collection. /// A random element from the collection. /// This was formerly getArrayValue() in the node server public T GetCollectionValue(IEnumerable collection) { return collection.ElementAt(GetInt(0, collection.Count() - 1)); } /// /// Gets a random key from the given dictionary /// /// The dictionary from which to retrieve a key. /// Type of key /// Type of Value /// A random TKey representing one of the keys of the dictionary. public TKey GetKey(Dictionary dictionary) where TKey : notnull { return GetCollectionValue(dictionary.Keys); } /// /// Generates a secure random number between 0 (inclusive) and 1 (exclusive). /// /// This method uses the `crypto` module to generate a 48-bit random integer, /// which is then divided by the maximum possible 48-bit integer value to /// produce a floating-point number in the range [0, 1). /// /// A secure random number between 0 (inclusive) and 1 (exclusive). private static double GetSecureRandomNumber() { var buffer = new byte[6]; using var rng = RandomNumberGenerator.Create(); // Fill buffer with random bytes rng.GetBytes(buffer); var integer = 0; for (var i = 0; i < 6; i++) { integer = (integer << 8) | buffer[i]; } const ulong maxInt = 1UL << 48; return (double)integer / maxInt; } /// /// Determines the number of decimal places in a number. /// /// The number to analyze. /// The number of decimal places, or 0 if none exist. private static int GetNumberPrecision(double num) { return num.ToString().Split('.')[1]?.Length ?? 0; } }