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,