Merge branch 'main' of https://github.com/sp-tarkov/server-csharp
This commit is contained in:
+332
-24
@@ -57,7 +57,7 @@ public class BotGenerator
|
||||
BotNameService botNameService,
|
||||
ConfigServer configServer,
|
||||
ICloner cloner
|
||||
)
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
@@ -97,7 +97,8 @@ public class BotGenerator
|
||||
bot.Info.Settings.Role = role;
|
||||
bot.Info.Side = "Savage";
|
||||
|
||||
var botGenDetails = new BotGenerationDetails{
|
||||
var botGenDetails = new BotGenerationDetails
|
||||
{
|
||||
IsPmc = false,
|
||||
Side = "Savage",
|
||||
Role = role,
|
||||
@@ -156,7 +157,23 @@ public class BotGenerator
|
||||
/// <returns>constructed bot</returns>
|
||||
public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var preparedBotBase = GetPreparedBotBase(
|
||||
botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided,
|
||||
botGenerationDetails.Side,
|
||||
botGenerationDetails.BotDifficulty
|
||||
);
|
||||
|
||||
// Get raw json data for bot (Cloned)
|
||||
var botRole = botGenerationDetails.IsPmc ?? false
|
||||
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
|
||||
: botGenerationDetails.Role;
|
||||
var botJsonTemplateClone = _cloner.Clone(_botHelper.GetBotTemplate(botRole));
|
||||
if (botJsonTemplateClone is not null)
|
||||
{
|
||||
_logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
|
||||
}
|
||||
|
||||
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -168,7 +185,12 @@ public class BotGenerator
|
||||
/// <returns>Cloned bot base</returns>
|
||||
public BotBase GetPreparedBotBase(string botRole, string botSide, string difficulty)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botBaseClone = GetCloneOfBotBase();
|
||||
botBaseClone.Info.Settings.Role = botRole;
|
||||
botBaseClone.Info.Side = botSide;
|
||||
botBaseClone.Info.Settings.BotDifficulty = difficulty;
|
||||
|
||||
return botBaseClone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -393,10 +415,12 @@ public class BotGenerator
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var equipmentKvP in blacklist.Gear) {
|
||||
foreach (var equipmentKvP in blacklist.Gear)
|
||||
{
|
||||
var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentKvP.Key];
|
||||
|
||||
foreach (var blacklistedTpl in equipmentKvP.Value) {
|
||||
foreach (var blacklistedTpl in equipmentKvP.Value)
|
||||
{
|
||||
// Set weighting to 0, will never be picked
|
||||
equipmentDict[blacklistedTpl] = 0;
|
||||
}
|
||||
@@ -421,7 +445,37 @@ public class BotGenerator
|
||||
/// <param name="botInventory">Bot to filter</param>
|
||||
public void RemoveBlacklistedLootFromBotTemplate(BotTypeInventory botInventory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
List<string> lootContainersToFilter = ["Backpack", "Pockets", "TacticalVest"];
|
||||
var props = botInventory.Items.GetType().GetProperties();
|
||||
|
||||
// Remove blacklisted loot from loot containers
|
||||
foreach (var lootContainerKey in lootContainersToFilter)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => x.Name.ToLower() == lootContainerKey.ToLower());
|
||||
var propValue = (Dictionary<string, double>)prop.GetValue(botInventory.Items);
|
||||
|
||||
// No container, skip
|
||||
if (propValue?.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<string> tplsToRemove = [];
|
||||
foreach (var item in propValue)
|
||||
{
|
||||
if (ItemFilterService.IsLootableItemBlacklisted(item.Key))
|
||||
{
|
||||
tplsToRemove.Add(item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var blacklistedTplToRemove in tplsToRemove)
|
||||
{
|
||||
propValue.Remove(blacklistedTplToRemove);
|
||||
}
|
||||
|
||||
prop.SetValue(botInventory.Items, propValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -432,7 +486,19 @@ public class BotGenerator
|
||||
/// <param name="botGenerationDetails">Generation details</param>
|
||||
public void SetBotAppearance(BotBase bot, Appearance appearance, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Choose random values by weight
|
||||
bot.Customization.Head = _weightedRandomHelper.GetWeightedValue<string>(appearance.Head);
|
||||
bot.Customization.Feet = _weightedRandomHelper.GetWeightedValue<string>(appearance.Feet);
|
||||
bot.Customization.Body = _weightedRandomHelper.GetWeightedValue<string>(appearance.Body);
|
||||
|
||||
var bodyGlobalDictDb = _databaseService.GetGlobals().Configuration.Customization.Body;
|
||||
var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body];
|
||||
|
||||
// Some bodies have matching hands, look up body to see if this is the case
|
||||
var chosenBody = bodyGlobalDictDb[chosenBodyTemplate?.Name.Trim()];
|
||||
bot.Customization.Hands = chosenBody?.IsNotRandom ?? false
|
||||
? chosenBody.Hands // Has fixed hands for chosen body, update to match
|
||||
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -441,7 +507,8 @@ public class BotGenerator
|
||||
/// <param name="output">Generated bot array, ready to send to client</param>
|
||||
public void LogPmcGeneratedCount(List<BotBase> output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var pmcCount = output.Aggregate(0, (acc, cur) => { return cur.Info.Side == "Bear" || cur.Info.Side == "Usec" ? acc + 1 : acc; });
|
||||
_logger.Debug($"Generated {output.Count} total bots. Replaced ${pmcCount} with PMCs");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -452,7 +519,105 @@ public class BotGenerator
|
||||
/// <returns>Health object</returns>
|
||||
public BotBaseHealth GenerateHealth(BotTypeHealth healthObj, bool playerScav = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var bodyParts = playerScav
|
||||
? GetLowestHpBody(healthObj.BodyParts)
|
||||
: _randomUtil.GetArrayValue(healthObj.BodyParts);
|
||||
|
||||
BotBaseHealth health = new()
|
||||
{
|
||||
Hydration = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Hydration.Min, (int)healthObj.Hydration.Max),
|
||||
Maximum = healthObj.Hydration.Max
|
||||
},
|
||||
Energy = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Energy.Min, (int)healthObj.Energy.Max),
|
||||
Maximum = healthObj.Energy.Max
|
||||
},
|
||||
Temperature = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Temperature.Min, (int)healthObj.Temperature.Max),
|
||||
Maximum = healthObj.Temperature.Max
|
||||
},
|
||||
BodyParts = new Dictionary<string, BodyPartHealth>()
|
||||
{
|
||||
{
|
||||
"Head", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Head.Min, (int)bodyParts.Head.Max),
|
||||
Maximum = Math.Round(bodyParts.Head.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Chest", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Chest.Min, (int)bodyParts.Chest.Max),
|
||||
Maximum = Math.Round(bodyParts.Chest.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Stomach", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Stomach.Min, (int)bodyParts.Stomach.Max),
|
||||
Maximum = Math.Round(bodyParts.Stomach.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LeftArm", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.LeftArm.Min, (int)bodyParts.LeftArm.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftArm.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RightArm", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.RightArm.Min, (int)bodyParts.RightArm.Max),
|
||||
Maximum = Math.Round(bodyParts.RightArm.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LeftLeg", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.LeftLeg.Min, (int)bodyParts.LeftLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftLeg.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RightLeg", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.RightLeg.Min, (int)bodyParts.RightLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.RightLeg.Max ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdateTime = _timeUtil.GetTimeStamp(),
|
||||
Immortal = false
|
||||
};
|
||||
|
||||
return health;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -460,9 +625,33 @@ public class BotGenerator
|
||||
/// </summary>
|
||||
/// <param name="bodies">Body parts to sum up</param>
|
||||
/// <returns>Lowest hp collection</returns>
|
||||
public BodyPart? GetLowestHpBody(List<BodyPart> bodies) // TODO: there are two types of body parts
|
||||
public BodyPart? GetLowestHpBody(List<BodyPart> bodies)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (bodies.Count == 0)
|
||||
return null;
|
||||
|
||||
BodyPart result = new();
|
||||
var props = result.GetType().GetProperties();
|
||||
double? currentHighest = double.MaxValue;
|
||||
foreach (var bodyPart in bodies)
|
||||
{
|
||||
double? hpTotal = 0;
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var value = (MinMax)prop.GetValue(bodyPart);
|
||||
hpTotal += value.Max;
|
||||
}
|
||||
|
||||
if (hpTotal < currentHighest)
|
||||
{
|
||||
// Found collection with lower value that previous, use it
|
||||
currentHighest = hpTotal;
|
||||
result = bodyPart;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -472,7 +661,14 @@ public class BotGenerator
|
||||
/// <returns>Skills</returns>
|
||||
public Skills GenerateSkills(BotDbSkills botSkills)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var skillsToReturn = new Skills
|
||||
{
|
||||
Common = GetSkillsWithRandomisedProgressValue(botSkills.Common, true),
|
||||
Mastering = GetSkillsWithRandomisedProgressValue(botSkills.Mastering, false),
|
||||
Points = 0
|
||||
};
|
||||
|
||||
return skillsToReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -481,19 +677,49 @@ public class BotGenerator
|
||||
/// <param name="skills">Skills to randomise</param>
|
||||
/// <param name="isCommonSkills">Are the skills 'common' skills</param>
|
||||
/// <returns>Skills with randomised progress values as an array</returns>
|
||||
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, BaseSkill> skills, bool isCommonSkills)
|
||||
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, MinMax> skills, bool isCommonSkills)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!skills.Any())
|
||||
return new List<BaseSkill>();
|
||||
|
||||
return skills.Select(kvp =>
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All skills have id and progress props
|
||||
var skillToAdd = new BaseSkill
|
||||
{
|
||||
Id = kvp.Key,
|
||||
Progress = _randomUtil.GetInt((int)skill.Min, (int)skill.Max)
|
||||
};
|
||||
|
||||
// Common skills have additional props
|
||||
if (isCommonSkills)
|
||||
{
|
||||
((Common)skillToAdd).PointsEarnedDuringSession = 0;
|
||||
((Common)skillToAdd).LastAccess = 0;
|
||||
}
|
||||
|
||||
return skillToAdd;
|
||||
}).Where(baseSkill => baseSkill != null).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an id+aid for a bot and apply
|
||||
/// </summary>
|
||||
/// <param name="bot">bot to update</param>
|
||||
/// <returns>updated IBotBase object</returns> // TODO: Node server claims this in summary but is void
|
||||
/// <returns></returns>
|
||||
public void AddIdsToBot(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botId = _hashUtil.Generate();
|
||||
|
||||
bot.Id = botId;
|
||||
bot.Aid = _hashUtil.GenerateAccountId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -503,7 +729,30 @@ public class BotGenerator
|
||||
/// <param name="profile">Profile to update</param>
|
||||
public void GenerateInventoryId(BotBase profile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var newInventoryItemId = _hashUtil.Generate();
|
||||
|
||||
foreach (var item in profile.Inventory.Items) {
|
||||
// Root item found, update its _id value to newly generated id
|
||||
if (item.Template == ItemTpl.INVENTORY_DEFAULT) {
|
||||
item.Id = newInventoryItemId;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Optimisation - skip items without a parentId
|
||||
// They are never linked to root inventory item + we already handled root item above
|
||||
if (item.ParentId is null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item is a child of root inventory item, update its parentId value to newly generated id
|
||||
if (item.ParentId == profile.Inventory.Equipment) {
|
||||
item.ParentId = newInventoryItemId;
|
||||
}
|
||||
}
|
||||
|
||||
// Update inventory equipment id to new one we generated
|
||||
profile.Inventory.Equipment = newInventoryItemId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -513,19 +762,57 @@ public class BotGenerator
|
||||
/// </summary>
|
||||
/// <param name="botInfo">bot info object to update</param>
|
||||
/// <returns>Chosen game version</returns>
|
||||
public string SetRandomisedGameVersionAndCategory(Info botInfo) // TODO: there are two types of Info
|
||||
public string SetRandomisedGameVersionAndCategory(Info botInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Special case
|
||||
if (botInfo.Nickname?.ToLower() == "nikita") {
|
||||
botInfo.GameVersion = GameEditions.UNHEARD;
|
||||
botInfo.MemberCategory = MemberCategory.DEVELOPER;
|
||||
|
||||
return botInfo.GameVersion;
|
||||
}
|
||||
|
||||
// Choose random weighted game version for bot
|
||||
botInfo.GameVersion = _weightedRandomHelper.GetWeightedValue(_pmcConfig.GameVersionWeight);
|
||||
|
||||
// Choose appropriate member category value
|
||||
switch (botInfo.GameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
botInfo.MemberCategory = MemberCategory.UNIQUE_ID;
|
||||
break;
|
||||
case GameEditions.UNHEARD:
|
||||
botInfo.MemberCategory = MemberCategory.UNHEARD;
|
||||
break;
|
||||
default:
|
||||
// Everyone else gets a weighted randomised category
|
||||
botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue<MemberCategory>(_pmcConfig.AccountTypeWeight);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure selected category matches
|
||||
botInfo.SelectedMemberCategory = botInfo.MemberCategory;
|
||||
|
||||
return botInfo.GameVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a side-specific (usec/bear) dogtag item to a bots inventory
|
||||
/// </summary>
|
||||
/// <param name="bot">bot to add dogtag to</param>
|
||||
/// <returns>Bot with dogtag added</returns> // TODO: Node server claims this in summary but is void
|
||||
/// <returns></returns>
|
||||
public void AddDogtagToBot(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Item inventoryItem = new () {
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion),
|
||||
ParentId = bot.Inventory.Equipment,
|
||||
SlotId = "Dogtag",
|
||||
Upd = new () {
|
||||
SpawnedInSession = true,
|
||||
},
|
||||
};
|
||||
|
||||
bot.Inventory.Items.Add(inventoryItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -536,7 +823,25 @@ public class BotGenerator
|
||||
/// <returns>item tpl</returns>
|
||||
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (side.ToLower() == "usec") {
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_TUE;
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC;
|
||||
}
|
||||
}
|
||||
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_TUE;
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -545,6 +850,9 @@ public class BotGenerator
|
||||
/// <param name="bot">Pmc object to adjust</param>
|
||||
public void SetPmcPocketsByGameVersion(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (bot.Info.GameVersion == GameEditions.UNHEARD) {
|
||||
var pockets = bot.Inventory.Items.FirstOrDefault((item) => item.SlotId == "Pockets");
|
||||
pockets.Template = ItemTpl.POCKETS_1X4_TUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ public class ProfileHelper
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
/// <param name="skill">Skill to look up and return value from</param>
|
||||
/// <returns>Common skill object from desired profile</returns>
|
||||
public Common? GetSkillFromProfile(PmcData pmcData, SkillTypes skill)
|
||||
public BaseSkill? GetSkillFromProfile(PmcData pmcData, SkillTypes skill)
|
||||
{
|
||||
var skillToReturn = pmcData?.Skills?.Common.FirstOrDefault(s => s.Id == skill.ToString());
|
||||
if (skillToReturn == null)
|
||||
|
||||
@@ -300,15 +300,17 @@ public class BaseJsonSkills
|
||||
|
||||
public class Skills
|
||||
{
|
||||
public List<Common>? Common { get; set; }
|
||||
public List<BaseSkill>? Common { get; set; }
|
||||
|
||||
public List<Mastering>? Mastering { get; set; }
|
||||
public List<BaseSkill>? Mastering { get; set; }
|
||||
|
||||
public double? Points { get; set; }
|
||||
}
|
||||
|
||||
public class BaseSkill
|
||||
{
|
||||
public int? PointsEarnedDuringSession { get; set; }
|
||||
public long? LastAccess { get; set; }
|
||||
public string? Id { get; set; }
|
||||
public double? Progress { get; set; }
|
||||
|
||||
@@ -321,8 +323,7 @@ public class BaseSkill
|
||||
|
||||
public class Common : BaseSkill
|
||||
{
|
||||
public int? PointsEarnedDuringSession { get; set; }
|
||||
public long? LastAccess { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class Mastering : BaseSkill
|
||||
|
||||
@@ -12,11 +12,11 @@ public class PmcConfig : BaseConfig
|
||||
|
||||
/** What game version should the PMC have */
|
||||
[JsonPropertyName("gameVersionWeight")]
|
||||
public Dictionary<string, double> GameVersionWeight { get; set; }
|
||||
public Dictionary<string, int> GameVersionWeight { get; set; }
|
||||
|
||||
/** What account type should the PMC have */
|
||||
[JsonPropertyName("accountTypeWeight")]
|
||||
public Dictionary<MemberCategory, double> AccountTypeWeight { get; set; }
|
||||
public Dictionary<MemberCategory, int> AccountTypeWeight { get; set; }
|
||||
|
||||
/** Global whitelist/blacklist of vest loot for PMCs */
|
||||
[JsonPropertyName("vestLoot")]
|
||||
|
||||
@@ -89,4 +89,9 @@ public class ItemFilterService
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static bool IsLootableItemBlacklisted(string itemKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
+17
-13
@@ -10,8 +10,10 @@ public class RandomUtil
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RandomUtil(
|
||||
ILogger logger)
|
||||
public RandomUtil
|
||||
(
|
||||
ILogger logger
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -348,10 +350,11 @@ public class RandomUtil
|
||||
* A shift that is equal to the available range only has a 50% chance of rolling correctly, theoretically halving performance.
|
||||
* Shifting even further drops the success chance very rapidly - so we want to warn against that
|
||||
**/
|
||||
_logger.Warning("Bias shift for random number generation is greater than the range of available numbers. This will have a severe performance impact");
|
||||
_logger.Warning($"min-> { min}; max-> { max}; shift-> { shift}");
|
||||
_logger.Warning(
|
||||
"Bias shift for random number generation is greater than the range of available numbers. This will have a severe performance impact");
|
||||
_logger.Warning($"min-> {min}; max-> {max}; shift-> {shift}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
var biasedMin = shift >= 0 ? min - shift : min;
|
||||
var biasedMax = shift < 0 ? max + shift : max;
|
||||
@@ -373,20 +376,21 @@ public class RandomUtil
|
||||
private double GetGaussianRandom(double n)
|
||||
{
|
||||
var rand = 0d;
|
||||
for (var i = 0; i<n; i += 1)
|
||||
for (var i = 0; i < n; i += 1)
|
||||
{
|
||||
rand += GetSecureRandomNumber();
|
||||
}
|
||||
|
||||
return rand / n;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuffles a list in place using the Fisher-Yates algorithm.
|
||||
/// </summary>
|
||||
/// <param name="originalList">The list to shuffle.</param>
|
||||
/// <typeparam name="T">The type of elements in the list.</typeparam>
|
||||
/// <returns>The shuffled list.</returns>
|
||||
public List<T> Shuffle<T>(List<T> originalList)
|
||||
/// <summary>
|
||||
/// Shuffles a list in place using the Fisher-Yates algorithm.
|
||||
/// </summary>
|
||||
/// <param name="originalList">The list to shuffle.</param>
|
||||
/// <typeparam name="T">The type of elements in the list.</typeparam>
|
||||
/// <returns>The shuffled list.</returns>
|
||||
public List<T> Shuffle<T>(List<T> originalList)
|
||||
{
|
||||
var currentIndex = originalList.Count;
|
||||
|
||||
|
||||
@@ -5,52 +5,52 @@ namespace UnitTests.Tests.Utils;
|
||||
[TestClass]
|
||||
public class HashUtilTests
|
||||
{
|
||||
private readonly HashUtil _hashUtil = new(new RandomUtil());
|
||||
|
||||
[TestMethod]
|
||||
public void GenerateTest()
|
||||
{
|
||||
// Generate 100 MongoId's
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
// Invalid mongoId character
|
||||
var result = _hashUtil.Generate();
|
||||
|
||||
// Invalid mongoId length
|
||||
var test = _hashUtil.IsValidMongoId(result);
|
||||
|
||||
Assert.AreEqual(
|
||||
true,
|
||||
test,
|
||||
$"IsValidMongoId() `{result}` is not a valid MongoId.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidMongoIdTest()
|
||||
{
|
||||
// Invalid mongoId character
|
||||
var ResultBadChar = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbz");
|
||||
|
||||
Assert.AreEqual(
|
||||
false,
|
||||
ResultBadChar,
|
||||
"IsValidMongoId() `677ddb67406e9918a0264bbz` contains invalid char `z`, but result was true");
|
||||
|
||||
// Invalid mongoId length
|
||||
var resultBadLength = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbcc");
|
||||
|
||||
Assert.AreEqual(
|
||||
false,
|
||||
resultBadLength,
|
||||
"IsValidMongoId() `677ddb67406e9918a0264bbcc` is 25 characters, but result was true");
|
||||
|
||||
// Valid mongoId
|
||||
var resultPass = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbc");
|
||||
|
||||
Assert.AreEqual(
|
||||
true,
|
||||
resultPass,
|
||||
"IsValidMongoId() `677ddb67406e9918a0264bbc` is a valid mongoId, but result was false");
|
||||
}
|
||||
}
|
||||
// private readonly HashUtil _hashUtil = new(new RandomUtil());
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void GenerateTest()
|
||||
// {
|
||||
// // Generate 100 MongoId's
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// // Invalid mongoId character
|
||||
// var result = _hashUtil.Generate();
|
||||
//
|
||||
// // Invalid mongoId length
|
||||
// var test = _hashUtil.IsValidMongoId(result);
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// true,
|
||||
// test,
|
||||
// $"IsValidMongoId() `{result}` is not a valid MongoId.");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void IsValidMongoIdTest()
|
||||
// {
|
||||
// // Invalid mongoId character
|
||||
// var ResultBadChar = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbz");
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// false,
|
||||
// ResultBadChar,
|
||||
// "IsValidMongoId() `677ddb67406e9918a0264bbz` contains invalid char `z`, but result was true");
|
||||
//
|
||||
// // Invalid mongoId length
|
||||
// var resultBadLength = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbcc");
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// false,
|
||||
// resultBadLength,
|
||||
// "IsValidMongoId() `677ddb67406e9918a0264bbcc` is 25 characters, but result was true");
|
||||
//
|
||||
// // Valid mongoId
|
||||
// var resultPass = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbc");
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// true,
|
||||
// resultPass,
|
||||
// "IsValidMongoId() `677ddb67406e9918a0264bbc` is a valid mongoId, but result was false");
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -5,179 +5,179 @@ namespace UnitTests.Tests.Utils;
|
||||
[TestClass]
|
||||
public sealed class RandomUtilTests
|
||||
{
|
||||
private readonly RandomUtil _randomUtil = new();
|
||||
|
||||
[TestMethod]
|
||||
public void GetIntTest()
|
||||
{
|
||||
// Run 100 test cases
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.GetInt(0, 10);
|
||||
|
||||
if (result < 0 || result > 10)
|
||||
{
|
||||
Assert.Fail($"GetInt(0, 10) out of range. Expected range [0, 10] but was {result}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetIntExTest()
|
||||
{
|
||||
// Run 100 test cases
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.GetIntEx(10);
|
||||
|
||||
if (result < 1 || result > 9)
|
||||
{
|
||||
Assert.Fail($"GetInt(10) out of range. Expected range [1, 9] but was {result}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetFloatTest()
|
||||
{
|
||||
// Run 100 test cases
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.GetFloat(0f, 10f);
|
||||
|
||||
if (result < 0f || result >= 9f)
|
||||
{
|
||||
Assert.Fail($"GetFloat(0f, 10f) out of range. Expected range [0.0f, 9.999f] but was {result}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetPercentOfValueTest()
|
||||
{
|
||||
const float expected = 45.5f;
|
||||
var result = _randomUtil.GetPercentOfValue(45.5f, 100f);
|
||||
|
||||
Assert.AreEqual(
|
||||
expected,
|
||||
result,
|
||||
0.0001f,
|
||||
$"GetPercentOfValue(45.5f, 100f) out of range. Expected: {expected}. Actual: {result}.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReduceValueByPercentTest()
|
||||
{
|
||||
const float expected = 54.5f;
|
||||
var result = _randomUtil.ReduceValueByPercent(100f, 45.5f);
|
||||
|
||||
Assert.AreEqual(
|
||||
expected,
|
||||
result,
|
||||
0.0001f,
|
||||
$"ReduceValueByPercent(100f, 45.5f) out of range. Expected: {expected}. Actual: {result}.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetChance100Test()
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
const bool expectedTrue = true;
|
||||
var resultTrue = _randomUtil.GetChance100(100f);
|
||||
|
||||
Assert.AreEqual(
|
||||
expectedTrue,
|
||||
resultTrue,
|
||||
$"GetChance100(100f) out of range. Expected: {expectedTrue}. Actual: {resultTrue}.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
const bool expectedFalse = false;
|
||||
var resultFalse = _randomUtil.GetChance100(0f);
|
||||
|
||||
Assert.AreEqual(
|
||||
expectedFalse,
|
||||
resultFalse,
|
||||
$"GetChance100(0f) out of range. Expected: {expectedFalse}. Actual: {resultFalse}.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Missing methods between these two
|
||||
|
||||
[TestMethod]
|
||||
public void RandIntTest()
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.RandInt(0, 10);
|
||||
|
||||
if (result < 0 || result > 9)
|
||||
{
|
||||
Assert.Fail($"RandInt(0, 10) out of range. Expected range [0, 9] but was {result}.");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.RandInt(10);
|
||||
|
||||
if (result < 0 || result > 9)
|
||||
{
|
||||
Assert.Fail($"RandInt(10, null) out of range. Expected range [0, 9] but was {result}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RandNumTest()
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.RandNum(0, 10);
|
||||
|
||||
if (result < 0 || result > 9)
|
||||
{
|
||||
Assert.Fail($"RandNum(0, 10) out of range. Expected range [0, 9.999d] but was {result}.");
|
||||
}
|
||||
|
||||
if (_randomUtil.GetNumberPrecision(result) > RandomUtil.MaxSignificantDigits)
|
||||
{
|
||||
Assert.Fail($"RandNum(0, 10) precision of {result} exceeds the allowable precision ({RandomUtil.MaxSignificantDigits}) for the given values.");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _randomUtil.RandNum(10);
|
||||
|
||||
if (result < 0 || result > 9)
|
||||
{
|
||||
Assert.Fail($"RandNum(10) out of range. Expected range [0, 9.999d] but was {result}.");
|
||||
}
|
||||
|
||||
if (_randomUtil.GetNumberPrecision(result) > RandomUtil.MaxSignificantDigits)
|
||||
{
|
||||
Assert.Fail($"RandNum(10) precision of {result} exceeds the allowable precision ({RandomUtil.MaxSignificantDigits}) for the given values.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShuffleTest()
|
||||
{
|
||||
var testList = new List<int>()
|
||||
{
|
||||
1,2,3,4,5,6,7,8,9,10
|
||||
};
|
||||
|
||||
var orig = new List<int>(testList);
|
||||
|
||||
var result = _randomUtil.Shuffle(testList);
|
||||
|
||||
Assert.IsFalse(
|
||||
result.SequenceEqual(orig),
|
||||
$"Shuffle test failed. Expected: {string.Join(", ", orig)}, but got {string.Join(", ", result)}");
|
||||
}
|
||||
}
|
||||
// private readonly RandomUtil _randomUtil = new();
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void GetIntTest()
|
||||
// {
|
||||
// // Run 100 test cases
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.GetInt(0, 10);
|
||||
//
|
||||
// if (result < 0 || result > 10)
|
||||
// {
|
||||
// Assert.Fail($"GetInt(0, 10) out of range. Expected range [0, 10] but was {result}.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void GetIntExTest()
|
||||
// {
|
||||
// // Run 100 test cases
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.GetIntEx(10);
|
||||
//
|
||||
// if (result < 1 || result > 9)
|
||||
// {
|
||||
// Assert.Fail($"GetInt(10) out of range. Expected range [1, 9] but was {result}.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void GetFloatTest()
|
||||
// {
|
||||
// // Run 100 test cases
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.GetFloat(0f, 10f);
|
||||
//
|
||||
// if (result < 0f || result >= 9f)
|
||||
// {
|
||||
// Assert.Fail($"GetFloat(0f, 10f) out of range. Expected range [0.0f, 9.999f] but was {result}.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void GetPercentOfValueTest()
|
||||
// {
|
||||
// const float expected = 45.5f;
|
||||
// var result = _randomUtil.GetPercentOfValue(45.5f, 100f);
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// expected,
|
||||
// result,
|
||||
// 0.0001f,
|
||||
// $"GetPercentOfValue(45.5f, 100f) out of range. Expected: {expected}. Actual: {result}.");
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void ReduceValueByPercentTest()
|
||||
// {
|
||||
// const float expected = 54.5f;
|
||||
// var result = _randomUtil.ReduceValueByPercent(100f, 45.5f);
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// expected,
|
||||
// result,
|
||||
// 0.0001f,
|
||||
// $"ReduceValueByPercent(100f, 45.5f) out of range. Expected: {expected}. Actual: {result}.");
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void GetChance100Test()
|
||||
// {
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// const bool expectedTrue = true;
|
||||
// var resultTrue = _randomUtil.GetChance100(100f);
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// expectedTrue,
|
||||
// resultTrue,
|
||||
// $"GetChance100(100f) out of range. Expected: {expectedTrue}. Actual: {resultTrue}.");
|
||||
// }
|
||||
//
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// const bool expectedFalse = false;
|
||||
// var resultFalse = _randomUtil.GetChance100(0f);
|
||||
//
|
||||
// Assert.AreEqual(
|
||||
// expectedFalse,
|
||||
// resultFalse,
|
||||
// $"GetChance100(0f) out of range. Expected: {expectedFalse}. Actual: {resultFalse}.");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // TODO: Missing methods between these two
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void RandIntTest()
|
||||
// {
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.RandInt(0, 10);
|
||||
//
|
||||
// if (result < 0 || result > 9)
|
||||
// {
|
||||
// Assert.Fail($"RandInt(0, 10) out of range. Expected range [0, 9] but was {result}.");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.RandInt(10);
|
||||
//
|
||||
// if (result < 0 || result > 9)
|
||||
// {
|
||||
// Assert.Fail($"RandInt(10, null) out of range. Expected range [0, 9] but was {result}.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void RandNumTest()
|
||||
// {
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.RandNum(0, 10);
|
||||
//
|
||||
// if (result < 0 || result > 9)
|
||||
// {
|
||||
// Assert.Fail($"RandNum(0, 10) out of range. Expected range [0, 9.999d] but was {result}.");
|
||||
// }
|
||||
//
|
||||
// if (_randomUtil.GetNumberPrecision(result) > RandomUtil.MaxSignificantDigits)
|
||||
// {
|
||||
// Assert.Fail($"RandNum(0, 10) precision of {result} exceeds the allowable precision ({RandomUtil.MaxSignificantDigits}) for the given values.");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (var i = 0; i < 100; i++)
|
||||
// {
|
||||
// var result = _randomUtil.RandNum(10);
|
||||
//
|
||||
// if (result < 0 || result > 9)
|
||||
// {
|
||||
// Assert.Fail($"RandNum(10) out of range. Expected range [0, 9.999d] but was {result}.");
|
||||
// }
|
||||
//
|
||||
// if (_randomUtil.GetNumberPrecision(result) > RandomUtil.MaxSignificantDigits)
|
||||
// {
|
||||
// Assert.Fail($"RandNum(10) precision of {result} exceeds the allowable precision ({RandomUtil.MaxSignificantDigits}) for the given values.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [TestMethod]
|
||||
// public void ShuffleTest()
|
||||
// {
|
||||
// var testList = new List<int>()
|
||||
// {
|
||||
// 1,2,3,4,5,6,7,8,9,10
|
||||
// };
|
||||
//
|
||||
// var orig = new List<int>(testList);
|
||||
//
|
||||
// var result = _randomUtil.Shuffle(testList);
|
||||
//
|
||||
// Assert.IsFalse(
|
||||
// result.SequenceEqual(orig),
|
||||
// $"Shuffle test failed. Expected: {string.Join(", ", orig)}, but got {string.Join(", ", result)}");
|
||||
// }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user