Fix types, Implement botGenHelper

This commit is contained in:
CWX
2025-01-15 12:24:37 +00:00
parent eb590db695
commit 9474487bdd
4 changed files with 262 additions and 41 deletions
+255 -34
View File
@@ -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<EquipmentSlots>(){
var excludedSlots = new List<EquipmentSlots>()
{
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<string, double>{ [ItemTpl.POCKETS_1X4_TUE] = 1 }
? new Dictionary<string, double> { [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
/// <param name="botRole">Role of bot vests are being filtered for</param>
public void FilterRigsToThoseWithProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole)
{
throw new NotImplementedException();
}
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> 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;
}
/// <summary>
@@ -360,9 +374,20 @@ public class BotInventoryGenerator
/// <param name="templateEquipment">Equpiment to filter TacticalVest of</param>
/// <param name="botRole">Role of bot vests are being filtered for</param>
/// <param name="allowEmptyRequest">Should the function return all rigs when 0 unarmored are found</param>
public void FilterRigsTothoseWithoutProtection(Equipment templateEquipment, string botRole, bool allowEmptyRequest = false)
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> 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;
}
/// <summary>
@@ -373,7 +398,129 @@ public class BotInventoryGenerator
public bool GenerateEquipment(GenerateEquipmentProperties settings)
{
_logger.Error("NOT IMPLEMENTED - GenerateEquipment");
return true;
List<string> 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<string>(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;
}
/// <summary>
@@ -384,7 +531,19 @@ public class BotInventoryGenerator
/// <returns>Filtered pool of mods</returns>
public Dictionary<string, List<string>> GetFilteredDynamicModsForItem(string itemTpl, Dictionary<string, List<string>> equipmentBlacklist)
{
throw new NotImplementedException();
var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl);
foreach (var modSlot in modPool.Keys ?? Enumerable.Empty<string>())
{
var blacklistedMods = equipmentBlacklist[modSlot] ?? [];
var filteredMods = modPool[modSlot].Where((slotName) => !blacklistedMods.Contains(slotName));
if (filteredMods.Count() > 0)
{
modPool[modSlot] = filteredMods.ToList();
}
}
return modPool;
}
/// <summary>
@@ -399,10 +558,27 @@ public class BotInventoryGenerator
/// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param>
/// <param name="botLevel">level of bot having weapon generated</param>
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
);
}
}
}
/// <summary>
@@ -410,9 +586,30 @@ public class BotInventoryGenerator
/// </summary>
/// <param name="equipmentChances">Chances bot has certain equipment</param>
/// <returns>What slots bot should have weapons generated for</returns>
public object GetDesiredWeaponsForBot(Chances equipmentChances) // TODO: Type fuckery { slot: EquipmentSlots; shouldSpawn: boolean }[]
public List<DesiredWeapons> 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
}
];
}
/// <summary>
@@ -427,10 +624,34 @@ public class BotInventoryGenerator
/// <param name="isPmc">Is the bot being generated as a pmc</param>
/// <param name="itemGenerationWeights"></param>
/// <param name="botLevel"></param>
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; }
}
+2 -2
View File
@@ -29,7 +29,7 @@ public class BotWeaponGenerator
/// <param name="isPmc">Is weapon generated for a pmc</param>
/// <param name="botLevel"></param>
/// <returns>GenerateWeaponResult object</returns>
public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotBaseInventory botTemplateInventory, string weaponParentId,
public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotTypeInventory botTemplateInventory, string weaponParentId,
Dictionary<string, double> modChances, string botRole, bool isPmc, int botLevel)
{
throw new NotImplementedException();
@@ -41,7 +41,7 @@ public class BotWeaponGenerator
/// <param name="equipmentSlot">Primary/secondary/holster</param>
/// <param name="botTemplateInventory">e.g. assault.json</param>
/// <returns>Weapon template</returns>
public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotBaseInventory botTemplateInventory)
public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotTypeInventory botTemplateInventory)
{
throw new NotImplementedException();
}
+2 -1
View File
@@ -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
/// <param name="tplToCheck">Tpl of the item to check for incompatibilities</param>
/// <param name="equipmentSlot">Slot the item will be placed into</param>
/// <returns>false if no incompatibilities, also has incompatibility reason</returns>
public object IsItemIncompatibleWithCurrentItems(List<Item> itemsEquipped, string tplToCheck, string equipmentSlot)
public ChooseRandomCompatibleModResult IsItemIncompatibleWithCurrentItems(List<Item> itemsEquipped, string tplToCheck, string equipmentSlot)
{
throw new NotImplementedException();
}
+3 -4
View File
@@ -20,7 +20,7 @@ public class WeightedRandomHelper
/// </summary>
/// <param name="values">Items and weights to use</param>
/// <returns>Chosen item from array</returns>
public T GetWeightedValue<T>(Dictionary<T, int> values) where T : notnull
public T GetWeightedValue<T>(Dictionary<T, double> values) where T : notnull
{
var itemKeys = values.Keys.ToList();
var weights = values.Values.ToList();
@@ -28,7 +28,6 @@ public class WeightedRandomHelper
var chosenItem = WeightedRandom<T>(itemKeys, weights);
return chosenItem.Item;
// SORRY IF THIS BLEW UP, I DONT SEE A REASON ITS GENERIC - CWX
}
/// <summary>
@@ -45,7 +44,7 @@ public class WeightedRandomHelper
/// <param name="items">List of items</param>
/// <param name="weights">List of weights</param>
/// <returns>Dictionary with item and index</returns>
public WeightedRandomResult<T> WeightedRandom<T>(List<T> items, List<int> weights)
public WeightedRandomResult<T> WeightedRandom<T>(List<T> items, List<double> weights)
{
if (items.Count == 0)
{
@@ -66,7 +65,7 @@ public class WeightedRandomHelper
List<int> 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)]