From 9474487bdd5f2b43dd44f15c9419ebacc0a519f5 Mon Sep 17 00:00:00 2001 From: CWX Date: Wed, 15 Jan 2025 12:24:37 +0000 Subject: [PATCH] Fix types, Implement botGenHelper --- Core/Generators/BotInventoryGenerator.cs | 289 ++++++++++++++++++++--- Core/Generators/BotWeaponGenerator.cs | 4 +- Core/Helpers/BotGeneratorHelper.cs | 3 +- Core/Helpers/WeightedRandomHelper.cs | 7 +- 4 files changed, 262 insertions(+), 41 deletions(-) diff --git a/Core/Generators/BotInventoryGenerator.cs b/Core/Generators/BotInventoryGenerator.cs index b1aaaefd..5415a479 100644 --- a/Core/Generators/BotInventoryGenerator.cs +++ b/Core/Generators/BotInventoryGenerator.cs @@ -7,6 +7,7 @@ using Core.Models.Enums; using Core.Models.Spt.Bots; using Core.Models.Spt.Config; using Core.Servers; +using Core.Services; using Core.Utils; using Equipment = Core.Models.Eft.Common.Tables.Equipment; using ILogger = Core.Models.Utils.ILogger; @@ -143,7 +144,8 @@ public class BotInventoryGenerator var questStashItemsId = _hashUtil.Generate(); var sortingTableId = _hashUtil.Generate(); - return new BotBaseInventory{ + return new BotBaseInventory + { Items = [ new() { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT }, @@ -179,7 +181,8 @@ public class BotInventoryGenerator BotBaseInventory botInventory, int botLevel, string chosenGameVersion, GetRaidConfigurationRequestData raidConfig) { // These will be handled later - var excludedSlots = new List(){ + var excludedSlots = new List() + { EquipmentSlots.Pockets, EquipmentSlots.FirstPrimaryWeapon, EquipmentSlots.SecondPrimaryWeapon, @@ -201,10 +204,11 @@ public class BotInventoryGenerator _weatherHelper.IsNightTime(raidConfig.TimeVariant) ) { - foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers)) { + foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers)) + { // Never let mod chance go outside of 0 - 100 randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min( - Math.Max( randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), 100); + Math.Max(randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), 100); } } @@ -216,14 +220,16 @@ public class BotInventoryGenerator // Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts // e.g. ArmorVest should be generated after TactivalVest // or FACE_COVER before HEADWEAR - foreach (var equipmentSlotKvP in templateInventory.Equipment) { + foreach (var equipmentSlotKvP in templateInventory.Equipment) + { // Skip some slots as they need to be done in a specific order + with specific parameter values // e.g. Weapons - if (excludedSlots.Contains(equipmentSlotKvP.Key)) { + if (excludedSlots.Contains(equipmentSlotKvP.Key)) + { continue; } - GenerateEquipment( new GenerateEquipmentProperties + GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = equipmentSlotKvP.Key, RootEquipmentPool = equipmentSlotKvP.Value, @@ -238,17 +244,17 @@ public class BotInventoryGenerator } // Generate below in specific order - GenerateEquipment( new GenerateEquipmentProperties + GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.Pockets, // Unheard profiles have unique sized pockets, TODO - handle this somewhere else in a better way RootEquipmentPool = chosenGameVersion == GameEditions.UNHEARD - ? new Dictionary{ [ItemTpl.POCKETS_1X4_TUE] = 1 } + ? new Dictionary { [ItemTpl.POCKETS_1X4_TUE] = 1 } : templateInventory.Equipment[EquipmentSlots.Pockets], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, - BotData = new BotData{ Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, + BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, @@ -256,7 +262,7 @@ public class BotInventoryGenerator GeneratingPlayerLevel = pmcProfile.Info.Level, }); - GenerateEquipment( new GenerateEquipmentProperties + GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.FaceCover, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.FaceCover], @@ -269,7 +275,7 @@ public class BotInventoryGenerator GeneratingPlayerLevel = pmcProfile.Info.Level, }); - GenerateEquipment( new GenerateEquipmentProperties + GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.Headwear, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Headwear], @@ -295,7 +301,7 @@ public class BotInventoryGenerator GeneratingPlayerLevel = pmcProfile.Info.Level, }); - var hasArmorVest = GenerateEquipment( new GenerateEquipmentProperties + var hasArmorVest = GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.ArmorVest, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.ArmorVest], @@ -309,23 +315,26 @@ public class BotInventoryGenerator }); // Bot has no armor vest and flagged to be forced to wear armored rig in this event - if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest) { + if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest) + { // Filter rigs down to only those with armor FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole); } // Optimisation - Remove armored rigs from pool - if (hasArmorVest) { + if (hasArmorVest) + { // Filter rigs down to only those with armor FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole); } // Bot is flagged as always needing a vest - if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest) { + if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest) + { wornItemChances.EquipmentChances["TacticalVest"] = 100; } - GenerateEquipment( new GenerateEquipmentProperties + GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.Earpiece, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece], @@ -346,12 +355,17 @@ public class BotInventoryGenerator /// Role of bot vests are being filtered for public void FilterRigsToThoseWithProtection(Dictionary> templateEquipment, string botRole) { - throw new NotImplementedException(); - } - - public void FilterRigsToThoseWithoutProtection(Dictionary> templateEquipment, string botRole, bool allowEmptyResult = true) - { - throw new NotImplementedException(); + var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => _itemHelper.ItemHasSlots(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + if (tacVestsWithArmor.Count() == 0) + { + _logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool"); + + return; + } + + templateEquipment[EquipmentSlots.TacticalVest] = tacVestsWithArmor; } /// @@ -360,9 +374,20 @@ public class BotInventoryGenerator /// Equpiment to filter TacticalVest of /// Role of bot vests are being filtered for /// Should the function return all rigs when 0 unarmored are found - public void FilterRigsTothoseWithoutProtection(Equipment templateEquipment, string botRole, bool allowEmptyRequest = false) + public void FilterRigsToThoseWithoutProtection(Dictionary> templateEquipment, string botRole, + bool allowEmptyResult = true) { - throw new NotImplementedException(); + var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + if (!allowEmptyResult && tacVestsWithoutArmor.Count() == 0) + { + _logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool"); + + return; + } + + templateEquipment[EquipmentSlots.TacticalVest] = tacVestsWithoutArmor; } /// @@ -373,7 +398,129 @@ public class BotInventoryGenerator public bool GenerateEquipment(GenerateEquipmentProperties settings) { _logger.Error("NOT IMPLEMENTED - GenerateEquipment"); - return true; + List slotsToCheck = [EquipmentSlots.Pockets.ToString(), EquipmentSlots.SecuredContainer.ToString()]; + double? spawnChance = slotsToCheck.Contains(settings.RootEquipmentSlot.ToString()) + ? 100 + : settings.SpawnChances.EquipmentChances[settings.RootEquipmentSlot.ToString()]; + + if (spawnChance is null) + { + _logger.Warning(_localisationService.GetText("bot-no_spawn_chance_defined_for_equipment_slot", + settings.RootEquipmentSlot)); + + return false; + } + + // Roll dice on equipment item + var shouldSpawn = _randomUtil.GetChance100(spawnChance ?? 0); + if (shouldSpawn && settings.RootEquipmentPool.Count() == 0) + { + TemplateItem pickedItemDb = new TemplateItem(); + var found = false; + + // Limit attempts to find a compatible item as its 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) + { + return false; + } + + var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool); + var dbResult = _itemHelper.GetItem(chosenItemTpl); + + if (!dbResult.Key) + { + _logger.Error(_localisationService.GetText("bot-missing_item_template", chosenItemTpl)); + _logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}"); + + // Remove picked item + settings.RootEquipmentPool.Remove(chosenItemTpl); + + attempts++; + + continue; + } + + // Is the chosen item compatible with other items equipped + var compatibilityResult = _botGeneratorHelper.IsItemIncompatibleWithCurrentItems( + settings.Inventory.Items, + chosenItemTpl, + settings.RootEquipmentSlot.ToString() + ); + if (compatibilityResult.Incompatible ?? false) + { + // Tried x different items that failed, stop + if (attempts > maxAttempts) + { + return false; + } + + // Remove picked item from pool + settings.RootEquipmentPool.Remove(chosenItemTpl); + + // Increment times tried + attempts++; + } + else + { + // Success + found = true; + pickedItemDb = dbResult.Value; + } + } + + // Create root item + var id = _hashUtil.Generate(); + Item item = new() + { + Id = id, + Template = pickedItemDb.Id, + ParentId = settings.Inventory.Equipment, + SlotId = settings.RootEquipmentSlot.ToString(), + Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(pickedItemDb, settings.BotData.Role) + }; + + var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist( + settings.BotData.EquipmentRole, + (double)settings.GeneratingPlayerLevel + ); + + // 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()))) + { + // Filter out mods from relevant blacklist + settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem( + pickedItemDb.Id, + botEquipBlacklist.Equipment + ); + } + + // Does item have slots for sub-mods to be inserted into + if (pickedItemDb.Properties.Slots?.Count() > 0 && (settings.GenerateModsBlacklist.Contains(pickedItemDb.Id))) + { + var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment( + [item], + id, + pickedItemDb, + settings, + botEquipBlacklist + ); + settings.Inventory.Items.AddRange(childItemsToAdd); + } + else + { + // No slots, add root item only + settings.Inventory.Items.Add(item); + } + + return true; + } + + return false; } /// @@ -384,7 +531,19 @@ public class BotInventoryGenerator /// Filtered pool of mods public Dictionary> GetFilteredDynamicModsForItem(string itemTpl, Dictionary> equipmentBlacklist) { - throw new NotImplementedException(); + var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl); + foreach (var modSlot in modPool.Keys ?? Enumerable.Empty()) + { + var blacklistedMods = equipmentBlacklist[modSlot] ?? []; + var filteredMods = modPool[modSlot].Where((slotName) => !blacklistedMods.Contains(slotName)); + + if (filteredMods.Count() > 0) + { + modPool[modSlot] = filteredMods.ToList(); + } + } + + return modPool; } /// @@ -399,10 +558,27 @@ public class BotInventoryGenerator /// Limits for items the bot can have /// level of bot having weapon generated public void GenerateAndAddWeaponsToBot(BotTypeInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory, - string botRole, - bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel) + string botRole, bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel) { - throw new NotImplementedException(); + var weaponSlotsToFill = GetDesiredWeaponsForBot(equipmentChances); + foreach (var weaponSlot in weaponSlotsToFill) + { + // Add weapon to bot if true and bot json has something to put into the slot + if (weaponSlot.ShouldSpawn && templateInventory.Equipment[weaponSlot.Slot].Any()) + { + AddWeaponAndMagazinesToInventory( + sessionId, + weaponSlot, + templateInventory, + botInventory, + equipmentChances, + botRole, + isPmc, + itemGenerationLimitsMinMax, + botLevel + ); + } + } } /// @@ -410,9 +586,30 @@ public class BotInventoryGenerator /// /// Chances bot has certain equipment /// What slots bot should have weapons generated for - public object GetDesiredWeaponsForBot(Chances equipmentChances) // TODO: Type fuckery { slot: EquipmentSlots; shouldSpawn: boolean }[] + public List GetDesiredWeaponsForBot(Chances equipmentChances) // TODO: Type fuckery { slot: EquipmentSlots; shouldSpawn: boolean }[] { - throw new NotImplementedException(); + var shouldSpawnPrimary = _randomUtil.GetChance100(equipmentChances.EquipmentChances["FirstPrimaryWeapon"]); + return + [ + new() + { + Slot = EquipmentSlots.FirstPrimaryWeapon, ShouldSpawn = shouldSpawnPrimary + }, + new() + { + Slot = EquipmentSlots.SecondPrimaryWeapon, + ShouldSpawn = shouldSpawnPrimary + ? _randomUtil.GetChance100(equipmentChances.EquipmentChances["SecondPrimaryWeapon"]) + : false + }, + new() + { + Slot = EquipmentSlots.Holster, + ShouldSpawn = shouldSpawnPrimary + ? _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]) // Primary weapon = roll for chance at pistol + : true // No primary = force pistol + } + ]; } /// @@ -427,10 +624,34 @@ public class BotInventoryGenerator /// Is the bot being generated as a pmc /// /// - public void AddWeaponAndMagazineToInventory(string sessionId, object weaponSlot, BotBaseInventory templateInventory, BotBaseInventory botInventory, + public void AddWeaponAndMagazinesToInventory(string sessionId, DesiredWeapons weaponSlot, BotTypeInventory templateInventory, BotBaseInventory botInventory, Chances equipmentChances, string botRole, bool isPmc, Generation itemGenerationWeights, int botLevel) { - throw new NotImplementedException(); + var generatedweapon = _botWeaponGenerator.GenerateRandomWeapon( + sessionId, + weaponSlot.Slot.ToString(), + templateInventory, + botInventory.Equipment, + equipmentChances.WeaponModsChances, + botRole, + isPmc, + botLevel + ); + + botInventory.Items.AddRange(generatedweapon.Weapon); + + _botWeaponGenerator.AddExtraMagazinesToInventory( + generatedweapon, + itemGenerationWeights.Items.Magazines, + botInventory, + botRole); } } + +public class DesiredWeapons +{ + public EquipmentSlots Slot { get; set; } + + public bool ShouldSpawn { get; set; } +} diff --git a/Core/Generators/BotWeaponGenerator.cs b/Core/Generators/BotWeaponGenerator.cs index 3f9f1c03..88a66c1e 100644 --- a/Core/Generators/BotWeaponGenerator.cs +++ b/Core/Generators/BotWeaponGenerator.cs @@ -29,7 +29,7 @@ public class BotWeaponGenerator /// Is weapon generated for a pmc /// /// GenerateWeaponResult object - public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotBaseInventory botTemplateInventory, string weaponParentId, + public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotTypeInventory botTemplateInventory, string weaponParentId, Dictionary modChances, string botRole, bool isPmc, int botLevel) { throw new NotImplementedException(); @@ -41,7 +41,7 @@ public class BotWeaponGenerator /// Primary/secondary/holster /// e.g. assault.json /// Weapon template - public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotBaseInventory botTemplateInventory) + public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotTypeInventory botTemplateInventory) { throw new NotImplementedException(); } diff --git a/Core/Helpers/BotGeneratorHelper.cs b/Core/Helpers/BotGeneratorHelper.cs index ecb8fd73..7900057b 100644 --- a/Core/Helpers/BotGeneratorHelper.cs +++ b/Core/Helpers/BotGeneratorHelper.cs @@ -1,6 +1,7 @@ using Core.Annotations; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; +using Core.Models.Spt.Bots; using Core.Models.Spt.Config; using Core.Servers; @@ -84,7 +85,7 @@ public class BotGeneratorHelper /// Tpl of the item to check for incompatibilities /// Slot the item will be placed into /// false if no incompatibilities, also has incompatibility reason - public object IsItemIncompatibleWithCurrentItems(List itemsEquipped, string tplToCheck, string equipmentSlot) + public ChooseRandomCompatibleModResult IsItemIncompatibleWithCurrentItems(List itemsEquipped, string tplToCheck, string equipmentSlot) { throw new NotImplementedException(); } diff --git a/Core/Helpers/WeightedRandomHelper.cs b/Core/Helpers/WeightedRandomHelper.cs index 11cc012c..e64dc36a 100644 --- a/Core/Helpers/WeightedRandomHelper.cs +++ b/Core/Helpers/WeightedRandomHelper.cs @@ -20,7 +20,7 @@ public class WeightedRandomHelper /// /// Items and weights to use /// Chosen item from array - public T GetWeightedValue(Dictionary values) where T : notnull + public T GetWeightedValue(Dictionary values) where T : notnull { var itemKeys = values.Keys.ToList(); var weights = values.Values.ToList(); @@ -28,7 +28,6 @@ public class WeightedRandomHelper var chosenItem = WeightedRandom(itemKeys, weights); return chosenItem.Item; - // SORRY IF THIS BLEW UP, I DONT SEE A REASON ITS GENERIC - CWX } /// @@ -45,7 +44,7 @@ public class WeightedRandomHelper /// List of items /// List of weights /// Dictionary with item and index - public WeightedRandomResult WeightedRandom(List items, List weights) + public WeightedRandomResult WeightedRandom(List items, List weights) { if (items.Count == 0) { @@ -66,7 +65,7 @@ public class WeightedRandomHelper List cumulativeWeights = []; for (var i = 0; i < weights.Count; i++) { - cumulativeWeights.Add(weights[i] + (i > 0 ? cumulativeWeights[i - 1] : 0)); + cumulativeWeights.Add((int)(weights[i]) + (i > 0 ? (cumulativeWeights[i - 1]) : 0)); } // Getting the random number in a range of [0...sum(weights)]