diff --git a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs index 4c1b4a45..171baca7 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs @@ -420,7 +420,7 @@ public class LocationLootGenerator( containerClone.Template.Root = parentId; containerClone.Template.Items[0].Id = parentId; - var containerMap = GetContainerMapping(containerTpl); + var containerMap = _itemHelper.GetContainerMapping(containerTpl); // Choose count of items to add to container var itemCountToAdd = GetWeightedCountOfContainerItems(containerTpl, staticLootDist, locationName); @@ -549,22 +549,6 @@ public class LocationLootGenerator( }; } - /// - /// Get a 2D grid of a container's item slots - /// - /// Tpl id of the container - protected int[][] GetContainerMapping(string containerTpl) - { - // Get template from db - var containerTemplate = _itemHelper.GetItem(containerTpl).Value; - - // Get height/width - var height = containerTemplate.Properties.Grids[0].Props.CellsV; - var width = containerTemplate.Properties.Grids[0].Props.CellsH; - - return _inventoryHelper.GetBlankContainerMap(height.Value, width.Value); - } - /// /// Look up a containers itemcountDistribution data and choose an item count based on the found weights /// diff --git a/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs index fedcfdc8..9276e8f4 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs @@ -36,9 +36,9 @@ public class LootGenerator( /// /// parameters to adjust how loot is generated /// An array of loot items - public List CreateRandomLoot(LootRequest options) + public List> CreateRandomLoot(LootRequest options) { - var result = new List(); + var result = new List>(); var itemTypeCounts = InitItemLimitCounter(options.ItemLimits); // Handle sealed weapon containers @@ -58,7 +58,7 @@ public class LootGenerator( { // Choose one at random + add to results array var chosenSealedContainer = _randomUtil.GetArrayValue(sealedWeaponContainerPool); - result.Add( + result.Add([ new Item { Id = _hashUtil.Generate(), @@ -69,7 +69,7 @@ public class LootGenerator( SpawnedInSession = true } } - ); + ]); } } @@ -175,9 +175,9 @@ public class LootGenerator( /// /// Dictionary of item tpls with minmax values /// Array of Item - public List CreateForcedLoot(Dictionary> forcedLootDict) + public List> CreateForcedLoot(Dictionary> forcedLootDict) { - var result = new List(); + var result = new List>(); var forcedItems = forcedLootDict; @@ -199,7 +199,7 @@ public class LootGenerator( }; var splitResults = _itemHelper.SplitStack(newLootItem); - result.AddRange(splitResults); + result.Add(splitResults); } return result; @@ -319,7 +319,7 @@ public class LootGenerator( /// true if item was valid and added to pool protected bool FindAndAddRandomItemToLoot(List items, Dictionary itemTypeCounts, LootRequest options, - List result) + List> result) { var randomItem = _randomUtil.GetArrayValue(items); @@ -353,7 +353,7 @@ public class LootGenerator( } newLootItem.Template = randomItem.Id; - result.Add(newLootItem); + result.Add([newLootItem]); if (randomItemLimitCount is not null) // Increment item count as it's in limit array @@ -396,7 +396,7 @@ public class LootGenerator( protected bool FindAndAddRandomPresetToLoot(List presetPool, Dictionary itemTypeCounts, HashSet itemBlacklist, - List result) + List> result) { // Choose random preset and get details from item db using encyclopedia value (encyclopedia === tplId) var chosenPreset = _randomUtil.GetArrayValue(presetPool); @@ -457,10 +457,7 @@ public class LootGenerator( _itemHelper.SetFoundInRaid(presetAndMods); // Add chosen preset tpl to result array - foreach (var item in presetAndMods) - { - result.Add(item); - } + result.Add(presetAndMods); if (itemLimitCount is not null) // Increment item count as item has been chosen and its inside itemLimitCount dictionary diff --git a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs index 64063bc1..d4a45a18 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs @@ -795,27 +795,6 @@ public class InventoryHelper( ]; } - /// - /// Get a blank two-dimensional representation of a container - /// - /// Horizontal size of container - /// Vertical size of container - /// Two-dimensional representation of container - public int[][] GetBlankContainerMap(int containerH, int containerY) - { - //var x = new int[containerY][]; - //for (int i = 0; i < containerY; i++) - //{ - // x[i] = new int[containerH]; - //} - - //return x; - - return Enumerable.Range(0, containerY) - .Select(i => new int[containerH]) - .ToArray(); - } - /// /// Get a 2d mapping of a container with what grid slots are filled /// @@ -827,7 +806,7 @@ public class InventoryHelper( public int[][] GetContainerMap(int containerH, int containerV, List itemList, string containerId) { // Create blank 2d map of container - var container2D = GetBlankContainerMap(containerH, containerV); + var container2D = _itemHelper.GetBlankContainerMap(containerH, containerV); // Get all items in players inventory keyed by their parentId and by ItemId var inventoryItemHash = GetInventoryItemHash(itemList); @@ -1026,7 +1005,7 @@ public class InventoryHelper( var containerH = firstContainerGrid.Props.CellsH; var containerV = firstContainerGrid.Props.CellsV; - return GetBlankContainerMap(containerH.Value, containerV.Value); + return _itemHelper.GetBlankContainerMap(containerH.Value, containerV.Value); } /// diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs index 99090494..3125c474 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs @@ -1203,8 +1203,10 @@ public class ItemHelper( { if (IsOfBaseclasses(item.Template, [BaseClasses.MONEY, BaseClasses.AMMO])) { - item.Upd ??= new Upd(); - item.Upd.SpawnedInSession = null; + if (item.Upd is not null) + { + item.Upd.SpawnedInSession = null; + } continue; } @@ -2181,6 +2183,43 @@ public class ItemHelper( } } } + + /// + /// Get a 2D grid of a container's item slots + /// + /// Tpl id of the container + public int[][] GetContainerMapping(string containerTpl) + { + // Get template from db + var containerTemplate = GetItem(containerTpl).Value; + + // Get height/width + var height = containerTemplate.Properties.Grids[0].Props.CellsV; + var width = containerTemplate.Properties.Grids[0].Props.CellsH; + + return GetBlankContainerMap(height.Value, width.Value); + } + + /// + /// Get a blank two-dimensional representation of a container + /// + /// Horizontal size of container + /// Vertical size of container + /// Two-dimensional representation of container + public int[][] GetBlankContainerMap(int containerH, int containerY) + { + //var x = new int[containerY][]; + //for (int i = 0; i < containerY; i++) + //{ + // x[i] = new int[containerH]; + //} + + //return x; + + return Enumerable.Range(0, containerY) + .Select(i => new int[containerH]) + .ToArray(); + } } public class ItemSize diff --git a/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs b/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs index 5d85185c..8d71c2c6 100644 --- a/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs @@ -3,6 +3,7 @@ using SPTarkov.Server.Core.Generators; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Location; +using SPTarkov.Server.Core.Models.Eft.Player; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Services; @@ -20,6 +21,7 @@ public class AirdropService( LootGenerator _lootGenerator, HashUtil _hashUtil, WeightedRandomHelper _weightedRandomHelper, + ContainerHelper _containerHelper, LocalisationService _localisationService, ItemFilterService _itemFilterService, ItemHelper _itemHelper) @@ -59,18 +61,24 @@ public class AirdropService( var airdropConfig = GetAirdropLootConfigByType(airdropType); // generate loot to put into airdrop crate - var crateLoot = airdropConfig.UseForcedLoot.GetValueOrDefault(false) + var crateLootPool = airdropConfig.UseForcedLoot.GetValueOrDefault(false) ? _lootGenerator.CreateForcedLoot(airdropConfig.ForcedLoot) : _lootGenerator.CreateRandomLoot(airdropConfig); // Create airdrop crate and add to result in first spot var airdropCrateItem = GetAirdropCrateItem(airdropType); - // Add crate to front of list - crateLoot.Insert(0, airdropCrateItem); + // Filter loot pool to just items that fit crate + var crateLoot = GetLootThatFitsContainer(airdropCrateItem, crateLootPool); - // Re-parent loot items to crate we added above - foreach (var item in crateLoot) + // Flatten loot into single array ready to be returned + var flattenedCrateLoot = crateLoot.SelectMany(x => x).ToList(); + + // Add crate to front of loot rewards + flattenedCrateLoot.Insert(0, airdropCrateItem); + + // Re-parent loot items to crate we just added + foreach (var item in flattenedCrateLoot) { if (item.Id == airdropCrateItem.Id) // Crate itself, don't alter @@ -79,7 +87,7 @@ public class AirdropService( } // no parentId = root item, make item have crate as parent - if (item.ParentId is null) + if (string.IsNullOrEmpty(item.ParentId)) { item.ParentId = airdropCrateItem.Id; item.SlotId = "main"; @@ -89,10 +97,49 @@ public class AirdropService( return new GetAirdropLootResponse { Icon = airdropConfig.Icon, - Container = crateLoot + Container = flattenedCrateLoot }; } + /// + /// Check if the items provided fit into the passed in container + /// + /// Crate item to fit items into + /// Item pool to try and fit into container + /// Items that will fit container + protected List> GetLootThatFitsContainer(Item container, List> crateLootPool) + { + var lootResult = new List>(); + var containerMap = _itemHelper.GetContainerMapping(container.Template); + + var failedToFitAttemptCount = 0; + foreach (var itemAndChildren in crateLootPool) + { + var itemSize = _itemHelper.GetItemSize(itemAndChildren, itemAndChildren[0].Id); + + // look for open slot to put chosen item into + var result = _containerHelper.FindSlotForItem(containerMap, itemSize.Width, itemSize.Height); + if (result.Success.GetValueOrDefault(false)) + { + // It Fits! + lootResult.AddRange(itemAndChildren); + + continue; + } + + if (failedToFitAttemptCount > 3) + // x attempts to fit an item, container is probably full, stop trying to add more + { + break; + } + + // Can't fit item, skip + failedToFitAttemptCount++; + } + + return lootResult; + } + /// /// Create a container create item based on passed in airdrop type /// diff --git a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs index bdf850db..ec5a7db7 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs @@ -503,12 +503,15 @@ public class LocationLifecycleService var mailableLoot = new List(); var parentId = _hashUtil.Generate(); - foreach (var item in loot) + foreach (var itemAndChildren in loot) { - item.ParentId = parentId; - mailableLoot.Add(item); + // Set all root items parent to new id + itemAndChildren[0].ParentId = parentId; } + // Flatten + mailableLoot.AddRange(loot.SelectMany(x => x)); + // Send message from fence giving player reward generated above _mailSendService.SendLocalisedNpcMessageToPlayer( sessionId,