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;
}
}