using Core.Annotations; using Core.Context; 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.Models.Utils; using Core.Servers; using Core.Services; using Core.Utils; namespace Core.Helpers; [Injectable] public class BotGeneratorHelper { private readonly ISptLogger _logger; private readonly RandomUtil _randomUtil; private readonly DatabaseService _databaseService; private readonly DurabilityLimitsHelper _durabilityLimitsHelper; private readonly ItemHelper _itemHelper; private readonly InventoryHelper _inventoryHelper; private readonly ContainerHelper _containerHelper; private readonly ApplicationContext _applicationContext; private readonly LocalisationService _localisationService; private readonly ConfigServer _configServer; private readonly BotConfig _botConfig; private readonly PmcConfig _pmcConfig; public BotGeneratorHelper ( ISptLogger logger, RandomUtil randomUtil, DatabaseService databaseService, DurabilityLimitsHelper durabilityLimitsHelper, ItemHelper itemHelper, InventoryHelper inventoryHelper, ContainerHelper containerHelper, ApplicationContext applicationContext, LocalisationService localisationService, ConfigServer configServer ) { _logger = logger; _randomUtil = randomUtil; _databaseService = databaseService; _durabilityLimitsHelper = durabilityLimitsHelper; _itemHelper = itemHelper; _inventoryHelper = inventoryHelper; _containerHelper = containerHelper; _applicationContext = applicationContext; _localisationService = localisationService; _configServer = configServer; _botConfig = _configServer.GetConfig(); _pmcConfig = _configServer.GetConfig(); } /// /// Adds properties to an item /// e.g. Repairable / HasHinge / Foldable / MaxDurability /// /// Item extra properties are being generated for /// Used by weapons to randomize the durability values. Null for non-equipped items /// Item Upd object with extra properties public Upd GenerateExtraPropertiesForItem(TemplateItem itemTemplate, string botRole = null) { // Get raid settings, if no raid, default to day var raidSettings = _applicationContext .GetLatestValue(ContextVariableType.RAID_CONFIGURATION) ?.GetValue(); var raidIsNight = raidSettings?.TimeVariant == DateTimeEnum.PAST; Upd itemProperties = new(); if (itemTemplate.Properties.MaxDurability is not null) { if (itemTemplate.Properties.WeapClass is not null) { // Is weapon itemProperties.Repairable = GenerateWeaponRepairableProperties(itemTemplate, botRole); } else if (itemTemplate.Properties.ArmorClass is not null) { // Is armor itemProperties.Repairable = GenerateArmorRepairableProperties(itemTemplate, botRole); } } if (itemTemplate.Properties.HasHinge ?? false) { itemProperties.Togglable = new() { On = true }; } if (itemTemplate.Properties.Foldable ?? false) { itemProperties.Foldable = new() { Folded = false }; } if (itemTemplate.Properties.WeapFireType?.Any() ?? false) { if (itemTemplate.Properties.WeapFireType.Contains("fullauto")) { itemProperties.FireMode = new() { FireMode = "fullauto" }; } else { itemProperties.FireMode = new() { FireMode = _randomUtil.GetArrayValue(itemTemplate.Properties.WeapFireType) }; } } if (itemTemplate.Properties.MaxHpResource is not null) { itemProperties.MedKit = new() { HpResource = (int)GetRandomizedResourceValue( itemTemplate.Properties.MaxHpResource ?? 0, _botConfig.LootItemResourceRandomization[botRole]?.Meds ) }; } if (itemTemplate.Properties.MaxResource is not null && itemTemplate.Properties.FoodUseTime is not null) { itemProperties.FoodDrink = new() { HpPercent = (int)GetRandomizedResourceValue( itemTemplate.Properties.MaxResource ?? 0, _botConfig.LootItemResourceRandomization[botRole]?.Food ), }; } if (itemTemplate.Parent == BaseClasses.FLASHLIGHT) { // Get chance from botconfig for bot type var lightLaserActiveChance = raidIsNight ? GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50) : GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25); itemProperties.Light = new() { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0, }; } else if (itemTemplate.Parent == BaseClasses.TACTICAL_COMBO) { // Get chance from botconfig for bot type, use 50% if no value found var lightLaserActiveChance = GetBotEquipmentSettingFromConfig( botRole, "laserIsActiveChancePercent", 50 ); itemProperties.Light = new() { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0, }; } if (itemTemplate.Parent == BaseClasses.NIGHTVISION) { // Get chance from botconfig for bot type var nvgActiveChance = raidIsNight ? GetBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90) : GetBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15); itemProperties.Togglable = new() { On = _randomUtil.GetChance100(nvgActiveChance) }; } // Togglable face shield if ((itemTemplate.Properties.HasHinge ?? false) && (itemTemplate.Properties.FaceShieldComponent ?? false)) { // Get chance from botconfig for bot type, use 75% if no value found var faceShieldActiveChance = GetBotEquipmentSettingFromConfig( botRole, "faceShieldIsActiveChancePercent", 75 ); itemProperties.Togglable = new() { On = _randomUtil.GetChance100(faceShieldActiveChance) } ; } return itemProperties; } /// /// Randomize the HpResource for bots e.g (245/400 resources) /// /// Max resource value of medical items /// Value provided from config /// Randomized value from maxHpResource protected double GetRandomizedResourceValue(double maxResource, RandomisedResourceValues randomizationValues) { if (randomizationValues is null) { return maxResource; } if (_randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent)) { return maxResource; } return _randomUtil.GetInt( (int)_randomUtil.GetPercentOfValue(randomizationValues.ResourcePercent, maxResource, 0), (int)maxResource ); } /// /// Get the chance for the weapon attachment or helmet equipment to be set as activated /// /// role of bot with weapon/helmet /// the setting of the weapon attachment/helmet equipment to be activated /// default value for the chance of activation if the botrole or bot equipment role is undefined /// Percent chance to be active protected double GetBotEquipmentSettingFromConfig(string botRole, string setting, double defaultValue) { if (botRole is null) { return defaultValue; } var botEquipmentSettings = _botConfig.Equipment[GetBotEquipmentRole(botRole)]; if (botEquipmentSettings is null) { _logger.Warning( _localisationService.GetText( "bot-missing_equipment_settings", new { BotRole = botRole, Setting = setting, DefaultValue = defaultValue } ) ); return defaultValue; } var props = botEquipmentSettings.GetType().GetProperties(); var propValue = (double?)props.FirstOrDefault(x => x.Name.ToLower() == setting.ToLower()).GetValue(botEquipmentSettings); if (propValue is null) { _logger.Warning( _localisationService.GetText( "bot-missing_equipment_settings_property", new { BotRole = botRole, Setting = setting, DefaultValue = defaultValue } ) ); return defaultValue; } return propValue ?? 0; } /// /// Create a repairable object for a weapon that containers durability + max durability properties /// /// weapon object being generated for /// type of bot being generated for /// Repairable object protected UpdRepairable GenerateWeaponRepairableProperties(TemplateItem itemTemplate, string botRole = null) { var maxDurability = _durabilityLimitsHelper.GetRandomizedMaxWeaponDurability(itemTemplate, botRole); var currentDurability = _durabilityLimitsHelper.GetRandomizedWeaponDurability( itemTemplate, botRole, maxDurability ); return new() { Durability = (int)currentDurability, MaxDurability = (int)maxDurability }; } /// /// Create a repairable object for an armor that containers durability + max durability properties /// /// weapon object being generated for /// type of bot being generated for /// Repairable object protected UpdRepairable GenerateArmorRepairableProperties(TemplateItem itemTemplate, string botRole = null) { double? maxDurability = null; double? currentDurability = null; if (itemTemplate.Properties.ArmorClass == 0) { maxDurability = itemTemplate.Properties.MaxDurability; currentDurability = itemTemplate.Properties.MaxDurability; } else { maxDurability = _durabilityLimitsHelper.GetRandomizedMaxArmorDurability(itemTemplate, botRole); currentDurability = _durabilityLimitsHelper.GetRandomizedArmorDurability( itemTemplate, botRole, maxDurability ?? 0 ); } return new() { Durability = (int)currentDurability, MaxDurability = (int)maxDurability }; } /// /// Can item be added to another item without conflict /// /// Items to check compatibilities with /// Tpl of the item to check for incompatibilities /// Slot the item will be placed into /// false if no incompatibilities, also has incompatibility reason public ChooseRandomCompatibleModResult IsItemIncompatibleWithCurrentItems(List itemsEquipped, string tplToCheck, string equipmentSlot) { throw new NotImplementedException(); } /// /// Convert a bots role to the equipment role used in config/bot.json /// /// Role to convert /// Equipment role (e.g. pmc / assault / bossTagilla) public string GetBotEquipmentRole(string botRole) { string[] pmcs = [_pmcConfig.UsecType.ToLower(), _pmcConfig.BearType.ToLower()]; return pmcs.Contains( botRole.ToLower() ) ? "pmc" : botRole; } /// /// Adds an item with all its children into specified equipmentSlots, wherever it fits. /// /// Slot to add item+children into /// Root item id to use as mod items parentid /// Root itms tpl id /// Item to add /// Inventory to add item+children into /// ItemAddedResult result object public ItemAddedResult AddItemWithChildrenToEquipmentSlot( List equipmentSlots, string rootItemId, string rootItemTplId, List itemWithChildren, BotBaseInventory inventory, HashSet containersIdFull = null) { throw new NotImplementedException(); } /// /// Is the provided item allowed inside a container /// /// Items sub-grid we want to place item inside /// Item tpl being placed /// True if allowed protected bool ItemAllowedInContainer(Grid slotGrid, string itemTpl) { throw new NotImplementedException(); } }