Moved container methods around

Added code to validate items fit into airdrop crate

Updated `CreateForcedLoot` and `CreateRandomLoot` to return items with their children

Updated `SetFoundInRaid` to not add a upd to money/currency if it doesn't already have one
This commit is contained in:
Chomp
2025-05-02 19:48:28 +01:00
parent ff40b2d1c7
commit 344ea6de35
6 changed files with 115 additions and 66 deletions
@@ -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(
};
}
/// <summary>
/// Get a 2D grid of a container's item slots
/// </summary>
/// <param name="containerTpl">Tpl id of the container</param>
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);
}
/// <summary>
/// Look up a containers itemcountDistribution data and choose an item count based on the found weights
/// </summary>
@@ -36,9 +36,9 @@ public class LootGenerator(
/// </summary>
/// <param name="options">parameters to adjust how loot is generated</param>
/// <returns>An array of loot items</returns>
public List<Item> CreateRandomLoot(LootRequest options)
public List<List<Item>> CreateRandomLoot(LootRequest options)
{
var result = new List<Item>();
var result = new List<List<Item>>();
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(
/// </summary>
/// <param name="forcedLootDict">Dictionary of item tpls with minmax values</param>
/// <returns>Array of Item</returns>
public List<Item> CreateForcedLoot(Dictionary<string, MinMax<int>> forcedLootDict)
public List<List<Item>> CreateForcedLoot(Dictionary<string, MinMax<int>> forcedLootDict)
{
var result = new List<Item>();
var result = new List<List<Item>>();
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(
/// <returns>true if item was valid and added to pool</returns>
protected bool FindAndAddRandomItemToLoot(List<TemplateItem> items, Dictionary<string, ItemLimit> itemTypeCounts,
LootRequest options,
List<Item> result)
List<List<Item>> 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<Preset> presetPool,
Dictionary<string, ItemLimit> itemTypeCounts,
HashSet<string> itemBlacklist,
List<Item> result)
List<List<Item>> 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
@@ -795,27 +795,6 @@ public class InventoryHelper(
];
}
/// <summary>
/// Get a blank two-dimensional representation of a container
/// </summary>
/// <param name="containerH">Horizontal size of container</param>
/// <param name="containerY">Vertical size of container</param>
/// <returns>Two-dimensional representation of container</returns>
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();
}
/// <summary>
/// Get a 2d mapping of a container with what grid slots are filled
/// </summary>
@@ -827,7 +806,7 @@ public class InventoryHelper(
public int[][] GetContainerMap(int containerH, int containerV, List<Item> 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);
}
/// <summary>
@@ -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(
}
}
}
/// <summary>
/// Get a 2D grid of a container's item slots
/// </summary>
/// <param name="containerTpl">Tpl id of the container</param>
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);
}
/// <summary>
/// Get a blank two-dimensional representation of a container
/// </summary>
/// <param name="containerH">Horizontal size of container</param>
/// <param name="containerY">Vertical size of container</param>
/// <returns>Two-dimensional representation of container</returns>
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
@@ -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
};
}
/// <summary>
/// Check if the items provided fit into the passed in container
/// </summary>
/// <param name="container">Crate item to fit items into</param>
/// <param name="crateLootPool">Item pool to try and fit into container</param>
/// <returns>Items that will fit container</returns>
protected List<List<Item>> GetLootThatFitsContainer(Item container, List<List<Item>> crateLootPool)
{
var lootResult = new List<List<Item>>();
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;
}
/// <summary>
/// Create a container create item based on passed in airdrop type
/// </summary>
@@ -503,12 +503,15 @@ public class LocationLifecycleService
var mailableLoot = new List<Item>();
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,