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; } }