using System.Collections.Frozen; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Utils; namespace SPTarkov.Server.Core.Helpers; [Injectable] public class BotWeaponGeneratorHelper( ISptLogger logger, ItemHelper itemHelper, WeightedRandomHelper weightedRandomHelper, BotGeneratorHelper botGeneratorHelper ) { private static readonly FrozenSet _magCheck = [ "CylinderMagazine", "SpringDrivenCylinder", ]; /// /// Get a randomized number of bullets for a specific magazine /// /// Weights of magazines /// Magazine to generate bullet count for /// Bullet count number public double? GetRandomizedBulletCount(GenerationData magCounts, TemplateItem magTemplate) { var randomizedMagazineCount = GetRandomizedMagazineCount(magCounts); var parentItem = itemHelper.GetItem(magTemplate.Parent).Value; double? chamberBulletCount; if (MagazineIsCylinderRelated(parentItem.Name)) { var firstSlotAmmoTpl = magTemplate .Properties.Cartridges.FirstOrDefault() ?.Props.Filters[0] .Filter.FirstOrDefault() ?? new MongoId(null); var ammoMaxStackSize = itemHelper.GetItem(firstSlotAmmoTpl).Value?.Properties?.StackMaxSize ?? 1; chamberBulletCount = ammoMaxStackSize == 1 ? 1 // Rotating grenade launcher : magTemplate.Properties.Slots.Count; // Shotguns/revolvers. We count the number of camoras as the _max_count of the magazine is 0 } else if (parentItem.Id == BaseClasses.UBGL) { // Underbarrel launchers can only have 1 chambered grenade chamberBulletCount = 1; } else { chamberBulletCount = magTemplate.Properties.Cartridges?[0].MaxCount; } // Get the amount of bullets that would fit in the internal magazine // and multiply by how many magazines were supposed to be created return chamberBulletCount * randomizedMagazineCount; } /// /// Get a randomized count of magazines /// /// Min and max value returned value can be between /// Numerical value of magazine count public int GetRandomizedMagazineCount(GenerationData magCounts) { return (int)weightedRandomHelper.GetWeightedValue(magCounts.Weights); } /// /// Is this magazine cylinder related (revolvers and grenade launchers) /// /// The name of the magazines parent /// True if it is cylinder related public bool MagazineIsCylinderRelated(string magazineParentName) { return _magCheck.Contains(magazineParentName); } /// /// Create a magazine using the parameters given /// /// Tpl of the magazine to create /// Ammo to add to magazine /// Template object of magazine /// Item array public List CreateMagazineWithAmmo( MongoId magazineTpl, MongoId ammoTpl, TemplateItem magTemplate ) { List magazine = [new() { Id = new MongoId(), Template = magazineTpl }]; itemHelper.FillMagazineWithCartridge(magazine, magTemplate, ammoTpl, 1); return magazine; } /// /// Add a specific number of cartridges to a bots inventory (defaults to vest and pockets) /// /// Ammo tpl to add to vest/pockets /// Number of cartridges to add to vest/pockets /// Bot inventory to add cartridges to /// What equipment slots should bullets be added into public void AddAmmoIntoEquipmentSlots( MongoId ammoTpl, int cartridgeCount, BotBaseInventory inventory, HashSet? equipmentSlotsToAddTo = null ) { // null guard input param equipmentSlotsToAddTo ??= [EquipmentSlots.TacticalVest, EquipmentSlots.Pockets]; var ammoItems = itemHelper.SplitStack( new Item { Id = new MongoId(), Template = ammoTpl, Upd = new Upd { StackObjectsCount = cartridgeCount }, } ); foreach (var ammoItem in ammoItems) { var result = botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( equipmentSlotsToAddTo, ammoItem.Id, ammoItem.Template, [ammoItem], inventory ); if (result != ItemAddedResult.SUCCESS) { logger.Debug( $"Unable to add ammo: {ammoItem.Template} to bot inventory, {result.ToString()}" ); if (result == ItemAddedResult.NO_SPACE || result == ItemAddedResult.NO_CONTAINERS) // If there's no space for 1 stack or no containers to hold item, there's no space for the others { break; } } } } }