Bot generation improvements

This commit is contained in:
Chomp
2025-01-19 23:24:06 +00:00
parent 10b4edfd8a
commit e679225064
9 changed files with 262 additions and 127 deletions
@@ -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];
+17 -15
View File
@@ -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(
/// <returns>List of cartridge tpls</returns>
protected List<string> 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;
+1 -1
View File
@@ -260,7 +260,7 @@ public class BotGeneratorHelper(
currentDurability = _durabilityLimitsHelper.GetRandomizedArmorDurability(
itemTemplate,
botRole,
maxDurability
maxDurability.Value
);
}
@@ -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
+189 -25
View File
@@ -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<DurabilityLimitsHelper> _logger,
RandomUtil _randomUtil,
BotHelper _botHelper,
ConfigServer _configServer)
{
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
/// <summary>
/// Get max durability for a weapon based on bot role
/// </summary>
@@ -14,7 +25,26 @@ public class DurabilityLimitsHelper
/// <returns>Max durability of weapon</returns>
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);
}
/// <summary>
@@ -25,7 +55,27 @@ public class DurabilityLimitsHelper
/// <returns>max durability</returns>
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;
}
/// <summary>
@@ -37,7 +87,25 @@ public class DurabilityLimitsHelper
/// <returns>Current weapon durability</returns>
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);
}
/// <summary>
@@ -47,68 +115,164 @@ public class DurabilityLimitsHelper
/// <param name="botRole">Role of bot to get current durability for</param>
/// <param name="maxDurability">Max durability of armor</param>
/// <returns>Current armor durability</returns>
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;
}
}
@@ -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<string, DefaultDurability> 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; }
@@ -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<string, double>();
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<string, double>();
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<string> 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<string, double>();
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);
}
}
}
+1 -3
View File
@@ -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)