Added implementation of GenerateModsForEquipment

This commit is contained in:
Chomp
2025-01-15 20:14:04 +00:00
parent 063013927c
commit 25c9570859
3 changed files with 429 additions and 91 deletions
+370 -34
View File
@@ -1,19 +1,65 @@
using Core.Annotations;
using Core.Annotations;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Enums;
using Core.Models.Spt.Bots;
using Core.Models.Spt.Config;
using Core.Models.Utils;
using Core.Helpers;
using Core.Models.Spt.Server;
using Core.Servers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
using System.Collections.Generic;
namespace Core.Generators;
[Injectable]
public class BotEquipmentModGenerator
{
private readonly ISptLogger<BotEquipmentModGenerator> _logger;
private readonly HashUtil _hashUtil;
private readonly RandomUtil _randomUtil;
private readonly ItemHelper _itemHelper;
private readonly BotGeneratorHelper _botGeneratorHelper;
private readonly BotEquipmentModPoolService _botEquipmentModPoolService;
private readonly PresetHelper _presetHelper;
private readonly ProbabilityHelper _probabilityHelper;
private readonly LocalisationService _localisationService;
private readonly ItemFilterService _itemFilterService;
private readonly ConfigServer _configServer;
private readonly ICloner _cloner;
private BotConfig _botConfig;
public BotEquipmentModGenerator()
public BotEquipmentModGenerator(
ISptLogger<BotEquipmentModGenerator> logger,
HashUtil hashUtil,
RandomUtil randomUtil,
ItemHelper itemHelper,
BotGeneratorHelper botGeneratorHelper,
BotEquipmentModPoolService botEquipmentModPoolService,
PresetHelper presetHelper,
ProbabilityHelper probabilityHelper,
LocalisationService localisationService,
ItemFilterService itemFilterService,
ConfigServer configServer,
ICloner cloner)
{
_logger = logger;
_hashUtil = hashUtil;
_randomUtil = randomUtil;
_itemHelper = itemHelper;
_botGeneratorHelper = botGeneratorHelper;
_botEquipmentModPoolService = botEquipmentModPoolService;
_presetHelper = presetHelper;
_probabilityHelper = probabilityHelper;
_localisationService = localisationService;
_itemFilterService = itemFilterService;
_configServer = configServer;
_cloner = cloner;
_botConfig = _configServer.GetConfig<BotConfig>();
}
/// <summary>
@@ -26,10 +72,153 @@ public class BotEquipmentModGenerator
/// <param name="specificBlacklist">The relevant blacklist from bot.json equipment dictionary</param>
/// <param name="shouldForceSpawn">should this mod be forced to spawn</param>
/// <returns>Item + compatible mods as an array</returns>
public Item GenerateModsForEquipment(List<Item> equipment, string parentId, TemplateItem parentTemplate, GenerateEquipmentProperties settings,
public List<Item> GenerateModsForEquipment(List<Item> equipment, string parentId, TemplateItem parentTemplate, GenerateEquipmentProperties settings,
EquipmentFilterDetails specificBlacklist, bool shouldForceSpawn = false)
{
throw new NotImplementedException();
var forceSpawn = shouldForceSpawn;
// Get mod pool for the desired item
var compatibleModsPool = settings.ModPool[parentTemplate.Id];
if (compatibleModsPool is null)
{
_logger.Warning($"bot: { settings.BotData.Role} lacks a mod slot pool for item: { parentTemplate.Id} { parentTemplate.Name}");
}
// Iterate over mod pool and choose mods to add to item
foreach (var modSlotKvP in compatibleModsPool)
{
var modSlotName = modSlotKvP.Key;
// Get the templates slot object from db
var itemSlotTemplate = GetModItemSlotFromDb(modSlotName, parentTemplate);
if (itemSlotTemplate is null)
{
_logger.Error(_localisationService.GetText("bot-mod_slot_missing_from_item", new {
modSlot = modSlotName,
parentId= parentTemplate.Id,
parentName= parentTemplate.Name,
botRole= settings.BotData.Role
}));
continue;
}
var modSpawnResult = ShouldModBeSpawned(
itemSlotTemplate,
modSlotName,
settings.SpawnChances.EquipmentModsChances,
settings.BotEquipmentConfig);
// Rolled to skip mod and it shouldnt be force-spawned
if (modSpawnResult == ModSpawn.SKIP && !forceSpawn)
{
continue;
}
// Ensure submods for nvgs all spawn together
if (modSlotName == "mod_nvg")
{
forceSpawn = true;
}
// Get pool of items we can add for this slot
var modPoolToChooseFrom = modSlotKvP.Value;
// Filter the pool of items in blacklist
var filteredModPool = FilterModsByBlacklist(modPoolToChooseFrom, specificBlacklist, modSlotName);
if (filteredModPool.Count > 0)
{
// use filtered pool as it has items in it
modPoolToChooseFrom = filteredModPool;
}
// Slot can hold armor plates + we are filtering possible items by bot level, handle
if (
settings.BotEquipmentConfig.FilterPlatesByLevel.GetValueOrDefault(false) &&
_itemHelper.IsRemovablePlateSlot(modSlotName.ToLower())
)
{
var plateSlotFilteringOutcome = FilterPlateModsForSlotByLevel(
settings,
modSlotName.ToLower(),
compatibleModsPool[modSlotName],
parentTemplate);
if (plateSlotFilteringOutcome.Result is Result.UNKNOWN_FAILURE or Result.NO_DEFAULT_FILTER)
{
_logger.Debug($"Plate slot: {modSlotName} selection for armor: {parentTemplate.Id} failed: {plateSlotFilteringOutcome.Result}, skipping");
continue;
}
if (plateSlotFilteringOutcome.Result == Result.LACKS_PLATE_WEIGHTS)
{
_logger.Warning($"Plate slot: {modSlotName} lacks weights for armor: { parentTemplate.Id}, unable to adjust plate choice, using existing data");
}
// Replace mod pool with pool of chosen plate items
modPoolToChooseFrom = plateSlotFilteringOutcome.PlateModTemplates;
}
// Choose random mod from pool and check its compatibility
string modTpl = null;
var found = false;
var exhaustableModPool = CreateExhaustableArray(modPoolToChooseFrom);
while (exhaustableModPool.HasValues())
{
modTpl = exhaustableModPool.GetRandomValue();
if (modTpl is not null && !_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(equipment, modTpl, modSlotName).Incompatible.GetValueOrDefault(false))
{
found = true;
break;
}
}
// Compatible item not found but slot REQUIRES item, get random item from db
if (!found && itemSlotTemplate.Required.GetValueOrDefault(false))
{
modTpl = GetRandomModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment);
found = modTpl is not null;
}
// Compatible item not found + not required - skip
if (!(found || itemSlotTemplate.Required.GetValueOrDefault(false)))
{
continue;
}
// Get chosen mods db template and check it fits into slot
var modTemplate = _itemHelper.GetItem(modTpl);
if (
!IsModValidForSlot(
modTemplate,
itemSlotTemplate,
modSlotName,
parentTemplate,
settings.BotData.Role)
)
{
continue;
}
// Generate new id to ensure all items are unique on bot
var modId = _hashUtil.Generate();
equipment.Add(
CreateModItem(modId, modTpl, parentId, modSlotName, modTemplate.Value, settings.BotData.Role));
// Does item being added exist in mod pool - has its own mod pool
if (settings.ModPool.ContainsKey(modTpl))
{
// Call self again with mod being added as item to add child mods to
GenerateModsForEquipment(
equipment,
modId,
modTemplate.Value,
settings,
specificBlacklist,
forceSpawn);
}
}
return equipment;
}
/// <summary>
@@ -66,7 +255,13 @@ public class BotEquipmentModGenerator
/// <returns>True if it should</returns>
public bool ShouldForceSubStockSlots(string modSlot, EquipmentFilters botEquipConfig, TemplateItem modToAddTemplate)
{
throw new NotImplementedException();
// Slots a weapon can store its stock in
string[] stockSlots = ["mod_stock", "mod_stock_000", "mod_stock_001", "mod_stock_akms"];
// Can the stock hold child items
var hasSubSlots = modToAddTemplate.Properties.Slots?.Count > 0;
return (stockSlots.Contains(modSlot) && hasSubSlots) || botEquipConfig.ForceStock.GetValueOrDefault(false);
}
/// <summary>
@@ -77,18 +272,35 @@ public class BotEquipmentModGenerator
/// <returns>true if it's a front/rear sight</returns>
public bool ModIsFrontOrRearSight(string modSlot, string tpl)
{
throw new NotImplementedException();
// Gas block /w front sight is special case, deem it a 'front sight' too
if (modSlot == "mod_gas_block" && tpl == "5ae30e795acfc408fb139a0b")
{
// M4A1 front sight with gas block
return true;
}
return ((string[])["mod_sight_front", "mod_sight_rear"]).Contains(modSlot);
}
/// <summary>
/// Does the provided mod details show the mod can hold a scope
/// </summary>
/// <param name="modSlot">e.g. mod_scope, mod_mount</param>
/// <param name="ModsParentId">Parent id of mod item</param>
/// <param name="modsParentId">Parent id of mod item</param>
/// <returns>true if it can hold a scope</returns>
public bool ModSlotCanHoldScope(string modSlot, string ModsParentId)
public bool ModSlotCanHoldScope(string modSlot, string modsParentId)
{
throw new NotImplementedException();
return (
((string[])[
"mod_scope",
"mod_mount",
"mod_mount_000",
"mod_scope_000",
"mod_scope_001",
"mod_scope_002",
"mod_scope_003",
]).Contains(modSlot.ToLower()) && modsParentId == BaseClasses.MOUNT
);
}
/// <summary>
@@ -97,9 +309,25 @@ public class BotEquipmentModGenerator
/// <param name="modSpawnChances">Chance dictionary to update</param>
/// <param name="modSlotsToAdjust"></param>
/// <param name="newChancePercent"></param>
public void AdjustSlotSpawnChances(Dictionary<string, double> modSpawnChances, List<string> modSlotsToAdjust, double newChancePercent)
public void AdjustSlotSpawnChances(Dictionary<string, double>? modSpawnChances, List<string>? modSlotsToAdjust, double newChancePercent)
{
throw new NotImplementedException();
if (modSpawnChances is null)
{
_logger.Warning("AdjustSlotSpawnChances() modSpawnChances missing");
return;
}
if (modSlotsToAdjust is null)
{
_logger.Warning("AdjustSlotSpawnChances() modSlotsToAdjust missing");
return;
}
foreach (var modName in modSlotsToAdjust) {
modSpawnChances[modName] = newChancePercent;
}
}
/// <summary>
@@ -108,9 +336,9 @@ public class BotEquipmentModGenerator
/// <param name="modSlot">Slot id to check</param>
/// <param name="modsParentId">OPTIONAL: parent id of modslot being checked</param>
/// <returns>True if modSlot can have muzzle-related items</returns>
public bool AodSlotCanHoldMuzzleDevices(string modSlot, string? modsParentId)
public bool ModSlotCanHoldMuzzleDevices(string modSlot, string? modsParentId)
{
throw new NotImplementedException();
return ((string[])["mod_muzzle", "mod_muzzle_000", "mod_muzzle_001"]).Contains(modSlot.ToLower());
}
/// <summary>
@@ -130,9 +358,20 @@ public class BotEquipmentModGenerator
/// <param name="modSlot">e.g patron_in_weapon</param>
/// <param name="parentTemplate">item template</param>
/// <returns>Slot item</returns>
public Slot GetModItemSlotFromDb(string modSlot, TemplateItem parentTemplate)
public Slot? GetModItemSlotFromDb(string modSlot, TemplateItem parentTemplate)
{
throw new NotImplementedException();
var modSlotLower = modSlot.ToLower();
switch (modSlotLower)
{
case "patron_in_weapon":
case "patron_in_weapon_000":
case "patron_in_weapon_001":
return parentTemplate.Properties.Chambers.FirstOrDefault((chamber) => chamber.Name.Contains(modSlotLower));
case "cartridges":
return parentTemplate.Properties.Cartridges.FirstOrDefault((c) => c.Name.ToLower() == modSlotLower);
default:
return parentTemplate.Properties.Slots.FirstOrDefault((s) => s.Name.ToLower() == modSlotLower);
}
}
/// <summary>
@@ -145,7 +384,20 @@ public class BotEquipmentModGenerator
/// <returns>ModSpawn.SPAWN when mod should be spawned, ModSpawn.DEFAULT_MOD when default mod should spawn, ModSpawn.SKIP when mod is skipped</returns>
public ModSpawn ShouldModBeSpawned(Slot itemSlot, string modSlotName, Dictionary<string, double> modSpawnChances, EquipmentFilters botEquipConfig)
{
throw new NotImplementedException();
var slotRequired = itemSlot.Required;
if (GetAmmoContainers().Contains(modSlotName))
{
// Always force mags/cartridges in weapon to spawn
return ModSpawn.SPAWN;
}
var spawnMod = _probabilityHelper.RollChance(modSpawnChances[modSlotName]);
if (!spawnMod && (slotRequired.GetValueOrDefault(false) || botEquipConfig.WeaponSlotIdsToMakeRequired.Contains(modSlotName)))
{
// Edge case: Mod is required but spawn chance roll failed, choose default mod spawn for slot
return ModSpawn.DEFAULT_MOD;
}
return spawnMod ? ModSpawn.SPAWN : ModSpawn.SKIP;
}
/// <summary>
@@ -199,9 +451,9 @@ public class BotEquipmentModGenerator
throw new NotImplementedException();
}
public object CreateExhaustableArray<T>(T itemsToAddToArray) // TODO: this wont likely be needed, reimplement for C#
public ExhaustableArray<T> CreateExhaustableArray<T>(List<T> itemsToAddToArray) // TODO: this wont likely be needed, reimplement for C#
{
throw new NotImplementedException();
return new ExhaustableArray<T>(itemsToAddToArray, _randomUtil, _cloner);
}
/// <summary>
@@ -210,9 +462,9 @@ public class BotEquipmentModGenerator
/// <param name="modPool"></param>
/// <param name="tplBlacklist">Tpls that are incompatible and should not be used</param>
/// <returns>string array of compatible mod tpls with weapon</returns>
public List<string> GetFilteredModPool(List<string> modPool, List<string> tplBlacklist) // TODO: tplBlacklist was Set<string>
public List<string> GetFilteredModPool(List<string> modPool, List<string> tplBlacklist)
{
throw new NotImplementedException();
return modPool.Where((tpl) => !tplBlacklist.Contains(tpl)).ToList();
}
/// <summary>
@@ -226,7 +478,19 @@ public class BotEquipmentModGenerator
/// <returns>Array of mod tpls</returns>
public List<string> GetModPoolForSlot(ModToSpawnRequest request, TemplateItem weaponTemplate)
{
throw new NotImplementedException();
// Mod is flagged as being default only, try and find it in globals
if (request.ModSpawnResult == ModSpawn.DEFAULT_MOD)
{
return GetModPoolForDefaultSlot(request, weaponTemplate);
}
if (request.IsRandomisableSlot.GetValueOrDefault(false))
{
return GetDynamicModPool(request.ParentTemplate.Id, request.ModSlot, request.BotEquipBlacklist);
}
// Required mod is not default or randomisable, use existing pool
return request.ItemModPool[request.ModSlot];
}
public List<string> GetModPoolForDefaultSlot(ModToSpawnRequest request, TemplateItem weaponTemplate)
@@ -234,9 +498,10 @@ public class BotEquipmentModGenerator
throw new NotImplementedException();
}
public object GetMatchingModFromPreset(ModToSpawnRequest request, TemplateItem weaponTemplate) // TODO: no return type given in node server
public Item GetMatchingModFromPreset(ModToSpawnRequest request, TemplateItem weaponTemplate)
{
throw new NotImplementedException();
var matchingPreset = GetMatchingPreset(weaponTemplate, request.ParentTemplate.Id);
return matchingPreset?.Items.FirstOrDefault((item) => item?.SlotId?.ToLower() == request.ModSlot.ToLower());
}
/// <summary>
@@ -247,7 +512,21 @@ public class BotEquipmentModGenerator
/// <returns>Default preset found</returns>
public Preset? GetMatchingPreset(TemplateItem weaponTemplate, string parentItemTpl)
{
throw new NotImplementedException();
// Edge case - using mp5sd reciever means default mp5 handguard doesn't fit
var isMp5sd = parentItemTpl == "5926f2e086f7745aae644231";
if (isMp5sd)
{
return _presetHelper.GetPreset("59411abb86f77478f702b5d2");
}
// Edge case - dvl 500mm is the silenced barrel and has specific muzzle mods
var isDvl500mmSilencedBarrel = parentItemTpl == "5888945a2459774bf43ba385";
if (isDvl500mmSilencedBarrel)
{
return _presetHelper.GetPreset("59e8d2b386f77445830dd299");
}
return _presetHelper.GetDefaultPreset(weaponTemplate.Id);
}
/// <summary>
@@ -258,7 +537,13 @@ public class BotEquipmentModGenerator
/// <returns>True if incompatible</returns>
public bool WeaponModComboIsIncompatible(List<Item> weapon, string modTpl)
{
throw new NotImplementedException();
// STM-9 + AR-15 Lone Star Ion Lite handguard
if (weapon[0].Template == "60339954d62c9b14ed777c06" && modTpl == "5d4405f0a4b9361e6a4e6bd9")
{
return true;
}
return false;
}
/// <summary>
@@ -273,7 +558,12 @@ public class BotEquipmentModGenerator
/// <returns>Item object</returns>
public Item CreateModItem(string modId, string modTpl, string parentId, string modSlot, TemplateItem modTemplate, string botRole)
{
throw new NotImplementedException();
return new Item {
Id = modId,
Template = modTpl,
ParentId = parentId,
SlotId = modSlot,
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(modTemplate, botRole)};
}
/// <summary>
@@ -283,7 +573,7 @@ public class BotEquipmentModGenerator
/// <returns>string array</returns>
public List<string> GetAmmoContainers()
{
throw new NotImplementedException();
return ["mod_magazine", "patron_in_weapon", "patron_in_weapon_000", "patron_in_weapon_001", "cartridges"];
}
/// <summary>
@@ -296,12 +586,27 @@ public class BotEquipmentModGenerator
/// <returns>Item tpl</returns>
public string? GetRandomModTplFromItemDb(string fallbackModTpl, Slot parentSlot, string modSlot, List<Item> items)
{
throw new NotImplementedException();
// Find compatible mods and make an array of them
var allowedItems = parentSlot.Props.Filters[0].Filter;
// Find mod item that fits slot from sorted mod array
var exhaustableModPool = CreateExhaustableArray(allowedItems);
var tmpModTpl = fallbackModTpl;
while (exhaustableModPool.HasValues())
{
tmpModTpl = exhaustableModPool.GetRandomValue();
if (!_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(items, tmpModTpl, modSlot).Incompatible.GetValueOrDefault(false))
{
return tmpModTpl;
}
}
// No mod found
return null;
}
/// <summary>
/// Check if mod exists in db + is for a required slot
/// TODO: modToAdd type was [boolean, ITemplateItem] in node
/// </summary>
/// <param name="modtoAdd">Db template of mod to check</param>
/// <param name="slotAddedToTemplate">Slot object the item will be placed as child into</param>
@@ -309,7 +614,7 @@ public class BotEquipmentModGenerator
/// <param name="parentTemplate">Db template of the mods being added</param>
/// <param name="botRole">Bots wildspawntype (assault/pmcBot/exUsec etc)</param>
/// <returns>True if valid for slot</returns>
public bool IsModValidForSlot(object modToAdd, Slot slotAddedToTemplate, string modSlot, TemplateItem parentTemplate, string botRole)
public bool IsModValidForSlot(KeyValuePair<bool, TemplateItem?> modToAdd, Slot slotAddedToTemplate, string modSlot, TemplateItem parentTemplate, string botRole)
{
throw new NotImplementedException();
}
@@ -336,7 +641,19 @@ public class BotEquipmentModGenerator
/// <returns>Array of compatible items for that slot</returns>
public List<string> GetDynamicModPool(string parentItemId, string modSlot, EquipmentFilterDetails botEquipBlacklist)
{
throw new NotImplementedException();
var modsFromDynamicPool = _cloner.Clone(
_botEquipmentModPoolService.GetCompatibleModsForWeaponSlot(parentItemId, modSlot)
);
var filteredMods = FilterModsByBlacklist(modsFromDynamicPool, botEquipBlacklist, modSlot);
if (!filteredMods.Any())
{
_logger.Warning(_localisationService.GetText("bot-unable_to_filter_mod_slot_all_blacklisted", modSlot));
return modsFromDynamicPool;
}
return filteredMods;
}
/// <summary>
@@ -346,9 +663,21 @@ public class BotEquipmentModGenerator
/// <param name="botEquipBlacklist">Equipment blacklist</param>
/// <param name="modSlot">Slot mods belong to</param>
/// <returns>Filtered array of mod tpls</returns>
public List<string> FilterModsByBlacklist(List<string> allowedMods, EquipmentFilterDetails botEquipBlacklist, string modSlot)
public List<string> FilterModsByBlacklist(List<string> allowedMods, EquipmentFilterDetails? botEquipBlacklist, string modSlot)
{
throw new NotImplementedException();
// No blacklist, nothing to filter out
if (botEquipBlacklist is null)
{
return allowedMods;
}
var result = new List<string>();
// Get item blacklist and mod equipment blacklist as one array
var blacklist = _itemFilterService.GetBlacklistedItems().Concat(botEquipBlacklist.Equipment[modSlot]);
result = allowedMods.Where((tpl) => !blacklist.Contains(tpl)).ToList();
return result;
}
/// <summary>
@@ -374,7 +703,14 @@ public class BotEquipmentModGenerator
/// <returns>String array of shells for multiple camora sources</returns>
public List<string> MergeCamoraPools(Dictionary<string, List<string>> camorasWithShells)
{
throw new NotImplementedException();
var uniqueShells = new HashSet<string>();
foreach (var shell in camorasWithShells
.SelectMany(shellKvP => shellKvP.Value))
{
uniqueShells.Add(shell);
}
return uniqueShells.ToList();
}
/// <summary>
+12 -13
View File
@@ -359,7 +359,7 @@ public class BotInventoryGenerator
var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => _itemHelper.ItemHasSlots(kvp.Key))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
if (tacVestsWithArmor.Count() == 0)
if (!tacVestsWithArmor.Any())
{
_logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
@@ -381,7 +381,7 @@ public class BotInventoryGenerator
var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
if (!allowEmptyResult && tacVestsWithoutArmor.Count() == 0)
if (!allowEmptyResult && !tacVestsWithoutArmor.Any())
{
_logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
@@ -398,13 +398,12 @@ public class BotInventoryGenerator
/// <returns>true when item added</returns>
public bool GenerateEquipment(GenerateEquipmentProperties settings)
{
_logger.Error("NOT IMPLEMENTED - GenerateEquipment");
List<string> slotsToCheck = [EquipmentSlots.Pockets.ToString(), EquipmentSlots.SecuredContainer.ToString()];
double? spawnChance = slotsToCheck.Contains(settings.RootEquipmentSlot.ToString())
? 100
: settings.SpawnChances.EquipmentChances[settings.RootEquipmentSlot.ToString()];
: settings.SpawnChances.EquipmentChances.GetValueOrDefault(settings.RootEquipmentSlot.ToString());
if (spawnChance is null)
if (!spawnChance.HasValue)
{
_logger.Warning(_localisationService.GetText("bot-no_spawn_chance_defined_for_equipment_slot",
settings.RootEquipmentSlot));
@@ -414,22 +413,22 @@ public class BotInventoryGenerator
// Roll dice on equipment item
var shouldSpawn = _randomUtil.GetChance100(spawnChance ?? 0);
if (shouldSpawn && settings.RootEquipmentPool.Count() == 0)
if (shouldSpawn && !settings.RootEquipmentPool.Any())
{
TemplateItem pickedItemDb = new TemplateItem();
var pickedItemDb = new TemplateItem();
var found = false;
// Limit attempts to find a compatible item as its expensive to check them all
// Limit attempts to find a compatible item as it's expensive to check them all
var maxAttempts = Math.Round(settings.RootEquipmentPool.Count() * 0.75); // Roughly 75% of pool size
var attempts = 0;
while (!found)
{
if (settings.RootEquipmentPool.Count() == 0)
if (!settings.RootEquipmentPool.Any())
{
return false;
}
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue<string>(settings.RootEquipmentPool);
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool);
var dbResult = _itemHelper.GetItem(chosenItemTpl);
if (!dbResult.Key)
@@ -490,8 +489,8 @@ public class BotInventoryGenerator
);
// Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot
if ((_botConfig.Equipment[settings.BotData.EquipmentRole] is not null) &&
(settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString())))
if (_botConfig.Equipment[settings.BotData.EquipmentRole] is not null &&
settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString()))
{
// Filter out mods from relevant blacklist
settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem(
@@ -501,7 +500,7 @@ public class BotInventoryGenerator
}
// Does item have slots for sub-mods to be inserted into
if (pickedItemDb.Properties.Slots?.Count() > 0 && (settings.GenerateModsBlacklist.Contains(pickedItemDb.Id)))
if (pickedItemDb.Properties.Slots.Any() && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id))
{
var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment(
[item],
+47 -44
View File
@@ -1,46 +1,49 @@
// using System.Collections.Generic;
//
// namespace Core.Models.Spt.Server;
//
// public class ExhaustableArray<T>
// {
// private List<T> pool;
//
// public ExhaustableArray(List<T> itemPool, RandomUtil randomUtil, ICloner cloner)
// {
// this.pool = cloner.Clone(itemPool);
// }
//
// public T GetRandomValue()
// {
// if (pool == null || pool.Count == 0)
// {
// return default;
// }
//
// int index = randomUtil.GetInt(0, pool.Count - 1);
// T toReturn = cloner.Clone(pool[index]);
// pool.RemoveAt(index);
// return toReturn;
// }
//
// public T GetFirstValue()
// {
// if (pool == null || pool.Count == 0)
// {
// return default;
// }
//
// T toReturn = cloner.Clone(pool[0]);
// pool.RemoveAt(0);
// return toReturn;
// }
//
// public bool HasValues()
// {
// return pool != null && pool.Count > 0;
// }
// }
using Core.Utils;
using Core.Utils.Cloners;
// TODO: Convert this to C# properly
namespace Core.Models.Spt.Server;
public class ExhaustableArray<T>
{
private readonly RandomUtil _randomUtil;
private readonly ICloner _cloner;
private List<T> pool;
public ExhaustableArray(List<T> itemPool, RandomUtil randomUtil, ICloner cloner)
{
_randomUtil = randomUtil;
_cloner = cloner;
this.pool = _cloner.Clone(itemPool);
}
public T GetRandomValue()
{
if (pool == null || pool.Count == 0)
{
return default;
}
var index = _randomUtil.GetInt(0, pool.Count - 1);
T toReturn = _cloner.Clone(pool[index]);
pool.RemoveAt(index);
return toReturn;
}
public T GetFirstValue()
{
if (pool == null || pool.Count == 0)
{
return default;
}
T toReturn = _cloner.Clone(pool[0]);
pool.RemoveAt(0);
return toReturn;
}
public bool HasValues()
{
return pool?.Count > 0;
}
}