Refactor of how bot loot has its position picked to improve performance (#548)

* Initial work on moving container space checks into new class

* Updated tests

Wired up service to save item into inventory when space is found

Updated `FillContainerMapWithItem` to return outcome and not throw exception on failure

Add containers to bot when generating bot equipment

Clean bot cache after completion of loot generation

Removed redundant code from `AddItemWithChildrenToEquipmentSlot`

Removed unnecessary Singleton status from `BotInventoryContainerService`

* Clean-up of service

* Add botId xml docs

* Updated documentation for `FillContainerMapWithItem`

* Code review fixes and improvements

* Remove TODO

---------

Co-authored-by: Chomp <dev@dev.sp-tarkov.com>
This commit is contained in:
Chomp
2025-08-13 15:35:57 +00:00
committed by GitHub
parent 965d503021
commit b061200803
17 changed files with 719 additions and 314 deletions
@@ -5,6 +5,7 @@ using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Services;
namespace UnitTests.Tests.Helpers;
@@ -13,12 +14,16 @@ public class BotGeneratorHelperTests
{
private BotGeneratorHelper _botGeneratorHelper;
private BotLootGenerator _botLootGenerator;
private BotInventoryContainerService _botInventoryContainerService;
private ItemHelper _itemHelper;
[OneTimeSetUp]
public void Initialize()
{
_botGeneratorHelper = DI.GetInstance().GetService<BotGeneratorHelper>();
_itemHelper = DI.GetInstance().GetService<ItemHelper>();
_botLootGenerator = DI.GetInstance().GetService<BotLootGenerator>();
_botInventoryContainerService = DI.GetInstance().GetService<BotInventoryContainerService>();
}
#region AddItemWithChildrenToEquipmentSlot
@@ -26,6 +31,7 @@ public class BotGeneratorHelperTests
[Test]
public void AddItemWithChildrenToEquipmentSlot_fit_vertical()
{
var botId = new MongoId();
var stashId = new MongoId();
var equipmentId = new MongoId();
var botInventory = new BotBaseInventory
@@ -42,14 +48,16 @@ public class BotGeneratorHelperTests
// Has a 3grids, first is a 3hx5v grid
Template = ItemTpl.BACKPACK_EBERLESTOCK_G2_GUNSLINGER_II_BACKPACK_DRY_EARTH,
ParentId = equipmentId,
SlotId = "Backpack",
SlotId = nameof(EquipmentSlots.Backpack),
};
botInventory.Items.Add(backpack);
_botInventoryContainerService.AddEmptyContainerToBot(botId, EquipmentSlots.Backpack, backpack);
var rootWeaponId = new MongoId();
var weaponWithChildren = CreateMp18(rootWeaponId);
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
botId,
[EquipmentSlots.Backpack],
rootWeaponId,
ItemTpl.SHOTGUN_MP18_762X54R_SINGLESHOT_RIFLE,
@@ -93,6 +101,7 @@ public class BotGeneratorHelperTests
[Test]
public void AddItemWithChildrenToEquipmentSlot_fit_horizontal()
{
var botId = new MongoId();
var stashId = new MongoId();
var equipmentId = new MongoId();
var botInventory = new BotBaseInventory
@@ -108,14 +117,16 @@ public class BotGeneratorHelperTests
Id = new MongoId(),
Template = ItemTpl.BACKPACK_ANA_TACTICAL_BETA_2_BATTLE_BACKPACK_OLIVE_DRAB,
ParentId = equipmentId,
SlotId = "Backpack",
SlotId = nameof(EquipmentSlots.Backpack),
};
botInventory.Items.Add(backpack);
_botInventoryContainerService.AddEmptyContainerToBot(botId, EquipmentSlots.Backpack, backpack);
var rootWeaponId = new MongoId();
var weaponWithChildren = CreateMp18(rootWeaponId);
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
botId,
[EquipmentSlots.Backpack],
rootWeaponId,
ItemTpl.SHOTGUN_MP18_762X54R_SINGLESHOT_RIFLE,
@@ -130,7 +141,7 @@ public class BotGeneratorHelperTests
{ ItemTpl.BARTER_GOLD_SKULL_RING, 1 },
{ ItemTpl.BARTER_PACK_OF_NAILS, 1 },
};
_botLootGenerator.AddLootFromPool(tplsToAdd, [EquipmentSlots.Backpack], 4, botInventory, "assault", null);
_botLootGenerator.AddLootFromPool(botId, tplsToAdd, [EquipmentSlots.Backpack], 4, botInventory, "assault", null);
Assert.AreEqual(ItemAddedResult.SUCCESS, result);
@@ -152,37 +163,40 @@ public class BotGeneratorHelperTests
[Test]
public void AddItemWithChildrenToEquipmentSlot_fit_vertical_with_items_in_backpack()
{
var botId = new MongoId();
var botInventory = new BotBaseInventory { Items = [] };
var backpack = new Item
{
Id = new MongoId(),
// Has a 3hx5v grid first
Template = ItemTpl.BACKPACK_EBERLESTOCK_G2_GUNSLINGER_II_BACKPACK_DRY_EARTH,
SlotId = "Backpack",
SlotId = nameof(EquipmentSlots.Backpack),
};
botInventory.Items.Add(backpack);
botInventory.Items.Add(
new Item
_botInventoryContainerService.AddEmptyContainerToBot(botId, EquipmentSlots.Backpack, backpack);
var akbsCartridge = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = 0,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
}
);
X = 0,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
};
_botInventoryContainerService.AddItemToBotContainer(botId, EquipmentSlots.Backpack, [akbsCartridge], botInventory, 1, 1);
var rootWeaponId = new MongoId();
var weaponWithChildren = CreateMp18(rootWeaponId);
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
botId,
[EquipmentSlots.Backpack],
rootWeaponId,
ItemTpl.SHOTGUN_MP18_762X54R_SINGLESHOT_RIFLE,
@@ -204,18 +218,28 @@ public class BotGeneratorHelperTests
[Test]
public void AddItemWithChildrenToEquipmentSlot_no_space_in_first_grid_choose_second_grid()
{
var botId = new MongoId();
var botInventory = new BotBaseInventory { Items = [] };
var backpack = new Item
{
Id = new MongoId(),
// Has a 3hx5v grid first
Template = ItemTpl.BACKPACK_EBERLESTOCK_G2_GUNSLINGER_II_BACKPACK_DRY_EARTH,
SlotId = "Backpack",
SlotId = nameof(EquipmentSlots.Backpack),
};
botInventory.Items.Add(backpack);
_botInventoryContainerService.AddEmptyContainerToBot(botId, EquipmentSlots.Backpack, backpack);
botInventory.Items.AddRange(
new Item
// Insert items at specific locations
var takenSlots = new List<XY>
{
new() { X = 0, Y = 0 },
new() { X = 1, Y = 0 },
new() { X = 2, Y = 0 },
};
foreach (var takenSlot in takenSlots)
{
var itemToAdd = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
@@ -223,46 +247,29 @@ public class BotGeneratorHelperTests
SlotId = "main",
Location = new ItemLocation
{
X = 0,
Y = 0,
X = (int)takenSlot.X.Value,
Y = (int)takenSlot.Y.Value,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
},
new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = 1,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
},
new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = 2,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
}
);
};
_botInventoryContainerService.AddItemToBotContainerFixedPosition(
botId,
EquipmentSlots.Backpack,
[itemToAdd],
botInventory,
1,
1,
(ItemLocation)itemToAdd.Location
);
}
var rootWeaponId = new MongoId();
var weaponWithChildren = CreateMp18(rootWeaponId);
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
botId,
[EquipmentSlots.Backpack],
rootWeaponId,
ItemTpl.SHOTGUN_MP18_762X54R_SINGLESHOT_RIFLE,
@@ -271,8 +278,10 @@ public class BotGeneratorHelperTests
);
Assert.AreEqual(ItemAddedResult.SUCCESS, result);
var weaponRoot = weaponWithChildren.FirstOrDefault(item => item.Id == rootWeaponId);
Assert.AreEqual("1", weaponRoot.SlotId);
Assert.AreEqual((weaponRoot.Location as ItemLocation).X, 0);
Assert.AreEqual((weaponRoot.Location as ItemLocation).Y, 0);
Assert.AreEqual((weaponRoot.Location as ItemLocation).R, ItemRotation.Vertical);
@@ -284,18 +293,29 @@ public class BotGeneratorHelperTests
[Test]
public void AddItemWithChildrenToEquipmentSlot_no_space()
{
var botId = new MongoId();
var botInventory = new BotBaseInventory { Items = [] };
var backpack = new Item
{
Id = new MongoId(),
// Has a 4hx5v grid first
Template = ItemTpl.BACKPACK_WARTECH_BERKUT_BB102_BACKPACK_ATACS_FG,
SlotId = "Backpack",
SlotId = nameof(EquipmentSlots.Backpack),
};
botInventory.Items.Add(backpack);
_botInventoryContainerService.AddEmptyContainerToBot(botId, EquipmentSlots.Backpack, backpack);
botInventory.Items.AddRange(
new Item
// Insert items at specific locations
var takenSlots = new List<XY>
{
new() { X = 0, Y = 0 },
new() { X = 1, Y = 0 },
new() { X = 2, Y = 0 },
new() { X = 3, Y = 0 },
};
foreach (var takenSlot in takenSlots)
{
var itemToAdd = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
@@ -303,60 +323,29 @@ public class BotGeneratorHelperTests
SlotId = "main",
Location = new ItemLocation
{
X = 0,
Y = 0,
X = (int)takenSlot.X.Value,
Y = (int)takenSlot.Y.Value,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
},
new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = 1,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
},
new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = 2,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
},
new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = 3,
Y = 0,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
}
);
};
_botInventoryContainerService.AddItemToBotContainerFixedPosition(
botId,
EquipmentSlots.Backpack,
[itemToAdd],
botInventory,
1,
1,
(ItemLocation)itemToAdd.Location
);
}
var rootWeaponId = new MongoId();
var weaponWithChildren = CreateMp18(rootWeaponId);
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
botId,
[EquipmentSlots.Backpack],
rootWeaponId,
ItemTpl.SHOTGUN_MP18_762X54R_SINGLESHOT_RIFLE,
@@ -373,16 +362,20 @@ public class BotGeneratorHelperTests
[Test]
public void AddItemWithChildrenToEquipmentSlot_custom_gun_no_space()
{
var botId = new MongoId();
var botInventory = new BotBaseInventory { Items = [] };
var backpack = new Item
{
Id = new MongoId(),
// Has a 4hx5v grid first
Template = ItemTpl.BACKPACK_GRUPPA_99_T30_BACKPACK_BLACK,
SlotId = "Backpack",
SlotId = nameof(EquipmentSlots.Backpack),
};
botInventory.Items.Add(backpack);
botInventory.Items.Add(backpack);
_botInventoryContainerService.AddEmptyContainerToBot(botId, EquipmentSlots.Backpack, backpack);
// Insert items at specific locations to ensure there's no space for adding the weapon
var takenSlots = new List<XY>
{
new() { X = 1, Y = 0 },
@@ -407,27 +400,40 @@ public class BotGeneratorHelperTests
};
foreach (var takenSlot in takenSlots)
{
botInventory.Items.Add(
new Item
var itemToAdd = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
Id = new MongoId(),
Template = ItemTpl.AMMO_762X25TT_AKBS,
ParentId = backpack.Id,
SlotId = "main",
Location = new ItemLocation
{
X = (int)takenSlot.X.Value,
Y = (int)takenSlot.Y.Value,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
}
X = (int)takenSlot.X.Value,
Y = (int)takenSlot.Y.Value,
R = ItemRotation.Horizontal,
},
Upd = new Upd { StackObjectsCount = 1 },
};
_botInventoryContainerService.AddItemToBotContainerFixedPosition(
botId,
EquipmentSlots.Backpack,
[itemToAdd],
botInventory,
1,
1,
(ItemLocation)itemToAdd.Location
);
}
var rootWeaponId = new MongoId();
var weaponWithChildren = new List<Item>();
var root = new Item { Id = rootWeaponId, Template = ItemTpl.ASSAULTRIFLE_MOLOT_ARMS_VPO136_VEPRKM_762X39_CARBINE };
var root = new Item
{
Id = rootWeaponId,
Template = ItemTpl.ASSAULTRIFLE_MOLOT_ARMS_VPO136_VEPRKM_762X39_CARBINE,
ParentId = backpack.Id,
};
weaponWithChildren.Add(root);
var stock = new Item
@@ -458,6 +464,7 @@ public class BotGeneratorHelperTests
weaponWithChildren.Add(muzzle);
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
botId,
[EquipmentSlots.Backpack],
rootWeaponId,
root.Template,