From c89c84dff36022786f546375eafe640ce1c2e2be Mon Sep 17 00:00:00 2001 From: Chomp Date: Wed, 30 Jul 2025 23:13:09 +0100 Subject: [PATCH] Converted `RemoveFiRStatusFromItemsInContainer` into extension method Improved performance of method by using breadth-first search to find children instead of loop Wrote tests for method --- .../Extensions/ItemExtensions.cs | 32 ++++ .../Helpers/InRaidHelper.cs | 28 ---- .../Services/LocationLifecycleService.cs | 2 +- UnitTests/Tests/Extensions/ItemTests.cs | 153 ++++++++++++++++++ 4 files changed, 186 insertions(+), 29 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs index 8a9bec1a..a0ce4565 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs @@ -416,5 +416,37 @@ namespace SPTarkov.Server.Core.Extensions return new InventoryItemHash { ByItemId = inventoryItems.ToDictionary(item => item.Id), ByParentId = byParentId }; } + + /// + /// Remove spawned in session (FiR) status from items inside a container + /// + /// Player profile + /// Container slot id to find items for and remove FiR from e.g. "Backpack" + public static void RemoveFiRStatusFromItemsInContainer(this PmcData pmcData, string containerSlotId) + { + var container = pmcData?.Inventory?.Items?.FirstOrDefault(item => item.SlotId == containerSlotId); + if (container is null) + { + return; + } + + var parentItemLookup = pmcData.Inventory.Items.ToLookup(item => item.ParentId); + var parentIdsToSearch = new Queue(); + parentIdsToSearch.Enqueue(container.Id); + + while (parentIdsToSearch.Count > 0) + { + var currentParentId = parentIdsToSearch.Dequeue(); + foreach (var childItem in parentItemLookup[currentParentId]) + { + if (childItem.Upd?.SpawnedInSession != null && childItem.Upd.SpawnedInSession.Value) + { + childItem.Upd.SpawnedInSession = false; + } + + parentIdsToSearch.Enqueue(childItem.Id); + } + } + } } } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs index f689352d..e9de0b4a 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs @@ -144,34 +144,6 @@ public class InRaidHelper(InventoryHelper inventoryHelper, ConfigServer configSe pmcData.Inventory.FastPanel = new(); } - /// - /// Remove FiR status from designated container. - /// - /// Session id - /// Player profile - /// Container slot id to find items for and remove FiR from - public void RemoveFiRStatusFromItemsInContainer(MongoId sessionId, PmcData pmcData, string secureContainerSlotId) - { - if (!pmcData.Inventory.Items.Any(item => item.SlotId == secureContainerSlotId)) - { - return; - } - - List itemsInsideContainer = []; - foreach (var inventoryItem in pmcData.Inventory.Items.Where(item => item.Upd is not null && item.SlotId != "hideout")) - { - if (inventoryItem.ItemIsInsideContainer(secureContainerSlotId, pmcData.Inventory.Items)) - { - itemsInsideContainer.Add(inventoryItem); - } - } - - foreach (var item in itemsInsideContainer.Where(item => item.Upd?.SpawnedInSession ?? false)) - { - item.Upd.SpawnedInSession = false; - } - } - /// /// Get a list of items from a profile that will be lost on death. /// diff --git a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs index 4f54bf1e..0f6da4f9 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs @@ -777,7 +777,7 @@ public class LocationLifecycleService( inRaidHelper.DeleteInventory(serverPmcProfile, sessionId); - inRaidHelper.RemoveFiRStatusFromItemsInContainer(sessionId, serverPmcProfile, "SecuredContainer"); + serverPmcProfile.RemoveFiRStatusFromItemsInContainer("SecuredContainer"); } // Must occur AFTER killer messages have been sent diff --git a/UnitTests/Tests/Extensions/ItemTests.cs b/UnitTests/Tests/Extensions/ItemTests.cs index 960bcd13..20dcd819 100644 --- a/UnitTests/Tests/Extensions/ItemTests.cs +++ b/UnitTests/Tests/Extensions/ItemTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Models.Common; +using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; namespace UnitTests.Tests.Extensions; @@ -139,4 +140,156 @@ public class ItemTests Assert.AreEqual(result[0].Id, rootItem.Id); Assert.AreEqual(result.Count, 1); } + + [Test] + public void RemoveFiRStatusFromItemsInContainer_twoItemsInBackpack() + { + var profile = new PmcData() { Inventory = new BotBaseInventory() { Items = [] } }; + profile.Inventory.Equipment = new MongoId(); + + // Add backpack + var backpackId = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = backpackId, + Template = ItemTpl.BACKPACK_HAZARD_4_PILLBOX_BACKPACK_BLACK, + ParentId = profile.Inventory.Equipment, + SlotId = "Backpack", + } + ); + + // Add ifak to first slot in backpack + var item1Id = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = item1Id, + Template = ItemTpl.MEDKIT_AFAK_TACTICAL_INDIVIDUAL_FIRST_AID_KIT, + ParentId = backpackId, + SlotId = "main", + Upd = new Upd { SpawnedInSession = true }, + Location = new ItemLocation + { + X = 0, + Y = 0, + R = ItemRotation.Horizontal, + }, + } + ); + + // Add wrench to first slot of ifak + var item2Id = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = item2Id, + Template = ItemTpl.BARTER_WRENCH, + ParentId = backpackId, + SlotId = "main", + Upd = new Upd { SpawnedInSession = true }, + Location = new ItemLocation + { + X = 1, + Y = 0, + R = ItemRotation.Horizontal, + }, + } + ); + + // Add armband to armband slot as root + var item3Id = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = item3Id, + Template = ItemTpl.ARMBAND_RED, + ParentId = profile.Inventory.Equipment, + SlotId = "Armband", + Upd = new Upd { SpawnedInSession = true }, + } + ); + + profile.RemoveFiRStatusFromItemsInContainer("Backpack"); + + Assert.AreEqual(false, profile.Inventory.Items.FirstOrDefault(item => item.Id == item1Id).Upd.SpawnedInSession); + Assert.AreEqual(false, profile.Inventory.Items.FirstOrDefault(item => item.Id == item2Id).Upd.SpawnedInSession); + Assert.AreEqual(true, profile.Inventory.Items.FirstOrDefault(item => item.Id == item3Id).Upd.SpawnedInSession); + } + + [Test] + public void RemoveFiRStatusFromItemsInContainer_oneItemWithChildInBackpack() + { + var profile = new PmcData { Inventory = new BotBaseInventory { Items = [] } }; + profile.Inventory.Equipment = new MongoId(); + + // Add backpack + var backpackId = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = backpackId, + Template = ItemTpl.BACKPACK_HAZARD_4_PILLBOX_BACKPACK_BLACK, + ParentId = profile.Inventory.Equipment, + SlotId = "Backpack", + } + ); + + // Add ifak to first slot in backpack + var item1Id = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = item1Id, + Template = ItemTpl.MEDKIT_AFAK_TACTICAL_INDIVIDUAL_FIRST_AID_KIT, + ParentId = backpackId, + SlotId = "main", + Upd = new Upd { SpawnedInSession = true }, + Location = new ItemLocation + { + X = 0, + Y = 0, + R = ItemRotation.Horizontal, + }, + } + ); + + // Add wrench to first slot of ifak as child + var item2Id = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = item2Id, + Template = ItemTpl.BARTER_WRENCH, + ParentId = item1Id, + SlotId = "main", + Upd = new Upd { SpawnedInSession = true }, + Location = new ItemLocation + { + X = 1, + Y = 0, + R = ItemRotation.Horizontal, + }, + } + ); + + // Add armband to armband slot as root + var item3Id = new MongoId(); + profile.Inventory.Items.Add( + new Item + { + Id = item3Id, + Template = ItemTpl.ARMBAND_RED, + ParentId = profile.Inventory.Equipment, + SlotId = "Armband", + Upd = new Upd { SpawnedInSession = true }, + } + ); + + profile.RemoveFiRStatusFromItemsInContainer("Backpack"); + + Assert.AreEqual(false, profile.Inventory.Items.FirstOrDefault(item => item.Id == item1Id).Upd.SpawnedInSession); + Assert.AreEqual(false, profile.Inventory.Items.FirstOrDefault(item => item.Id == item2Id).Upd.SpawnedInSession); + Assert.AreEqual(true, profile.Inventory.Items.FirstOrDefault(item => item.Id == item3Id).Upd.SpawnedInSession); + } }