diff --git a/Libraries/Core/Generators/BotEquipmentModGenerator.cs b/Libraries/Core/Generators/BotEquipmentModGenerator.cs
index 95736e79..ba1bdf71 100644
--- a/Libraries/Core/Generators/BotEquipmentModGenerator.cs
+++ b/Libraries/Core/Generators/BotEquipmentModGenerator.cs
@@ -61,7 +61,7 @@ public class BotEquipmentModGenerator(
}
// Iterate over mod pool and choose mods to add to item
- foreach (var (modSlotName, modPool) in compatibleModsPool)
+ foreach (var (modSlotName, modPool) in compatibleModsPool ?? [])
{
// Get the templates slot object from db
var itemSlotTemplate = GetModItemSlotFromDb(modSlotName, parentTemplate);
diff --git a/Libraries/Core/Generators/BotInventoryGenerator.cs b/Libraries/Core/Generators/BotInventoryGenerator.cs
index 49345186..cf5be899 100644
--- a/Libraries/Core/Generators/BotInventoryGenerator.cs
+++ b/Libraries/Core/Generators/BotInventoryGenerator.cs
@@ -393,7 +393,7 @@ public class BotInventoryGenerator(
// Roll dice on equipment item
var shouldSpawn = _randomUtil.GetChance100(spawnChance ?? 0);
- if (shouldSpawn && !settings.RootEquipmentPool.Any())
+ if (shouldSpawn && settings.RootEquipmentPool.Any())
{
var pickedItemDb = new TemplateItem();
var found = false;
@@ -469,7 +469,8 @@ public class BotInventoryGenerator(
);
// Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot
- if (_botConfig.Equipment[settings.BotData.EquipmentRole] is not null &&
+ if (_botConfig.Equipment.ContainsKey(settings.BotData.EquipmentRole) &&
+ settings.RandomisationDetails is not null &&
settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString()))
{
// Filter out mods from relevant blacklist
@@ -480,7 +481,8 @@ public class BotInventoryGenerator(
}
// Does item have slots for sub-mods to be inserted into
- if (pickedItemDb.Properties.Slots.Any() && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id))
+ if (pickedItemDb.Properties?.Slots?.Count > 0
+ && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id))
{
var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment(
[item],
diff --git a/Libraries/Core/Helpers/BotGeneratorHelper.cs b/Libraries/Core/Helpers/BotGeneratorHelper.cs
index 563638d6..3d4adb69 100644
--- a/Libraries/Core/Helpers/BotGeneratorHelper.cs
+++ b/Libraries/Core/Helpers/BotGeneratorHelper.cs
@@ -474,13 +474,13 @@ public class BotGeneratorHelper(
}
// Get container to put item into
- var container = (inventory.Items ?? []).FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
+ var container = (inventory.Items).FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
if (container is null)
{
missingContainerCount++;
if (missingContainerCount == equipmentSlots.Count)
{
- // Bot doesnt have any containers we want to add item to
+ // Bot doesn't have any containers we want to add item to
_logger.Debug(
$"Unable to add item: {itemWithChildren.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
);
diff --git a/Libraries/Core/Helpers/ContainerHelper.cs b/Libraries/Core/Helpers/ContainerHelper.cs
index 8942a56e..04050f83 100644
--- a/Libraries/Core/Helpers/ContainerHelper.cs
+++ b/Libraries/Core/Helpers/ContainerHelper.cs
@@ -1,4 +1,4 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
using SptCommon.Annotations;
namespace Core.Helpers;
@@ -13,9 +13,59 @@ public class ContainerHelper
/// Width of item
/// Height of item
/// Location to place item in container
- public FindSlotResult FindSlotForItem(List> container2D, int itemWidth, int itemHeight)
+ public FindSlotResult FindSlotForItem(int[][] container2D, int itemWidth, int itemHeight)
{
- throw new NotImplementedException();
+ var rotation = false;
+ var minVolume = (itemWidth < itemHeight ? itemWidth : itemHeight) - 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
+ for (var y = 0; y < limitY; y++)
+ {
+ // Across
+ if (container2D[y].All((x) => x == 1))
+ {
+ // Every item in row is full, skip row
+ continue;
+ }
+
+ for (var x = 0; x < limitX; x++)
+ {
+ var foundSlot = LocateSlot(container2D, containerX, containerY, x, y, itemWidth, itemHeight);
+
+ // Failed to find slot, rotate item and try again
+ if (!foundSlot && itemWidth * itemHeight > 1)
+ {
+ // Bigger than 1x1
+ foundSlot = LocateSlot(container2D, containerX, containerY, x, y, itemHeight, itemWidth); // Height/Width swapped
+ if (foundSlot)
+ {
+ // Found a slot for it when rotated
+ rotation = true;
+ }
+ }
+
+ if (!foundSlot)
+ {
+ // Didn't fit this hole, try again
+ continue;
+ }
+
+ return new FindSlotResult(true, x, y, rotation);
+ }
+ }
+
+ // Tried all possible holes, nothing big enough for the item
+ return new FindSlotResult(false);
}
///
@@ -30,7 +80,7 @@ public class ContainerHelper
/// Items height
/// True - slot found
protected bool LocateSlot(
- List> container2D,
+ int[][] container2D,
int containerX,
int containerY,
int x,
@@ -51,7 +101,7 @@ public class ContainerHelper
/// Items height
/// is item rotated
public void FillContainerMapWithItem(
- List> container2D,
+ int[][] container2D,
int x,
int y,
int itemW,
@@ -64,14 +114,31 @@ public class ContainerHelper
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 double? X { get; set; }
+ public int? X { get; set; }
[JsonPropertyName("y")]
- public double? Y { get; set; }
+ public int? Y { get; set; }
[JsonPropertyName("rotation")]
public bool? Rotation { get; set; }
diff --git a/Libraries/Core/Helpers/InventoryHelper.cs b/Libraries/Core/Helpers/InventoryHelper.cs
index 829c3898..1ce5272e 100644
--- a/Libraries/Core/Helpers/InventoryHelper.cs
+++ b/Libraries/Core/Helpers/InventoryHelper.cs
@@ -8,6 +8,12 @@ using Core.Models.Spt.Config;
using Core.Models.Spt.Inventory;
using Core.Models.Utils;
using Core.Services;
+using Core.Models.Eft.Player;
+using System.ComponentModel;
+using Core.Models.Eft.Hideout;
+using Core.Models.Enums;
+using Core.Models.Spt.Bots;
+using Core.Models.Spt.Services;
namespace Core.Helpers;
@@ -16,6 +22,7 @@ public class InventoryHelper(
ISptLogger _logger,
ProfileHelper _profileHelper,
DialogueHelper _dialogueHelper,
+ ItemHelper _itemHelper,
LocalisationService _localisationService
)
{
@@ -187,7 +194,8 @@ public class InventoryHelper(
/// [width, height]
public List GetItemSize(string? itemTpl, string itemId, List- inventoryItems)
{
- throw new NotImplementedException();
+ // -> Prepares item Width and height returns [sizeX, sizeY]
+ return GetSizeByInventoryItemHash(itemTpl, itemId, GetInventoryItemHash(inventoryItems));
}
///
@@ -198,9 +206,126 @@ 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, InventoryItemHash inventoryItemHash)
+ protected List GetSizeByInventoryItemHash(string itemTpl, string itemID, InventoryItemHash inventoryItemHash)
{
- throw new NotImplementedException();
+ var toDo = new List { itemID };
+ var result = _itemHelper.GetItem(itemTpl);
+ var tmpItem = result.Value;
+
+ // Invalid item
+ if (!result.Key)
+ {
+ _logger.Error(_localisationService.GetText("inventory-invalid_item_missing_from_db", itemTpl));
+ }
+
+ // Item found but no _props property
+ if (tmpItem is not null && tmpItem.Properties is null)
+ {
+ _localisationService.GetText("inventory-item_missing_props_property", new {
+ itemTpl = itemTpl,
+ itemName = tmpItem?.Name
+ });
+ }
+
+ // No item object or getItem() returned false
+ if (tmpItem is null && result.Value is null)
+ {
+ // return default size of 1x1
+ _logger.Error(_localisationService.GetText("inventory-return_default_size", itemTpl));
+
+ return [1, 1]; // Invalid input data, return defaults
+ }
+
+ var rootItem = inventoryItemHash.ByItemId[itemID];
+ var foldableWeapon = tmpItem.Properties.Foldable;
+ var foldedSlot = tmpItem.Properties.FoldedSlot;
+
+ var sizeUp = 0;
+ var sizeDown = 0;
+ var sizeLeft = 0;
+ var sizeRight = 0;
+
+ var forcedUp = 0;
+ var forcedDown = 0;
+ var forcedLeft = 0;
+ var forcedRight = 0;
+ var outX = (int)tmpItem.Properties.Width;
+ var outY = (int)tmpItem.Properties.Height;
+
+ // Item types to ignore
+ var skipThisItems = new List { BaseClasses.BACKPACK, BaseClasses.SEARCHABLE_ITEM, BaseClasses.SIMPLE_CONTAINER };
+
+ var rootFolded = rootItem?.Upd?.Foldable?.Folded == true;
+
+ // The item itself is collapsible
+ if (foldableWeapon is not null && string.IsNullOrEmpty(foldedSlot) && rootFolded)
+ {
+ outX -= tmpItem.Properties.SizeReduceRight.Value;
+ }
+
+ // Calculate size contribution from child items/attachments
+ if (!skipThisItems.Contains(tmpItem.Parent))
+ {
+ while (toDo.Count > 0)
+ {
+ if (inventoryItemHash.ByParentId.ContainsKey(toDo[0])) {
+ foreach (var item in inventoryItemHash.ByParentId[toDo[0]]) {
+ // Filtering child items outside of mod slots, such as those inside containers, without counting their ExtraSize attribute
+ if (item.SlotId.IndexOf("mod_") < 0)
+ {
+ continue;
+ }
+
+ toDo.Add(item.Id);
+
+ // If the barrel is folded the space in the barrel is not counted
+ var itemResult = _itemHelper.GetItem(item.Template);
+ if (!itemResult.Key)
+ {
+ _logger.Error(
+ _localisationService.GetText("inventory-get_item_size_item_not_found_by_tpl", item.Template));
+ }
+
+ var itm = itemResult.Value;
+ var childFoldable = itm.Properties.Foldable.GetValueOrDefault(false);
+ var childFolded = item.Upd?.Foldable is not null && item.Upd.Foldable.Folded == true;
+
+ if (foldableWeapon is true && foldedSlot == item.SlotId && (rootFolded || childFolded))
+ {
+ continue;
+ }
+
+ if (childFoldable && rootFolded && childFolded)
+ {
+ continue;
+ }
+
+ // Calculating child ExtraSize
+ if (itm.Properties.ExtraSizeForceAdd == true)
+ {
+ forcedUp += itm.Properties.ExtraSizeUp.Value;
+ forcedDown += itm.Properties.ExtraSizeDown.Value;
+ forcedLeft += itm.Properties.ExtraSizeLeft.Value;
+ forcedRight += itm.Properties.ExtraSizeRight.Value;
+ }
+ else
+ {
+ sizeUp = sizeUp < itm.Properties.ExtraSizeUp ? itm.Properties.ExtraSizeUp.Value : sizeUp;
+ sizeDown = sizeDown < itm.Properties.ExtraSizeDown ? itm.Properties.ExtraSizeDown.Value : sizeDown;
+ sizeLeft = sizeLeft < itm.Properties.ExtraSizeLeft ? itm.Properties.ExtraSizeLeft.Value : sizeLeft;
+ sizeRight = sizeRight < itm.Properties.ExtraSizeRight ? itm.Properties.ExtraSizeRight.Value : sizeRight;
+ }
+ }
+ }
+
+ toDo.RemoveAt(0);
+ }
+ }
+
+ return [
+ outX + sizeLeft + sizeRight + forcedLeft + forcedRight,
+ outY + sizeUp + sizeDown + forcedUp + forcedDown,
+ ];
}
///
@@ -209,9 +334,9 @@ public class InventoryHelper(
/// Horizontal size of container
/// Vertical size of container
/// Two-dimensional representation of container
- protected List
> GetBlankContainerMap(int containerH, int containerY)
+ protected int[][] GetBlankContainerMap(int containerH, int containerY)
{
- throw new NotImplementedException();
+ return Enumerable.Repeat(Enumerable.Repeat(0, containerH).ToArray(), containerY).ToArray();
}
///
@@ -222,9 +347,68 @@ public class InventoryHelper(
/// Players inventory items
/// Id of the container
/// Two-dimensional representation of container
- public List> GetContainerMap(double containerH, double containerV, List- itemList, string containerId)
+ public int[][] GetContainerMap(int containerH, int containerV, List
- itemList, string containerId)
{
- throw new NotImplementedException();
+ // Create blank 2d map of container
+ var container2D = GetBlankContainerMap(containerH, containerV);
+
+ // Get all items in players inventory keyed by their parentId and by ItemId
+ var inventoryItemHash = GetInventoryItemHash(itemList);
+
+ // Get subset of items that belong to the desired container
+ if (!inventoryItemHash.ByParentId.TryGetValue(containerId, out List
- containerItemHash))
+ {
+ // No items in container, exit early
+ return container2D;
+ }
+
+ // Check each item in container
+ foreach (var item in containerItemHash) {
+ var itemLocation = item?.Location as ItemLocation;
+ if (itemLocation is null)
+ {
+ // item has no location property
+ _logger.Error("Unable to find 'location' property on item with id: ${ item._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 = IsVertical(itemLocation) ? iW : iH;
+ var fW = IsVertical(itemLocation) ? iH : iW;
+
+ // Find the ending x coord of container
+ var fillTo = itemLocation.X + fW;
+
+ for (var y = 0; y < fH; y++)
+ {
+ try
+ {
+ var rowIndex = itemLocation.Y + y;
+ var containerRow = container2D[rowIndex.Value];
+ if (containerRow is null)
+ {
+ _logger.Error("Unable to find container: { containerId} row line: { itemLocation.y + y}");
+ }
+
+ // Fill the corresponding cells in the container map to show the slot is taken
+ Array.Fill(containerRow, 1, itemLocation.X.Value, fillTo.Value);
+ } catch (Exception ex) {
+ _logger.Error(
+ _localisationService.GetText("inventory-unable_to_fill_container", new {
+ id = item.Id,
+ error = ex.Message
+ })
+ );
+ }
+
+ }
+ }
+
+ return container2D;
}
protected bool IsVertical(ItemLocation itemLocation)
@@ -234,7 +418,25 @@ public class InventoryHelper(
protected InventoryItemHash GetInventoryItemHash(List
- inventoryItems)
{
- throw new NotImplementedException();
+ var inventoryItemHash = new InventoryItemHash
+ {
+ ByItemId = new(),
+ ByParentId = new()
+ };
+ foreach (var item in inventoryItems)
+ {
+ inventoryItemHash.ByItemId.TryAdd(item.Id, item);
+
+ if (item.ParentId is null) {
+ continue;
+ }
+
+ if (!inventoryItemHash.ByParentId.ContainsKey(item.ParentId)) {
+ inventoryItemHash.ByParentId[item.ParentId] = [];
+ }
+ inventoryItemHash.ByParentId[item.ParentId].Add(item);
+ }
+ return inventoryItemHash;
}
///
diff --git a/Libraries/Core/Models/Eft/Common/Tables/Item.cs b/Libraries/Core/Models/Eft/Common/Tables/Item.cs
index 605126df..25170621 100644
--- a/Libraries/Core/Models/Eft/Common/Tables/Item.cs
+++ b/Libraries/Core/Models/Eft/Common/Tables/Item.cs
@@ -29,8 +29,8 @@ public record Item
public record ItemLocation
{
- public double? X { get; set; }
- public double? Y { get; set; }
+ public int? X { get; set; }
+ public int? Y { get; set; }
public object? R { get; set; } // TODO: Can be string or number
public bool? IsSearched { get; set; }
diff --git a/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs b/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs
index 13b4461e..812940ae 100644
--- a/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs
+++ b/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs
@@ -153,19 +153,19 @@ public record Props
public double? RepairSpeed { get; set; }
[JsonPropertyName("ExtraSizeLeft")]
- public double? ExtraSizeLeft { get; set; }
+ public int? ExtraSizeLeft { get; set; }
[JsonPropertyName("ExtraSizeRight")]
- public double? ExtraSizeRight { get; set; }
+ public int? ExtraSizeRight { get; set; }
[JsonPropertyName("ExtraSizeUp")]
- public double? ExtraSizeUp { get; set; }
+ public int? ExtraSizeUp { get; set; }
[JsonPropertyName("FlareTypes")]
public List? FlareTypes { get; set; }
[JsonPropertyName("ExtraSizeDown")]
- public double? ExtraSizeDown { get; set; }
+ public int? ExtraSizeDown { get; set; }
[JsonPropertyName("ExtraSizeForceAdd")]
public bool? ExtraSizeForceAdd { get; set; }
@@ -474,7 +474,7 @@ public record Props
public bool? Retractable { get; set; }
[JsonPropertyName("SizeReduceRight")]
- public double? SizeReduceRight { get; set; }
+ public int? SizeReduceRight { get; set; }
[JsonPropertyName("CenterOfImpact")]
public double? CenterOfImpact { get; set; }
@@ -1512,10 +1512,10 @@ public record GridProps
public List? Filters { get; set; }
[JsonPropertyName("cellsH")]
- public double? CellsH { get; set; }
+ public int? CellsH { get; set; }
[JsonPropertyName("cellsV")]
- public double? CellsV { get; set; }
+ public int? CellsV { get; set; }
[JsonPropertyName("minCount")]
public double? MinCount { get; set; }