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? itemWidthX, int? itemHeightY) { // Assume not rotated var rotation = false; // Find the min volume the item will take up var minVolume = (itemWidthX < itemHeightY ? itemWidthX : itemHeightY) - 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 row = 0; row < limitY; row++) { if (RowIsFull(container2D, row)) { continue; } // Left to right across columns, look for free position for (var column = 0; column < limitX; column++) { // Does item fit if (CanItemBePlacedInContainerAtPosition(container2D, row, column, itemWidthX.Value, itemHeightY.Value)) { // Success, found a spot it fits return new FindSlotResult(true, column, row, rotation); } if (!ItemBiggerThan1X1(itemWidthX.Value, itemHeightY.Value)) { // Doesn't fit AND rotating won't help continue; } // Rotate item by swapping x and y item values if ( CanItemBePlacedInContainerAtPosition( container2D, row, column, itemHeightY.Value, // Swapped itemWidthX.Value // Swapped ) ) { // Found a position for the item when rotated rotation = true; return new FindSlotResult(true, column, row, 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 y size /// Container x size /// Items width /// Items height /// is item rotated public static void FillContainerMapWithItem( this int[,] container2D, int columnStartPositionX, int rowStartPositionY, int? itemXWidth, int? itemYHeight, bool isRotated ) { var containerY = container2D.GetLength(0); // rows var containerX = container2D.GetLength(1); // columns // Swap height/width if item needs to be rotated to fit var itemWidth = isRotated ? itemYHeight : itemXWidth; var itemHeight = isRotated ? itemXWidth : itemYHeight; var itemRowEndPosition = rowStartPositionY + (itemHeight - 1); var itemColumnEndPosition = columnStartPositionX + (itemWidth - 1); //Item is a 1x1, flag slot as taken and exit early if (itemXWidth == 1 && itemYHeight == 1) { container2D[rowStartPositionY, columnStartPositionX] = 1; return; } // Loop over rows and columns and flag each as taken by item for (var y = rowStartPositionY; y <= itemRowEndPosition; y++) { for (var x = columnStartPositionX; x <= itemColumnEndPosition; x++) { if (container2D[y, x] == 0) { // Flag slot as used container2D[y, x] = 1; } else { throw new Exception( $"Slot at: ({containerX}, {containerY}) is already filled. Cannot fit: {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(1); // Column 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 y position for item /// Starting x position for item /// Items width (y) /// Items height (x) /// True - slot found public static bool CanItemBePlacedInContainerAtPosition( this int[,] container, int itemStartVerticalPos, int itemStartHorizontalPos, int itemWidth, int itemHeight ) { var containerHeight = container.GetLength(0); // Rows var containerWidth = container.GetLength(1); // Columns var itemEndColPosition = itemStartHorizontalPos + itemWidth - 1; var itemEndRowPosition = itemStartVerticalPos + itemHeight - 1; // Check item isn't bigger than container when at position if (itemEndColPosition > containerWidth - 1 || itemEndRowPosition > containerHeight - 1) { // Item is bigger than container, will never fit return false; } // Early exit if exact spot is taken if (container[itemStartVerticalPos, itemStartHorizontalPos] == 1) { return false; } // Single slot item, do direct check if (itemWidth == 1 && itemHeight == 1) { return container[itemStartVerticalPos, itemStartHorizontalPos] == 0; } for (var row = itemStartVerticalPos; row <= itemEndRowPosition; row++) { for (var column = itemStartHorizontalPos; column <= itemEndColPosition; column++) { if (container[row, column] == 1) { // Occupied by something return false; } } } return true; // Slot is free } } }