diff --git a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs index d14809fc..cd921be3 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Frozen; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Constants; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; @@ -47,19 +48,21 @@ public class BotGeneratorHelper( /// /// Item extra properties are being generated for /// Used by weapons to randomize the durability values. Null for non-equipped items + /// Force property on item /// Item Upd object with extra properties - public Upd GenerateExtraPropertiesForItem(TemplateItem? itemTemplate, string? botRole = null) + public Upd? GenerateExtraPropertiesForItem(TemplateItem? itemTemplate, string? botRole = null, bool forceStackObjectsCount = false) { // Get raid settings, if no raid, default to day var raidSettings = profileActivityService.GetFirstProfileActivityRaidData()?.RaidConfiguration; + // BotRole property exists, we have specific bot randomisation values to make use of RandomisedResourceDetails? randomisationSettings = null; if (botRole is not null) { BotConfig.LootItemResourceRandomization.TryGetValue(botRole, out randomisationSettings); } - Upd itemProperties = new(); + Upd itemUpd = new(); var hasProperties = false; if (itemTemplate?.Properties?.MaxDurability is not null && itemTemplate.Properties.MaxDurability > 0) @@ -67,32 +70,32 @@ public class BotGeneratorHelper( if (itemTemplate.Properties.WeapClass is not null) { // Is weapon - itemProperties.Repairable = GenerateWeaponRepairableProperties(itemTemplate, botRole); + itemUpd.Repairable = GenerateWeaponRepairableProperties(itemTemplate, botRole); hasProperties = true; } else if (itemTemplate.Properties.ArmorClass is not null) { // Is armor - itemProperties.Repairable = GenerateArmorRepairableProperties(itemTemplate, botRole); + itemUpd.Repairable = GenerateArmorRepairableProperties(itemTemplate, botRole); hasProperties = true; } } if (itemTemplate?.Properties?.HasHinge ?? false) { - itemProperties.Togglable = new UpdTogglable { On = true }; + itemUpd.Togglable = new UpdTogglable { On = true }; hasProperties = true; } if (itemTemplate?.Properties?.Foldable ?? false) { - itemProperties.Foldable = new UpdFoldable { Folded = false }; + itemUpd.Foldable = new UpdFoldable { Folded = false }; hasProperties = true; } if (itemTemplate?.Properties?.WeapFireType?.Count == 0) { - itemProperties.FireMode = itemTemplate.Properties.WeapFireType.Contains("fullauto") + itemUpd.FireMode = itemTemplate.Properties.WeapFireType.Contains("fullauto") ? new UpdFireMode { FireMode = "fullauto" } : new UpdFireMode { FireMode = randomUtil.GetArrayValue(itemTemplate.Properties.WeapFireType) }; hasProperties = true; @@ -100,7 +103,7 @@ public class BotGeneratorHelper( if (itemTemplate?.Properties?.MaxHpResource is not null) { - itemProperties.MedKit = new UpdMedKit + itemUpd.MedKit = new UpdMedKit { HpResource = GetRandomizedResourceValue(itemTemplate.Properties.MaxHpResource ?? 0, randomisationSettings?.Meds), }; @@ -109,7 +112,7 @@ public class BotGeneratorHelper( if (itemTemplate?.Properties?.MaxResource is not null && itemTemplate.Properties?.FoodUseTime is not null) { - itemProperties.FoodDrink = new UpdFoodDrink + itemUpd.FoodDrink = new UpdFoodDrink { HpPercent = GetRandomizedResourceValue(itemTemplate.Properties.MaxResource ?? 0, randomisationSettings?.Food), }; @@ -124,7 +127,7 @@ public class BotGeneratorHelper( ? equipmentSettings?.LightIsActiveNightChancePercent ?? 50 : equipmentSettings?.LightIsActiveDayChancePercent ?? 25; - itemProperties.Light = new UpdLight { IsActive = randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0 }; + itemUpd.Light = new UpdLight { IsActive = randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0 }; hasProperties = true; } else if (itemTemplate?.Parent == BaseClasses.TACTICAL_COMBO) @@ -132,7 +135,7 @@ public class BotGeneratorHelper( // Get chance from botconfig for bot type, use 50% if no value found var lightLaserActiveChance = equipmentSettings?.LaserIsActiveChancePercent ?? 50; - itemProperties.Light = new UpdLight { IsActive = randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0 }; + itemUpd.Light = new UpdLight { IsActive = randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0 }; hasProperties = true; } @@ -144,7 +147,7 @@ public class BotGeneratorHelper( ? equipmentSettings?.NvgIsActiveChanceNightPercent ?? 90 : equipmentSettings?.NvgIsActiveChanceDayPercent ?? 15; - itemProperties.Togglable = new UpdTogglable { On = randomUtil.GetChance100(nvgActiveChance) }; + itemUpd.Togglable = new UpdTogglable { On = randomUtil.GetChance100(nvgActiveChance) }; hasProperties = true; } @@ -152,12 +155,18 @@ public class BotGeneratorHelper( if ((itemTemplate?.Properties?.HasHinge ?? false) && (itemTemplate.Properties.FaceShieldComponent ?? false)) { var faceShieldActiveChance = equipmentSettings?.FaceShieldIsActiveChancePercent ?? 75; - itemProperties.Togglable = new UpdTogglable { On = randomUtil.GetChance100(faceShieldActiveChance) }; + itemUpd.Togglable = new UpdTogglable { On = randomUtil.GetChance100(faceShieldActiveChance) }; hasProperties = true; } - // Get chance from botconfig for bot type, use 75% if no value found - return hasProperties ? itemProperties : null; + if (forceStackObjectsCount) + { + // Ensure property is set + itemUpd.StackObjectsCount ??= 1; + } + + // Some items (weapon mods) may not have any props, and we don't want an empty Upd object + return hasProperties ? itemUpd : null; } /// @@ -168,17 +177,22 @@ public class BotGeneratorHelper( /// Randomized value from maxHpResource protected double GetRandomizedResourceValue(double maxResource, RandomisedResourceValues? randomizationValues) { - if (randomizationValues is null) + if (randomizationValues is null || randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent)) { return maxResource; } - if (randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent)) + if (maxResource.Approx(1)) { - return maxResource; + return 1; } - return randomUtil.GetDouble(randomUtil.GetPercentOfValue(randomizationValues.ResourcePercent, maxResource, 0), maxResource); + var min = randomUtil.GetPercentOfValue(randomizationValues.ResourcePercent, maxResource, 0); + + // Using food at 0 causes client to error + var clampedMin = Math.Clamp(min, 1, min); + + return randomUtil.GetDouble(clampedMin, maxResource); } ///