From 4e2d4dc708850a82b287fb95577a3ca7ba97523d Mon Sep 17 00:00:00 2001 From: Chomp <27521899+chompDev@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:36:13 +0100 Subject: [PATCH] Rewrote container item space system to use 2 dimensional arrays (#442) * Rewrote container item space system to use 2 dimensional arrays * Moved container helper code into extension methods * Reduced amount of parameters passed into `RowIsFull` * Skip root trader items * Remove debug --------- Co-authored-by: Chomp --- .../Extensions/ContainerExtensions.cs | 239 ++++++++++++++++++ .../Extensions/ItemExtensions.cs | 41 ++- .../Generators/LocationLootGenerator.cs | 8 +- .../Helpers/BotGeneratorHelper.cs | 13 +- .../Helpers/ContainerHelper.cs | 211 ---------------- .../Helpers/InventoryHelper.cs | 192 +++++++------- .../Helpers/ItemHelper.cs | 14 +- .../Helpers/SecureContainerHelper.cs | 30 --- .../Models/Common/MongoId.cs | 5 - .../Models/Spt/Inventory/FindSlotResult.cs | 33 +++ .../Services/AirdropService.cs | 13 +- .../Services/CircleOfCultistService.cs | 10 +- 12 files changed, 416 insertions(+), 393 deletions(-) create mode 100644 Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs delete mode 100644 Libraries/SPTarkov.Server.Core/Helpers/ContainerHelper.cs delete mode 100644 Libraries/SPTarkov.Server.Core/Helpers/SecureContainerHelper.cs create mode 100644 Libraries/SPTarkov.Server.Core/Models/Spt/Inventory/FindSlotResult.cs diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs new file mode 100644 index 00000000..14ffd2cb --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs @@ -0,0 +1,239 @@ +using SPTarkov.Server.Core.Models.Spt.Inventory; + +namespace SPTarkov.Server.Core.Extensions +{ + public static class ContainerExtensions + { + /// + /// Finds a slot for an item in a given 2D container map + /// + /// List of container with positions filled/free + /// Width of item + /// Height of item + /// Location to place item in container + public static FindSlotResult FindSlotForItem( + this int[,] container2D, + int? itemX, + int? itemY + ) + { + // Assume not rotated + var rotation = false; + + // Find the min volume the item will take up + var minVolume = (itemX < itemY ? itemX : itemY) - 1; + var containerY = container2D.GetLength(0); // rows + var containerX = container2D.GetLength(1); // columns + var limitY = containerY - minVolume; + var limitX = containerX - minVolume; + + // Every x+y slot taken up in container, exit + if (ContainerIsFull(container2D)) + { + return new FindSlotResult(false); + } + + // Down = y, iterate over rows + for (var y = 0; y < limitY; y++) + { + if (RowIsFull(container2D, y)) + { + continue; + } + + // Left to right across columns, look for free position + for (var x = 0; x < limitX; x++) + { + // Does item fit + if ( + CanItemBePlacedInContainerAtPosition( + container2D, + x, + y, + itemX.Value, + itemY.Value + ) + ) + { + // Success, found a spot it fits + return new FindSlotResult(true, x, y, rotation); + } + + if (!ItemBiggerThan1X1(itemX.Value, itemY.Value)) + { + // Doesn't fit AND rotating won't help + continue; + } + + // Rotate item by swapping x and y item values + if ( + CanItemBePlacedInContainerAtPosition( + container2D, + x, + y, + itemY.Value, // Swapped + itemX.Value // Swapped + ) + ) + { + // Found a position for the item when rotated + rotation = true; + return new FindSlotResult(true, x, y, rotation); + } + } + } + + // Tried all possible positions, nothing big enough for item + return new FindSlotResult(false); + } + + /// + /// Find a free slot for an item to be placed at + /// + /// Container to place item in + /// Container x size + /// Container y size + /// Items width + /// Items height + /// is item rotated + public static void FillContainerMapWithItem( + this int[,] container2D, + int x, + int y, + int? itemXWidth, + int? itemYHeight, + bool isRotated + ) + { + // Swap height/width if item needs to be rotated to fit + var itemWidth = isRotated ? itemYHeight : itemXWidth; + var itemHeight = isRotated ? itemXWidth : itemYHeight; + + for (var tmpY = y; tmpY < y + itemHeight; tmpY++) + { + for (var tmpX = x; tmpX < x + itemWidth; tmpX++) + { + if (container2D[tmpY, tmpX] == 0) + { + // Flag slot as used + container2D[tmpY, tmpX] = 1; + } + else + { + throw new Exception( + $"Slot at({x}, {y}) is already filled. Cannot fit a {itemXWidth} by {itemYHeight} item" + ); + } + } + } + } + + /// + /// Is the requested row full + /// + /// Container to check + /// Index of row to check + /// True = full + private static bool RowIsFull(int[,] container2D, int rowIndex) + { + var rowFull = true; + var containerColumnCount = container2D.GetLength(0); // rows + for (var col = 0; col < containerColumnCount; col++) + { + if (container2D[rowIndex, col] == 0) + { + rowFull = false; + break; + } + } + + return rowFull; + } + + /// + /// Is every slot in container full + /// + /// Container to check + /// True = full + private static bool ContainerIsFull(int[,] container2D) + { + var containerY = container2D.GetLength(0); // rows + var containerX = container2D.GetLength(1); // columns + var containerFull = true; + for (var y = 0; y < containerY; y++) + { + for (var x = 0; x < containerX; x++) + { + if (container2D[y, x] == 0) + { + containerFull = false; + break; + } + } + if (!containerFull) + { + break; + } + } + + return containerFull; + } + + /// + /// Is the item size values passed in bigger than 1x1 + /// + /// Width of item + /// Height of item + /// True = bigger than 1x1 + private static bool ItemBiggerThan1X1(int itemWidth, int itemHeight) + { + return itemWidth + itemHeight > 2; + } + + /// + /// Can an item of specified size be placed inside a 2d container at a specific position + /// + /// Container to find space in + /// Starting x position for item + /// Starting y position for item + /// Items width + /// Items height + /// True - slot found + private static bool CanItemBePlacedInContainerAtPosition( + int[,] container, + int startXPos, + int startYPos, + int itemXWidth, + int itemYHeight + ) + { + var containerHeight = container.GetLength(0); // Rows + var containerWidth = container.GetLength(1); // Columns + + // Check item isn't bigger than container when at position + if ( + startXPos + itemXWidth > containerWidth + || startYPos + itemYHeight > containerHeight + ) + { + // Item is bigger than container, will never fit + return false; + } + + // Check each slot, is any filled + for (var checkY = startYPos; checkY < startYPos + itemYHeight; checkY++) + { + for (var checkX = startXPos; checkX < startXPos + itemXWidth; checkX++) + { + if (container[checkY, checkX] == 1) + { + // Occupied by something + return false; + } + } + } + + return true; // Slot is free + } + } +} diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs index f32869be..1c513589 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs @@ -1,4 +1,6 @@ -using SPTarkov.Server.Core.Models.Common; +using System.Text.Json; +using SPTarkov.Common.Extensions; +using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; @@ -342,5 +344,42 @@ namespace SPTarkov.Server.Core.Extensions ExtensionData = item.ExtensionData, }; } + + public static ItemLocation? GetParsedLocation(this Item item) + { + if (item.Location is null) + { + return null; + } + + if (item.Location is JsonElement element) + { + // TODO: when is this true + return element.ToObject(); + } + + return (ItemLocation)item.Location; + } + + /// + /// Get a list of the item IDs (NOT tpls) inside a secure container + /// + /// Inventory items to look for secure container in + /// List of ids + public static List GetSecureContainerItems(this List items) + { + var secureContainer = items.First(x => x.SlotId == "SecuredContainer"); + + // No container found, drop out + if (secureContainer is null) + { + return []; + } + + var itemsInSecureContainer = items.FindAndReturnChildrenByItems(secureContainer.Id); + + // Return all items returned and exclude the secure container item itself + return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToList(); + } } } diff --git a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs index eaae244d..c006184a 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs @@ -21,10 +21,8 @@ namespace SPTarkov.Server.Core.Generators; public class LocationLootGenerator( ISptLogger _logger, RandomUtil _randomUtil, - HashUtil _hashUtil, ItemHelper _itemHelper, DatabaseService _databaseService, - ContainerHelper _containerHelper, PresetHelper _presetHelper, ServerLocalisationService _serverLocalisationService, SeasonalEventService _seasonalEventService, @@ -600,8 +598,7 @@ public class LocationLootGenerator( : chosenItemWithChildren.Items; // look for open slot to put chosen item into - var result = _containerHelper.FindSlotForItem( - containerMap, + var result = containerMap.FindSlotForItem( chosenItemWithChildren.Width, chosenItemWithChildren.Height ); @@ -620,8 +617,7 @@ public class LocationLootGenerator( } // Find somewhere for item inside container - _containerHelper.FillContainerMapWithItem( - containerMap, + containerMap.FillContainerMapWithItem( result.X.Value, result.Y.Value, chosenItemWithChildren.Width, diff --git a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs index 59f57a90..9f782d85 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs @@ -21,7 +21,6 @@ public class BotGeneratorHelper( DurabilityLimitsHelper _durabilityLimitsHelper, ItemHelper _itemHelper, InventoryHelper _inventoryHelper, - ContainerHelper _containerHelper, ProfileActivityService _profileActivityService, ServerLocalisationService _serverLocalisationService, ConfigServer _configServer @@ -665,7 +664,7 @@ public class BotGeneratorHelper( } // Get x/y grid size of item - var itemSize = _inventoryHelper.GetItemSize( + var (width, height) = _inventoryHelper.GetItemSize( rootItemTplId, rootItemId, itemWithChildren @@ -680,7 +679,7 @@ public class BotGeneratorHelper( if ( slotGrid.Props?.CellsH == 0 || slotGrid.Props?.CellsV == 0 - || itemSize[0] * itemSize[1] > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH + || width * height > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH ) { continue; @@ -718,11 +717,7 @@ public class BotGeneratorHelper( ); // Try to fit item into grid - var findSlotResult = _containerHelper.FindSlotForItem( - slotGridMap, - itemSize[0], - itemSize[1] - ); + var findSlotResult = slotGridMap.FindSlotForItem(width, height); // Free slot found, add item if (findSlotResult.Success ?? false) @@ -768,7 +763,7 @@ public class BotGeneratorHelper( } // if the item was a one by one, we know it must be full. Or if the maps cant find a slot for a one by one - if (itemSize[0] == 1 && itemSize[1] == 1) + if (width == 1 && height == 1) { containersIdFull.Add(equipmentSlotId.ToString()); } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ContainerHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ContainerHelper.cs deleted file mode 100644 index 7e99615f..00000000 --- a/Libraries/SPTarkov.Server.Core/Helpers/ContainerHelper.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System.Text.Json.Serialization; -using SPTarkov.DI.Annotations; - -namespace SPTarkov.Server.Core.Helpers; - -[Injectable] -public class ContainerHelper -{ - /// - /// Finds a slot for an item in a given 2D container map - /// - /// List of container with positions filled/free - /// Width of item - /// Height of item - /// Location to place item in container - public FindSlotResult FindSlotForItem(int[][] container2D, int? itemX, int? itemY) - { - // Assume not rotated - var rotation = false; - - var minVolume = (itemX < itemY ? itemX : itemY) - 1; - var containerY = container2D.Length; - var containerX = container2D[0].Length; - var limitY = containerY - minVolume; - var limitX = containerX - minVolume; - - // Every x+y slot taken up in container, exit - if (container2D.All(x => x.All(y => y == 1))) - { - return new FindSlotResult(false); - } - - // Down = y - for (var y = 0; y < limitY; y++) - { - if (container2D[y].All(x => x == 1)) - // Every item in row is full, skip row - { - continue; - } - - // Go left to right across x-axis looking for free position - for (var x = 0; x < limitX; x++) - { - if ( - CanItemBePlacedInContainerAtPosition( - container2D, - containerX, - containerY, - x, - y, - itemX!.Value, - itemY!.Value - ) - ) - { - // Success, return result - return new FindSlotResult(true, x, y, rotation); - } - - if (ItemBiggerThan1X1(itemX!.Value, itemY!.Value)) - { - // Pointless rotating a 1x1, try next position across - continue; - } - - // Bigger than 1x1, try rotating by swapping x and y values - if ( - !CanItemBePlacedInContainerAtPosition( - container2D, - containerX, - containerY, - x, - y, - itemY!.Value, - itemX!.Value - ) - ) - { - continue; - } - - // Found a position for item when rotated - rotation = true; - - return new FindSlotResult(true, x, y, rotation); - } - } - - // Tried all possible positions, nothing big enough for item - return new FindSlotResult(false); - } - - protected static bool ItemBiggerThan1X1(int itemWidth, int itemHeight) - { - return itemWidth + itemHeight > 2; - } - - /// - /// Can an item of specified size be placed inside a 2d container at a specific position - /// - /// Container to find space in - /// Container x size - /// Container y size - /// Starting x position for item - /// Starting y position for item - /// Items width - /// Items height - /// True - slot found - protected bool CanItemBePlacedInContainerAtPosition( - int[][] container, - int containerWidth, - int containerHeight, - int startXPos, - int startYPos, - int itemWidth, - int itemHeight - ) - { - // Check item isn't bigger than container when at position - if (startXPos + itemWidth > containerWidth || startYPos + itemHeight > containerHeight) - { - return false; - } - - // Check each position item will take up in container, go across and then down - for (var itemY = startYPos; itemY < startYPos + itemHeight; itemY++) - { - for (var itemX = startXPos; itemX < startXPos + itemWidth; itemX++) - { - // e,g for a 2x2 item; [0,0] then [0,1] then [1,0] then [1,1] - if (container[itemY][itemX] != 0) - { - // x,y Position blocked, can't place - return false; - } - } - } - - return true; - } - - /// - /// Find a free slot for an item to be placed at - /// - /// Container to place item in - /// Container x size - /// Container y size - /// Items width - /// Items height - /// is item rotated - public void FillContainerMapWithItem( - int[][] container2D, - int x, - int y, - int? itemW, - int? itemH, - bool rotate - ) - { - // Swap height/width if we want to fit it in rotated - var itemWidth = rotate ? itemH : itemW; - var itemHeight = rotate ? itemW : itemH; - - for (var tmpY = y; tmpY < y + itemHeight; tmpY++) - for (var tmpX = x; tmpX < x + itemWidth; tmpX++) - { - if (container2D[tmpY][tmpX] == 0) - // Flag slot as used - { - container2D[tmpY][tmpX] = 1; - } - else - { - throw new Exception( - $"Slot at({x}, {y}) is already filled. Cannot fit a {itemW} by {itemH} item" - ); - } - } - } -} - -public class FindSlotResult -{ - public FindSlotResult(bool success) - { - Success = success; - } - - public FindSlotResult(bool success, int x, int y, bool rotation) - { - Success = success; - X = x; - Y = y; - Rotation = rotation; - } - - public FindSlotResult() { } - - [JsonPropertyName("success")] - public bool? Success { get; set; } - - [JsonPropertyName("x")] - public int? X { get; set; } - - [JsonPropertyName("y")] - public int? Y { get; set; } - - [JsonPropertyName("rotation")] - public bool? Rotation { get; set; } -} diff --git a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs index c1fe39b2..21b1ea7d 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs @@ -1,7 +1,5 @@ using System.Collections.Frozen; -using System.Text.Json; using System.Text.Json.Serialization; -using SPTarkov.Common.Extensions; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Models.Common; @@ -25,10 +23,8 @@ namespace SPTarkov.Server.Core.Helpers; [Injectable] public class InventoryHelper( ISptLogger _logger, - HashUtil _hashUtil, HttpResponseUtil _httpResponseUtil, DialogueHelper _dialogueHelper, - ContainerHelper _containerHelper, EventOutputHolder _eventOutputHolder, ProfileHelper _profileHelper, ItemHelper _itemHelper, @@ -146,10 +142,7 @@ public class InventoryHelper( // Run callback try { - if (request.Callback is not null) - { - request.Callback((int)(itemWithModsToAddClone[0].Upd.StackObjectsCount ?? 0)); - } + request.Callback?.Invoke((int)(itemWithModsToAddClone[0].Upd.StackObjectsCount ?? 0)); } catch (Exception ex) { @@ -169,7 +162,7 @@ public class InventoryHelper( if (_logger.IsLogEnabled(LogLevel.Debug)) { _logger.Debug( - $"Added {itemWithModsToAddClone[0].Upd?.StackObjectsCount ?? 1} item: {itemWithModsToAddClone[0].Template} with: {itemWithModsToAddClone.Count - 1} mods to inventory" + $"Added: {itemWithModsToAddClone[0].Upd?.StackObjectsCount ?? 1} item: {itemWithModsToAddClone[0].Template} with: {itemWithModsToAddClone.Count - 1} mods to inventory" ); } } @@ -245,7 +238,7 @@ public class InventoryHelper( /// Container grid to fit items into /// Items to try and fit into grid /// True all fit - public bool CanPlaceItemsInContainer(int[][] containerFS2D, List> itemsWithChildren) + public bool CanPlaceItemsInContainer(int[,] containerFS2D, List> itemsWithChildren) { return itemsWithChildren.All(itemWithChildren => CanPlaceItemInContainer(containerFS2D, itemWithChildren) @@ -258,28 +251,23 @@ public class InventoryHelper( /// Container grid /// Item to check fits /// True it fits - public bool CanPlaceItemInContainer(int[][] containerFS2D, List itemWithChildren) + public bool CanPlaceItemInContainer(int[,] containerFS2D, List itemWithChildren) { // Get x/y size of item var rootItem = itemWithChildren[0]; - var itemSize = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren); + var (sizeX, sizeY) = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren); // Look for a place to slot item into - var findSlotResult = _containerHelper.FindSlotForItem( - containerFS2D, - itemSize[0], - itemSize[1] - ); + var findSlotResult = containerFS2D.FindSlotForItem(sizeX, sizeY); if (findSlotResult.Success.GetValueOrDefault(false)) { try { - _containerHelper.FillContainerMapWithItem( - containerFS2D, + containerFS2D.FillContainerMapWithItem( findSlotResult.X.Value, findSlotResult.Y.Value, - itemSize[0], - itemSize[1], + sizeX, + sizeY, findSlotResult.Rotation.Value ); } @@ -310,7 +298,7 @@ public class InventoryHelper( /// Id of the container we're fitting item into /// Slot id value to use, default is "hideout" public void PlaceItemInContainer( - int[][] containerFS2D, + int[,] containerFS2D, List itemWithChildren, string containerId, string desiredSlotId = "hideout" @@ -318,24 +306,23 @@ public class InventoryHelper( { // Get x/y size of item var rootItemAdded = itemWithChildren[0]; - var itemSize = GetItemSize(rootItemAdded.Template, rootItemAdded.Id, itemWithChildren); + var (sizeX, sizeY) = GetItemSize( + rootItemAdded.Template, + rootItemAdded.Id, + itemWithChildren + ); // Look for a place to slot item into - var findSlotResult = _containerHelper.FindSlotForItem( - containerFS2D, - itemSize[0], - itemSize[1] - ); + var findSlotResult = containerFS2D.FindSlotForItem(sizeX, sizeY); if (findSlotResult.Success.GetValueOrDefault(false)) { try { - _containerHelper.FillContainerMapWithItem( - containerFS2D, + containerFS2D.FillContainerMapWithItem( findSlotResult.X.Value, findSlotResult.Y.Value, - itemSize[0], - itemSize[1], + sizeX, + sizeY, findSlotResult.Rotation.Value ); } @@ -378,8 +365,8 @@ public class InventoryHelper( /// Should sorting table to be used if main stash has no space /// Output to send back to client protected void PlaceItemInInventory( - int[][] stashFS2D, - int[][] sortingTableFS2D, + int[,] stashFS2D, + int[,] sortingTableFS2D, List itemWithChildren, BotBaseInventory playerInventory, bool useSortingTable, @@ -388,20 +375,19 @@ public class InventoryHelper( { // Get x/y size of item var rootItem = itemWithChildren[0]; - var itemSize = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren); + var (sizeX, sizeY) = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren); // Look for a place to slot item into - var findSlotResult = _containerHelper.FindSlotForItem(stashFS2D, itemSize[0], itemSize[1]); + var findSlotResult = stashFS2D.FindSlotForItem(sizeX, sizeY); if (findSlotResult.Success.Value) { try { - _containerHelper.FillContainerMapWithItem( - stashFS2D, + stashFS2D.FillContainerMapWithItem( findSlotResult.X.Value, findSlotResult.Y.Value, - itemSize[0], - itemSize[1], + sizeX, + sizeY, findSlotResult.Rotation.Value ); } @@ -412,7 +398,7 @@ public class InventoryHelper( return; } - // Store details for object, incuding container item will be placed in + // Store details for object, including container item will be placed in rootItem.ParentId = playerInventory.Stash; rootItem.SlotId = "hideout"; rootItem.Location = new ItemLocation @@ -430,20 +416,15 @@ public class InventoryHelper( // Space not found in main stash, use sorting table if (useSortingTable) { - var findSortingSlotResult = _containerHelper.FindSlotForItem( - sortingTableFS2D, - itemSize[0], - itemSize[1] - ); + var findSortingSlotResult = sortingTableFS2D.FindSlotForItem(sizeX, sizeY); try { - _containerHelper.FillContainerMapWithItem( - sortingTableFS2D, + sortingTableFS2D.FillContainerMapWithItem( findSortingSlotResult.X.Value, findSortingSlotResult.Y.Value, - itemSize[0], - itemSize[1], + sizeX, + sizeY, findSortingSlotResult.Rotation.Value ); } @@ -687,7 +668,7 @@ public class InventoryHelper( /// Items id to get size of /// /// [width, height] - public List GetItemSize(string? itemTpl, string itemId, List inventoryItems) + public (int, int) GetItemSize(string? itemTpl, string itemId, List inventoryItems) { // -> Prepares item Width and height returns [sizeX, sizeY] return GetSizeByInventoryItemHash(itemTpl, itemId, GetInventoryItemHash(inventoryItems)); @@ -701,9 +682,9 @@ public class InventoryHelper( /// Items id /// Hashmap of inventory items /// An array representing the [width, height] of the item - protected List GetSizeByInventoryItemHash( - string itemTpl, - string itemId, + protected (int, int) GetSizeByInventoryItemHash( + MongoId itemTpl, + MongoId itemId, InventoryItemHash inventoryItemHash ) { @@ -736,7 +717,7 @@ public class InventoryHelper( _serverLocalisationService.GetText("inventory-return_default_size", itemTpl) ); - return [1, 1]; // Invalid input data, return defaults + return (1, 1); // Invalid input data, return defaults } if (!inventoryItemHash.ByItemId.TryGetValue(itemId, out var rootItem)) @@ -745,7 +726,7 @@ public class InventoryHelper( $"Unable to get root item with Id: {itemId} from player inventory. Defaulting to 1x1" ); - return [1, 1]; // Invalid input data, return defaults + return (1, 1); // Invalid input data, return defaults } // Does root item support being folded @@ -867,11 +848,10 @@ public class InventoryHelper( } } - return - [ + return ( outX.Value + sizeLeft + sizeRight + forcedLeft + forcedRight, - outY.Value + sizeUp + sizeDown + forcedUp + forcedDown, - ]; + outY.Value + sizeUp + sizeDown + forcedUp + forcedDown + ); } /// @@ -882,7 +862,7 @@ public class InventoryHelper( /// Players inventory items /// Id of the container /// Two-dimensional representation of container - public int[][] GetContainerMap(int sizeX, int sizeY, List itemList, string containerId) + public int[,] GetContainerMap(int sizeX, int sizeY, List itemList, string containerId) { // Create blank 2d map of container var containerYX = _itemHelper.GetBlankContainerMap(sizeY, sizeX); @@ -892,66 +872,63 @@ public class InventoryHelper( // Get subset of items that belong to the desired container if (!inventoryItemHash.ByParentId.TryGetValue(containerId, out var rootItemsInContainer)) - // No items in container, exit early { + // No items in container, exit early and return the blank container map return containerYX; } - // Check each item in container - foreach (var item in rootItemsInContainer) + // Add every root items size (with mods attached) found in container + foreach (var rootItem in rootItemsInContainer) { - ItemLocation? itemLocation; - if (item.Location is JsonElement element) - { - // TODO: is this ever true? - itemLocation = element.ToObject(); - } - else - { - itemLocation = (ItemLocation?)item.Location; - } - + var itemLocation = rootItem.GetParsedLocation(); if (itemLocation is null) { // Item has no location property _logger.Error( - $"Unable to find 'location' property on item with id: {item.Id}, skipping" + $"Unable to find 'location' property on item with id: {rootItem.Id}, skipping" ); continue; } // Get x/y size of item - var tmpSize = GetSizeByInventoryItemHash(item.Template, item.Id, inventoryItemHash); - var iW = tmpSize[0]; // x - var iH = tmpSize[1]; // y - var fH = itemLocation.IsVertical() ? iW : iH; - var fW = itemLocation.IsVertical() ? iH : iW; + var (xSize, ySize) = GetSizeByInventoryItemHash( + rootItem.Template, + rootItem.Id, + inventoryItemHash + ); + var itemHSize = itemLocation.IsVertical() ? xSize : ySize; + var itemWSize = itemLocation.IsVertical() ? ySize : xSize; - for (var y = 0; y < fH; y++) + for (var yOffset = 0; yOffset < itemHSize; yOffset++) { - try + for (var xOffset = 0; xOffset < itemWSize; xOffset++) { - var rowIndex = itemLocation.Y + y; - var containerX = containerYX.ElementAtOrDefault(rowIndex.Value); - if (containerX is null) - { - _logger.Error( - $"Unable to find container: {containerId} row line: {itemLocation.Y + y}" - ); - } + var currentY = itemLocation.Y.Value + yOffset; + var currentX = itemLocation.X.Value + xOffset; - // Fill the corresponding cells in the container map to show the slot is taken - Array.Fill(containerX, 1, itemLocation.X.Value, fW); - } - catch (Exception ex) - { - _logger.Error( - _serverLocalisationService.GetText( - "inventory-unable_to_fill_container", - new { id = item.Id, error = $"{ex.Message} {ex.StackTrace}" } - ) - ); + // Check still in containers bounds + if (currentY >= 0 && currentY < sizeY && currentX >= 0 && currentX < sizeX) + { + // mark slot used + containerYX[currentY, currentX] = 1; + } + else + { + // Out of bounds + var message = + $"Item: {rootItem.Id} at: {itemLocation.X}, {itemLocation.Y} size: {itemHSize}x{itemWSize} extends outside the containers bounds"; + + _logger.Error( + _serverLocalisationService.GetText( + "inventory-unable_to_fill_container", + new { id = rootItem.Id, error = $"{message}" } + ) + ); + + // Stop and try next row + break; + } } } } @@ -975,6 +952,11 @@ public class InventoryHelper( continue; } + if (item.ParentId == "hideout") + { + continue; + } + if (!inventoryItemHash.ByParentId.ContainsKey(item.ParentId)) { inventoryItemHash.ByParentId[item.ParentId] = []; @@ -1057,7 +1039,7 @@ public class InventoryHelper( /// /// Player profile /// 2-dimensional array - protected int[][] GetStashSlotMap(PmcData pmcData) + protected int[,] GetStashSlotMap(PmcData pmcData) { var (horizontal, vertical) = GetPlayerStashSize(pmcData); return GetContainerMap( @@ -1073,7 +1055,7 @@ public class InventoryHelper( /// /// Container to get data for /// blank two-dimensional array - public int[][] GetContainerSlotMap(string containerTpl) + public int[,] GetContainerSlotMap(string containerTpl) { var containerTemplate = _itemHelper.GetItem(containerTpl).Value; @@ -1089,7 +1071,7 @@ public class InventoryHelper( /// /// Player profile /// two-dimensional array - protected int[][] GetSortingTableSlotMap(PmcData pmcData) + protected int[,] GetSortingTableSlotMap(PmcData pmcData) { return GetContainerMap(10, 45, pmcData.Inventory.Items, pmcData.Inventory.SortingTable); } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs index 07e72355..cc8818f1 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs @@ -2057,7 +2057,7 @@ public class ItemHelper( /// Get a 2D grid of a container's item slots /// /// Tpl id of the container - public int[][] GetContainerMapping(string containerTpl) + public int[,] GetContainerMapping(string containerTpl) { // Get template from db var containerTemplate = GetItem(containerTpl).Value; @@ -2075,16 +2075,8 @@ public class ItemHelper( /// Horizontal size of container /// Vertical size of container /// Two-dimensional representation of container - public int[][] GetBlankContainerMap(int containerY, int containerX) + public int[,] GetBlankContainerMap(int containerY, int containerX) { - //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(_ => new int[containerX]).ToArray(); + return new int[containerX, containerY]; } } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/SecureContainerHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/SecureContainerHelper.cs deleted file mode 100644 index 661a8d0d..00000000 --- a/Libraries/SPTarkov.Server.Core/Helpers/SecureContainerHelper.cs +++ /dev/null @@ -1,30 +0,0 @@ -using SPTarkov.DI.Annotations; -using SPTarkov.Server.Core.Extensions; -using SPTarkov.Server.Core.Models.Eft.Common.Tables; - -namespace SPTarkov.Server.Core.Helpers; - -[Injectable] -public class SecureContainerHelper(ItemHelper _itemHelper) -{ - /// - /// Get a list of the item IDs (NOT tpls) inside a secure container - /// - /// Inventory items to look for secure container in - /// List of ids - public List GetSecureContainerItems(List items) - { - var secureContainer = items.First(x => x.SlotId == "SecuredContainer"); - - // No container found, drop out - if (secureContainer is null) - { - return []; - } - - var itemsInSecureContainer = items.FindAndReturnChildrenByItems(secureContainer.Id); - - // Return all items returned and exclude the secure container item itself - return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToList(); - } -} diff --git a/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs b/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs index d8447c63..6f7af548 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs @@ -23,11 +23,6 @@ public readonly struct MongoId : IEquatable return; } - if (id == "hideout") - { - throw new Exception("wtf"); - } - if (id.Length != 24) { // TODO: Items.json root item has an empty parentId property diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Inventory/FindSlotResult.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Inventory/FindSlotResult.cs new file mode 100644 index 00000000..8aa02646 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Inventory/FindSlotResult.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace SPTarkov.Server.Core.Models.Spt.Inventory; + +public class FindSlotResult +{ + public FindSlotResult(bool success) + { + Success = success; + } + + public FindSlotResult(bool success, int x, int y, bool rotation) + { + Success = success; + X = x; + Y = y; + Rotation = rotation; + } + + public FindSlotResult() { } + + [JsonPropertyName("success")] + public bool? Success { get; set; } + + [JsonPropertyName("x")] + public int? X { get; set; } + + [JsonPropertyName("y")] + public int? Y { get; set; } + + [JsonPropertyName("rotation")] + public bool? Rotation { get; set; } +} diff --git a/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs b/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs index e023886c..37996336 100644 --- a/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs @@ -1,4 +1,5 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Generators; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; @@ -9,7 +10,6 @@ using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Services; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; -using SPTarkov.Server.Core.Utils; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Services; @@ -19,9 +19,7 @@ public class AirdropService( ConfigServer configServer, ISptLogger _logger, LootGenerator _lootGenerator, - HashUtil _hashUtil, WeightedRandomHelper _weightedRandomHelper, - ContainerHelper _containerHelper, ServerLocalisationService _serverLocalisationService, ItemFilterService _itemFilterService, ItemHelper _itemHelper @@ -135,19 +133,14 @@ public class AirdropService( 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 - ); + var result = containerMap.FindSlotForItem(itemSize.Width, itemSize.Height); if (result.Success.GetValueOrDefault(false)) { // It Fits, add item + children lootResult.AddRange(itemAndChildren); // Update container with item we just added - _containerHelper.FillContainerMapWithItem( - containerMap, + containerMap.FillContainerMapWithItem( result.X.Value, result.Y.Value, itemSize.Width, diff --git a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs index e0f01859..9f67d6a0 100644 --- a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs @@ -137,7 +137,7 @@ public class CircleOfCultistService( _hideoutConfig.CultistCircle ), rewardAmountRoubles, - cultistCircleStashId, + cultistCircleStashId.Value, _hideoutConfig.CultistCircle ); @@ -153,7 +153,7 @@ public class CircleOfCultistService( pmcData, rewards, containerGrid, - cultistCircleStashId, + cultistCircleStashId.Value, output ); @@ -352,7 +352,7 @@ public class CircleOfCultistService( protected List> GetRewardsWithinBudget( List rewardItemTplPool, double rewardBudget, - string cultistCircleStashId, + MongoId cultistCircleStashId, CultistCircleSettings circleConfig ) { @@ -1014,8 +1014,8 @@ public class CircleOfCultistService( string sessionId, PmcData pmcData, List> rewards, - int[][] containerGrid, - string cultistCircleStashId, + int[,] containerGrid, + MongoId cultistCircleStashId, ItemEventRouterResponse output ) {