diff --git a/Libraries/Core/Generators/BotEquipmentModGenerator.cs b/Libraries/Core/Generators/BotEquipmentModGenerator.cs index 2e5c1b21..eaf37413 100644 --- a/Libraries/Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/Core/Generators/BotEquipmentModGenerator.cs @@ -1119,7 +1119,7 @@ public class BotEquipmentModGenerator( var parentSlotCompatibleItems = request.ParentTemplate.Properties.Slots?.FirstOrDefault( (slot) => string.Equals(slot.Name.ToLower(), request.ModSlot.ToLower(), StringComparison.Ordinal) ) - ?.Props.Filters[0].Filter; + ?.Props.Filters?[0].Filter; // Mod isn't in existing pool, only add if it has no children and exists inside parent filter if ( @@ -1458,7 +1458,7 @@ public class BotEquipmentModGenerator( modPool[cylinderMagTemplate.Id] = new(); foreach (var camora in camoraSlots) { - modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters[0].Filter; + modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters?[0].Filter; } itemModPool = modPool[cylinderMagTemplate.Id]; diff --git a/Libraries/Core/Generators/BotWeaponGenerator.cs b/Libraries/Core/Generators/BotWeaponGenerator.cs index b89ca241..98b14008 100644 --- a/Libraries/Core/Generators/BotWeaponGenerator.cs +++ b/Libraries/Core/Generators/BotWeaponGenerator.cs @@ -1,4 +1,4 @@ -using SptCommon.Annotations; +using SptCommon.Annotations; using Core.Generators.WeaponGen; using Core.Helpers; using Core.Models.Eft.Common; @@ -607,22 +607,24 @@ public class BotWeaponGenerator( /// List of cartridge tpls protected List GetCompatibleCartridgesFromWeaponTemplate(TemplateItem weaponTemplate) { - var cartridges = weaponTemplate.Properties.Chambers?[0].Props?.Filters[0]?.Filter; + var cartridges = weaponTemplate.Properties?.Chambers.FirstOrDefault()?.Props?.Filters?[0].Filter; + if (cartridges is not null) + { + return cartridges; + } + // Fallback to the magazine if possible, e.g. for revolvers + // Grab the magazines template + var firstMagazine = weaponTemplate.Properties.Slots.FirstOrDefault(slot => slot.Name == "mod_magazine"); + var magazineTemplate = _itemHelper.GetItem(firstMagazine.Props.Filters?[0].Filter[0]); + var magProperties = magazineTemplate.Value.Properties; + + // Get the first slots array of cartridges + cartridges = magProperties.Slots.FirstOrDefault()?.Props.Filters?[0].Filter; if (cartridges is null) { - // Fallback to the magazine if possible, e.g. for revolvers - // Grab the magazines template - var firstMagazine = weaponTemplate.Properties.Slots.FirstOrDefault((slot) => slot.Name == "mod_magazine"); - var magazineTemplate = _itemHelper.GetItem(firstMagazine.Props.Filters[0].Filter[0]); - - // Get the first slots array of cartridges - cartridges = magazineTemplate.Value.Properties.Slots[0]?.Props.Filters[0].Filter; - if (cartridges is null) - { - // Normal magazines - // None found, try the cartridges array - cartridges = magazineTemplate.Value.Properties.Cartridges[0]?.Props.Filters[0].Filter; - } + // Normal magazines + // None found, try the cartridges array + cartridges = magProperties.Cartridges.FirstOrDefault()?.Props.Filters[0].Filter; } return cartridges; diff --git a/Libraries/Core/Helpers/BotGeneratorHelper.cs b/Libraries/Core/Helpers/BotGeneratorHelper.cs index 85d6e711..668d87a9 100644 --- a/Libraries/Core/Helpers/BotGeneratorHelper.cs +++ b/Libraries/Core/Helpers/BotGeneratorHelper.cs @@ -260,7 +260,7 @@ public class BotGeneratorHelper( currentDurability = _durabilityLimitsHelper.GetRandomizedArmorDurability( itemTemplate, botRole, - maxDurability + maxDurability.Value ); } diff --git a/Libraries/Core/Helpers/BotWeaponGeneratorHelper.cs b/Libraries/Core/Helpers/BotWeaponGeneratorHelper.cs index 38519ced..c283f697 100644 --- a/Libraries/Core/Helpers/BotWeaponGeneratorHelper.cs +++ b/Libraries/Core/Helpers/BotWeaponGeneratorHelper.cs @@ -1,4 +1,4 @@ -using SptCommon.Annotations; +using SptCommon.Annotations; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; using Core.Models.Utils; @@ -35,7 +35,7 @@ public class BotWeaponGeneratorHelper( double? chamberBulletCount = 0; if (MagazineIsCylinderRelated(parentItem.Name)) { - var firstSlotAmmoTpl = magTemplate.Properties.Cartridges[0].Props.Filters[0].Filter[0]; + var firstSlotAmmoTpl = magTemplate.Properties.Cartridges.FirstOrDefault()?.Props.Filters[0].Filter[0]; var ammoMaxStackSize = _itemHelper.GetItem(firstSlotAmmoTpl).Value?.Properties?.StackMaxSize ?? 1; chamberBulletCount = ammoMaxStackSize == 1 ? 1 // Rotating grenade launcher @@ -48,7 +48,7 @@ public class BotWeaponGeneratorHelper( } else { - chamberBulletCount = magTemplate.Properties.Cartridges[0].MaxCount; + chamberBulletCount = magTemplate.Properties.Cartridges?[0].MaxCount; } // Get the amount of bullets that would fit in the internal magazine diff --git a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs index 3a1ba1e5..2871d8ad 100644 --- a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs +++ b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs @@ -1,11 +1,22 @@ -using SptCommon.Annotations; +using SptCommon.Annotations; using Core.Models.Eft.Common.Tables; +using Core.Models.Spt.Config; +using Core.Models.Utils; +using Core.Servers; +using Core.Utils; namespace Core.Helpers; [Injectable] -public class DurabilityLimitsHelper +public class DurabilityLimitsHelper( + ISptLogger _logger, + RandomUtil _randomUtil, + BotHelper _botHelper, + ConfigServer _configServer) { + + private readonly BotConfig _botConfig = _configServer.GetConfig(); + /// /// Get max durability for a weapon based on bot role /// @@ -14,7 +25,26 @@ public class DurabilityLimitsHelper /// Max durability of weapon public double GetRandomizedMaxWeaponDurability(TemplateItem itemTemplate, string? botRole = null) { - throw new NotImplementedException(); + if (botRole is not null) + { + if (_botHelper.IsBotPmc(botRole)) + { + return GenerateMaxWeaponDurability("pmc"); + } + + if (_botHelper.IsBotBoss(botRole)) + { + return GenerateMaxWeaponDurability("boss"); + } + + if (_botHelper.IsBotFollower(botRole)) + { + return GenerateMaxWeaponDurability("follower"); + } + } + + + return GenerateMaxWeaponDurability(botRole); } /// @@ -25,7 +55,27 @@ public class DurabilityLimitsHelper /// max durability public double GetRandomizedMaxArmorDurability(TemplateItem? itemTemplate, string? botRole = null) { - throw new NotImplementedException(); + var itemMaxDurability = itemTemplate.Properties.MaxDurability.Value; + + if (botRole is not null) + { + if (_botHelper.IsBotPmc(botRole)) + { + return GenerateMaxPmcArmorDurability(itemMaxDurability); + } + + if (_botHelper.IsBotBoss(botRole)) + { + return itemMaxDurability; + } + + if (_botHelper.IsBotFollower(botRole)) + { + return itemMaxDurability; + } + } + + return itemMaxDurability; } /// @@ -37,7 +87,25 @@ public class DurabilityLimitsHelper /// Current weapon durability public double GetRandomizedWeaponDurability(TemplateItem itemTemplate, string? botRole, double maxDurability) { - throw new NotImplementedException(); + if (botRole is not null) + { + if (_botHelper.IsBotPmc(botRole)) + { + return GenerateWeaponDurability("pmc", maxDurability); + } + + if (_botHelper.IsBotBoss(botRole)) + { + return GenerateWeaponDurability("boss", maxDurability); + } + + if (_botHelper.IsBotFollower(botRole)) + { + return GenerateWeaponDurability("follower", maxDurability); + } + } + + return GenerateWeaponDurability(botRole, maxDurability); } /// @@ -47,68 +115,164 @@ public class DurabilityLimitsHelper /// Role of bot to get current durability for /// Max durability of armor /// Current armor durability - public double GetRandomizedArmorDurability(TemplateItem? itemTemplate, string? botRole, double? maxDurability) + public double GetRandomizedArmorDurability(TemplateItem? itemTemplate, string? botRole, double maxDurability) { - throw new NotImplementedException(); + if (botRole is not null) + { + if (_botHelper.IsBotPmc(botRole)) + { + return GenerateArmorDurability("pmc", maxDurability); + } + + if (_botHelper.IsBotBoss(botRole)) + { + return GenerateArmorDurability("boss", maxDurability); + } + + if (_botHelper.IsBotFollower(botRole)) + { + return GenerateArmorDurability("follower", maxDurability); + } + } + + + return GenerateArmorDurability(botRole, maxDurability); } protected double GenerateMaxWeaponDurability(string? botRole = null) { - throw new NotImplementedException(); + var lowestMax = GetLowestMaxWeaponFromConfig(botRole); + var highestMax = GetHighestMaxWeaponDurabilityFromConfig(botRole); + + return _randomUtil.GetInt(lowestMax, highestMax); } protected double GenerateMaxPmcArmorDurability(double itemMaxDurability) { - throw new NotImplementedException(); + var lowestMaxPercent = _botConfig.Durability.Pmc.Armor.LowestMaxPercent; + var highestMaxPercent = _botConfig.Durability.Pmc.Armor.HighestMaxPercent; + var multiplier = _randomUtil.GetInt(lowestMaxPercent, highestMaxPercent); + + return itemMaxDurability * (multiplier / 100); } - protected double GetLowestMaxWeaponFromConfig(string? botRole = null) + protected int GetLowestMaxWeaponFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Weapon.LowestMax; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var durability); + return durability.Weapon.LowestMax; } - protected double GetHighestMaxWeaponDurabilityFromConfig(string? botRole = null) + protected int GetHighestMaxWeaponDurabilityFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Weapon.HighestMax; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var durability); + return durability.Weapon.HighestMax; } protected double GenerateWeaponDurability(string? botRole, double maxDurability) { - throw new NotImplementedException(); + var minDelta = GetMinWeaponDeltaFromConfig(botRole); + var maxDelta = GetMaxWeaponDeltaFromConfig(botRole); + var delta = _randomUtil.GetInt(minDelta, maxDelta); + var result = maxDurability - delta; + var durabilityValueMinLimit = Math.Round( + (GetMinWeaponLimitPercentFromConfig(botRole) / 100) * maxDurability); + + // Don't let weapon dura go below the percent defined in config + return result >= durabilityValueMinLimit ? result : durabilityValueMinLimit; } protected double GenerateArmorDurability(string? botRole, double maxDurability) { - throw new NotImplementedException(); + var minDelta = GetMinArmorDeltaFromConfig(botRole); + var maxDelta = GetMaxArmorDeltaFromConfig(botRole); + var delta = _randomUtil.GetInt(minDelta, maxDelta); + var result = maxDurability - delta; + var durabilityValueMinLimit = Math.Round( + (GetMinArmorLimitPercentFromConfig(botRole) / 100) * maxDurability); + + // Don't let armor dura go below the percent defined in config + return result >= durabilityValueMinLimit ? result : durabilityValueMinLimit; } - protected double GetMinWeaponDeltaFromConfig(string? botRole = null) + protected int GetMinWeaponDeltaFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Weapon.MinDelta; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); + + return value.Weapon.MinDelta; } - protected double GetMaxWeaponDeltaFromConfig(string? botRole = null) + protected int GetMaxWeaponDeltaFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Weapon.HighestMax; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); + + return value.Weapon.HighestMax; } - protected double GetMinArmorDeltaFromConfig(string? botRole = null) + protected int GetMinArmorDeltaFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Armor.MinDelta; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); + + return value.Armor.MinDelta; } - protected double GetMaxArmorDeltaFromConfig(string? botRole = null) + protected int GetMaxArmorDeltaFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Armor.MaxDelta; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); + + return value.Armor.MaxDelta; } protected double GetMinArmorLimitPercentFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Armor.MinLimitPercent; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); + + return value.Armor.MinLimitPercent; } protected double GetMinWeaponLimitPercentFromConfig(string? botRole = null) { - throw new NotImplementedException(); + if (botRole is null or "default") + { + return _botConfig.Durability.Default.Weapon.MinLimitPercent; + } + + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); + + return value.Weapon.MinLimitPercent; } } diff --git a/Libraries/Core/Models/Spt/Config/BotDurability.cs b/Libraries/Core/Models/Spt/Config/BotDurability.cs index 3aea61b9..679cfe0b 100644 --- a/Libraries/Core/Models/Spt/Config/BotDurability.cs +++ b/Libraries/Core/Models/Spt/Config/BotDurability.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Core.Models.Spt.Config; @@ -7,47 +7,11 @@ public record BotDurability [JsonPropertyName("default")] public DefaultDurability Default { get; set; } + [JsonPropertyName("botDurabilities")] + public Dictionary BotDurabilities { get; set; } + [JsonPropertyName("pmc")] public PmcDurability Pmc { get; set; } - - [JsonPropertyName("boss")] - public PmcDurability Boss { get; set; } - - [JsonPropertyName("follower")] - public PmcDurability Follower { get; set; } - - [JsonPropertyName("assault")] - public PmcDurability Assault { get; set; } - - [JsonPropertyName("cursedassault")] - public PmcDurability CursedAssault { get; set; } - - [JsonPropertyName("marksman")] - public PmcDurability Marksman { get; set; } - - [JsonPropertyName("pmcbot")] - public PmcDurability PmcBot { get; set; } - - [JsonPropertyName("arenafighterevent")] - public PmcDurability ArenaFighterEvent { get; set; } - - [JsonPropertyName("arenafighter")] - public PmcDurability ArenaFighter { get; set; } - - [JsonPropertyName("crazyassaultevent")] - public PmcDurability CrazyAssaultEvent { get; set; } - - [JsonPropertyName("exusec")] - public PmcDurability Exusec { get; set; } - - [JsonPropertyName("gifter")] - public PmcDurability Gifter { get; set; } - - [JsonPropertyName("sectantpriest")] - public PmcDurability SectantPriest { get; set; } - - [JsonPropertyName("sectantwarrior")] - public PmcDurability SectantWarrior { get; set; } } /** Durability values to be used when a more specific bot type can't be found */ @@ -72,46 +36,52 @@ public record PmcDurability public record PmcDurabilityArmor { [JsonPropertyName("lowestMaxPercent")] - public double LowestMaxPercent { get; set; } + public int LowestMaxPercent { get; set; } [JsonPropertyName("highestMaxPercent")] - public double HighestMaxPercent { get; set; } + public int HighestMaxPercent { get; set; } [JsonPropertyName("maxDelta")] - public double MaxDelta { get; set; } + public int MaxDelta { get; set; } [JsonPropertyName("minDelta")] - public double MinDelta { get; set; } + public int MinDelta { get; set; } [JsonPropertyName("minLimitPercent")] - public double MinLimitPercent { get; set; } + public int MinLimitPercent { get; set; } } public record ArmorDurability { [JsonPropertyName("maxDelta")] - public double MaxDelta { get; set; } + public int MaxDelta { get; set; } [JsonPropertyName("minDelta")] - public double MinDelta { get; set; } + public int MinDelta { get; set; } [JsonPropertyName("minLimitPercent")] - public double MinLimitPercent { get; set; } + public int MinLimitPercent { get; set; } + + [JsonPropertyName("lowestMaxPercent")] + public int LowestMaxPercent { get; set; } + + [JsonPropertyName("highestMaxPercent")] + public int HighestMaxPercent { get; set; } } public record WeaponDurability { [JsonPropertyName("lowestMax")] - public double LowestMax { get; set; } + public int LowestMax { get; set; } [JsonPropertyName("highestMax")] - public double HighestMax { get; set; } + public int HighestMax { get; set; } [JsonPropertyName("maxDelta")] - public double MaxDelta { get; set; } + public int MaxDelta { get; set; } [JsonPropertyName("minDelta")] - public double MinDelta { get; set; } + public int MinDelta { get; set; } [JsonPropertyName("minLimitPercent")] public double MinLimitPercent { get; set; } diff --git a/Libraries/Core/Services/BotEquipmentFilterService.cs b/Libraries/Core/Services/BotEquipmentFilterService.cs index 1d29ae9b..1f41006a 100644 --- a/Libraries/Core/Services/BotEquipmentFilterService.cs +++ b/Libraries/Core/Services/BotEquipmentFilterService.cs @@ -252,24 +252,23 @@ public class BotEquipmentFilterService if (blacklist is not null) { - foreach (var equipmentSlotKey in baseBotNode.BotInventory.Equipment) + foreach (var equipmentSlotKvP in baseBotNode.BotInventory.Equipment) { - var botEquipment = baseBotNode.BotInventory.Equipment[equipmentSlotKey.Key]; + var botEquipment = baseBotNode.BotInventory.Equipment[equipmentSlotKvP.Key]; // Skip equipment slot if blacklist doesn't exist / is empty - var equipmentSlotBlacklist = blacklist.Equipment[equipmentSlotKey.Key.ToString()]; - if (equipmentSlotBlacklist is null || equipmentSlotBlacklist.Count == 0) + if (!blacklist.Equipment.TryGetValue(equipmentSlotKvP.Key.ToString(), out var equipmentSlotBlacklist)) { continue; } // Filter equipment slot items to just items not in blacklist - baseBotNode.BotInventory.Equipment[equipmentSlotKey.Key] = new Dictionary(); + equipmentSlotKvP.Value.Clear(); foreach (var dict in botEquipment) { if (!equipmentSlotBlacklist.Contains(dict.Key)) { - baseBotNode.BotInventory.Equipment[equipmentSlotKey.Key][dict.Key] = botEquipment[dict.Key]; + equipmentSlotKvP.Value[dict.Key] = botEquipment[dict.Key]; } } } @@ -291,22 +290,24 @@ public class BotEquipmentFilterService { if (whitelist is not null) { - foreach ( var ammoCaliber in baseBotNode.BotInventory.Ammo) { - var botAmmo = baseBotNode.BotInventory.Ammo[ammoCaliber.Key]; + // Loop over each caliber + cartridges of that type + foreach ( var (caliber, cartridges) in baseBotNode.BotInventory.Ammo) { - // Skip cartridge slot if whitelist doesn't exist / is empty - var whiteListedCartridgesForCaliber = whitelist.Cartridge[ammoCaliber.Key]; - if (whiteListedCartridgesForCaliber is null || whiteListedCartridgesForCaliber.Count == 0) + if(!whitelist.Cartridge.TryGetValue(caliber, out var matchingWhitelist)) { + // No cartridge whitelist, move to next cartridge continue; } - // Filter calibre slot items to just items in whitelist - baseBotNode.BotInventory.Ammo[ammoCaliber.Key] = new Dictionary(); - foreach ( var dict in botAmmo) { - if (whitelist.Cartridge[ammoCaliber.Key].Contains(dict.Key)) + // Loop over each cartridge + weight + // Clear all cartridges ready for whitelist to be added + foreach (var ammoKvP in cartridges) + { + // Cartridge not on whitelist + if (!matchingWhitelist.Contains(ammoKvP.Key)) { - baseBotNode.BotInventory.Ammo[ammoCaliber.Key][dict.Key] = botAmmo[dict.Key]; + // Remove + cartridges.Remove(ammoKvP.Key); } } } @@ -316,23 +317,21 @@ public class BotEquipmentFilterService if (blacklist is not null) { - foreach ( var ammoCaliberKey in baseBotNode.BotInventory.Ammo) { - var botAmmo = baseBotNode.BotInventory.Ammo[ammoCaliberKey.Key]; + foreach ( var ammoCaliberKvP in baseBotNode.BotInventory.Ammo) { + var botAmmo = baseBotNode.BotInventory.Ammo[ammoCaliberKvP.Key]; // Skip cartridge slot if blacklist doesn't exist / is empty - var cartridgeCaliberBlacklist = blacklist.Cartridge[ammoCaliberKey.Key]; + blacklist.Cartridge.TryGetValue(ammoCaliberKvP.Key, out List cartridgeCaliberBlacklist); if (cartridgeCaliberBlacklist is null || cartridgeCaliberBlacklist.Count == 0) { continue; } // Filter cartridge slot items to just items not in blacklist - baseBotNode.BotInventory.Ammo[ammoCaliberKey.Key] = new Dictionary(); - foreach ( var dict in botAmmo) { - if (!cartridgeCaliberBlacklist.Contains(dict.Key)) - { - baseBotNode.BotInventory.Ammo[ammoCaliberKey.Key][dict.Key] = botAmmo[dict.Key]; - } + foreach (var blacklistedTpl in cartridgeCaliberBlacklist + .Where(blacklistedTpl => ammoCaliberKvP.Value.ContainsKey(blacklistedTpl))) + { + ammoCaliberKvP.Value.Remove(blacklistedTpl); } } } diff --git a/Libraries/Core/Services/BotNameService.cs b/Libraries/Core/Services/BotNameService.cs index d278e450..7cbd93c4 100644 --- a/Libraries/Core/Services/BotNameService.cs +++ b/Libraries/Core/Services/BotNameService.cs @@ -50,7 +50,6 @@ public class BotNameService( var showTypeInNickname = !botGenerationDetails.IsPlayerScav.GetValueOrDefault(false) && _botConfig.ShowTypeInNickname; var roleShouldBeUnique = uniqueRoles?.Contains(botRole.ToLower()); - var isUnique = true; var attempts = 0; while (attempts <= 5) { @@ -78,8 +77,7 @@ public class BotNameService( if (roleShouldBeUnique.GetValueOrDefault(false)) { // Check name in cache - isUnique = _usedNameCache.Contains(name); - if (!isUnique) + if (_usedNameCache.Contains(name)) { // Not unique if (attempts >= 5) diff --git a/Server/Assets/configs/bot.json b/Server/Assets/configs/bot.json index e0e120de..d8203cd6 100644 --- a/Server/Assets/configs/bot.json +++ b/Server/Assets/configs/bot.json @@ -88,7 +88,8 @@ "minLimitPercent": 15 } }, - "pmc": { + "botDurabilities": { + "pmc": { "armor": { "lowestMaxPercent": 90, "highestMaxPercent": 100, @@ -274,6 +275,7 @@ "minLimitPercent": 15 } } + } }, "lootItemResourceRandomization": { "assault": {