using Core.Annotations; using Core.Context; using Core.Helpers; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Match; using Core.Models.Enums; using Core.Models.Spt.Bots; using Core.Models.Spt.Config; using Core.Servers; using Core.Utils; using Equipment = Core.Models.Eft.Common.Tables.Equipment; using ILogger = Core.Models.Utils.ILogger; namespace Core.Generators; [Injectable] public class BotInventoryGenerator { private readonly ILogger _logger; private readonly HashUtil _hashUtil; private readonly RandomUtil _randomUtil; private readonly DatabaseService _databaseService; private readonly ApplicationContext _applicationContext; private readonly BotWeaponGenerator _botWeaponGenerator; private readonly BotLootGenerator _botLootGenerator; private readonly BotGeneratorHelper _botGeneratorHelper; private readonly ProfileHelper _profileHelper; private readonly BotHelper _botHelper; private readonly WeightedRandomHelper _weightedRandomHelper; private readonly ItemHelper _itemHelper; private readonly WeatherHelper _weatherHelper; private readonly LocalisationService _localisationService; private readonly BotEquipmentFilterService _botEquipmentFilterService; private readonly BotEquipmentModPoolService _botEquipmentModPoolService; private readonly BotEquipmentModGenerator _botEquipmentModGenerator; private readonly ConfigServer _configServer; private BotConfig _botConfig; public BotInventoryGenerator( ILogger logger, HashUtil hashUtil, RandomUtil randomUtil, DatabaseService databaseService, ApplicationContext applicationContext, BotWeaponGenerator botWeaponGenerator, BotLootGenerator botLootGenerator, BotGeneratorHelper botGeneratorHelper, ProfileHelper profileHelper, BotHelper botHelper, WeightedRandomHelper weightedRandomHelper, ItemHelper itemHelper, WeatherHelper weatherHelper, LocalisationService localisationService, BotEquipmentFilterService botEquipmentFilterService, BotEquipmentModPoolService botEquipmentModPoolService, BotEquipmentModGenerator botEquipmentModGenerator, ConfigServer configServer ) { _logger = logger; _hashUtil = hashUtil; _randomUtil = randomUtil; _databaseService = databaseService; _applicationContext = applicationContext; _botWeaponGenerator = botWeaponGenerator; _botLootGenerator = botLootGenerator; _botGeneratorHelper = botGeneratorHelper; _profileHelper = profileHelper; _botHelper = botHelper; _weightedRandomHelper = weightedRandomHelper; _itemHelper = itemHelper; _weatherHelper = weatherHelper; _localisationService = localisationService; _botEquipmentFilterService = botEquipmentFilterService; _botEquipmentModPoolService = botEquipmentModPoolService; _botEquipmentModGenerator = botEquipmentModGenerator; _configServer = configServer; _botConfig = _configServer.GetConfig(); } /// /// Add equipment/weapons/loot to bot /// /// Session id /// Base json db file for the bot having its loot generated /// Role bot has (assault/pmcBot) /// Is bot being converted into a pmc /// Level of bot being generated /// Game version for bot, only really applies for PMCs /// PmcInventory object with equipment/weapons/loot public BotBaseInventory GenerateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion) { var templateInventory = botJsonTemplate.BotInventory; var wornItemChances = botJsonTemplate.BotChances; var itemGenerationLimitsMinMax = botJsonTemplate.BotGeneration; // Generate base inventory with no items var botInventory = GenerateInventoryBase(); // Get generated raid details bot will be spawned in var raidConfig = _applicationContext .GetLatestValue(ContextVariableType.RAID_CONFIGURATION) ?.GetValue(); GenerateAndAddEquipmentToBot( sessionId, templateInventory, wornItemChances, botRole, botInventory, botLevel, chosenGameVersion, raidConfig); // Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed GenerateAndAddWeaponsToBot( templateInventory, wornItemChances, sessionId, botInventory, botRole, isPmc, itemGenerationLimitsMinMax, botLevel); // Pick loot and add to bots containers (rig/backpack/pockets/secure) _botLootGenerator.GenerateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel); return botInventory; } /// /// Create a pmcInventory object with all the base/generic items needed /// /// PmcInventory object public BotBaseInventory GenerateInventoryBase() { var equipmentId = _hashUtil.Generate(); var stashId = _hashUtil.Generate(); var questRaidItemsId = _hashUtil.Generate(); var questStashItemsId = _hashUtil.Generate(); var sortingTableId = _hashUtil.Generate(); return new BotBaseInventory{ Items = [ new() { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT }, new() { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 }, new() { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID }, new() { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE }, new() { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE } ], Equipment = equipmentId, Stash = stashId, QuestRaidItems = questRaidItemsId, QuestStashItems = questStashItemsId, SortingTable = sortingTableId, HideoutAreaStashes = { }, FastPanel = { }, FavoriteItems = [], HideoutCustomizationStashId = "", }; } /// /// Add equipment to a bot /// /// Session id /// bot/x.json data from db /// Chances items will be added to bot /// Role bot has (assault/pmcBot) /// Inventory to add equipment to /// Level of bot /// Game version for bot, only really applies for PMCs /// RadiConfig public void GenerateAndAddEquipmentToBot(string sessionId, BotTypeInventory templateInventory, Chances wornItemChances, string botRole, BotBaseInventory botInventory, int botLevel, string chosenGameVersion, GetRaidConfigurationRequestData raidConfig) { // These will be handled later var excludedSlots = new List(){ EquipmentSlots.Pockets, EquipmentSlots.FirstPrimaryWeapon, EquipmentSlots.SecondPrimaryWeapon, EquipmentSlots.Holster, EquipmentSlots.ArmorVest, EquipmentSlots.TacticalVest, EquipmentSlots.FaceCover, EquipmentSlots.Headwear, EquipmentSlots.Earpiece }; _botConfig.Equipment.TryGetValue(_botGeneratorHelper.GetBotEquipmentRole(botRole), out var botEquipConfig); var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig); // Apply nighttime changes if its nighttime + there's changes to make if ( randomistionDetails?.NighttimeChanges is not null && raidConfig is not null && _weatherHelper.IsNightTime(raidConfig.TimeVariant) ) { 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); } } // Get profile of player generating bots, we use their level later on var pmcProfile = _profileHelper.GetPmcProfile(sessionId); var botEquipmentRole = _botGeneratorHelper.GetBotEquipmentRole(botRole); // 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) { // 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)) { continue; } GenerateEquipment( new GenerateEquipmentProperties { RootEquipmentSlot = equipmentSlotKvP.Key, RootEquipmentPool = equipmentSlotKvP.Value, ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GeneratingPlayerLevel = pmcProfile.Info.Level, }); } // Generate below in specific order 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 } : templateInventory.Equipment[EquipmentSlots.Pockets], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData{ Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE], GeneratingPlayerLevel = pmcProfile.Info.Level, }); GenerateEquipment( new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.FaceCover, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.FaceCover], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GeneratingPlayerLevel = pmcProfile.Info.Level, }); GenerateEquipment( new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.Headwear, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Headwear], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GeneratingPlayerLevel = pmcProfile.Info.Level, }); GenerateEquipment(new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.Earpiece, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GeneratingPlayerLevel = pmcProfile.Info.Level, }); var hasArmorVest = GenerateEquipment( new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.ArmorVest, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.ArmorVest], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GeneratingPlayerLevel = pmcProfile.Info.Level, }); // Bot has no armor vest and flagged to be forced to wear armored rig in this event 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) { // 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) { wornItemChances.EquipmentChances["TacticalVest"] = 100; } GenerateEquipment( new GenerateEquipmentProperties { RootEquipmentSlot = EquipmentSlots.Earpiece, RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece], ModPool = templateInventory.Mods, SpawnChances = wornItemChances, BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole }, Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, GeneratingPlayerLevel = pmcProfile.Info.Level, }); } /// /// Remove non-armored rigs from parameter data /// /// Equpiment to filter TacticalVest of /// 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(); } /// /// Remove armored rigs from parameter data /// /// 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) { throw new NotImplementedException(); } /// /// Add a piece of equipment with mods to inventory from the provided pools /// /// Values to adjust how item is chosen and added to bot /// true when item added public bool GenerateEquipment(GenerateEquipmentProperties settings) { _logger.Error("NOT IMPLEMENTED - GenerateEquipment"); return true; } /// /// Get all possible mods for item and filter down based on equipment blacklist from bot.json config /// /// Item mod pool is being retrieved and filtered /// Blacklist to filter mod pool with /// Filtered pool of mods public Dictionary> GetFilteredDynamicModsForItem(string itemTpl, Dictionary> equipmentBlacklist) { throw new NotImplementedException(); } /// /// Work out what weapons bot should have equipped and add them to bot inventory /// /// bot/x.json data from db /// Chances bot can have equipment equipped /// Session id /// Inventory to add weapons to /// assault/pmcBot/bossTagilla etc /// Is the bot being generated as a pmc /// 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) { throw new NotImplementedException(); } /// /// Calculate if the bot should have weapons in Primary/Secondary/Holster slots /// /// 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 }[] { throw new NotImplementedException(); } /// /// Add weapon + spare mags/ammo to bots inventory /// /// Session id /// Weapon slot being generated /// bot/x.json data from db /// Inventory to add weapon+mags/ammo to /// Chances bot can have equipment equipped /// assault/pmcBot/bossTagilla etc /// Is the bot being generated as a pmc /// /// public void AddWeaponAndMagazineToInventory(string sessionId, object weaponSlot, BotBaseInventory templateInventory, BotBaseInventory botInventory, Chances equipmentChances, string botRole, bool isPmc, Generation itemGenerationWeights, int botLevel) { throw new NotImplementedException(); } }