diff --git a/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs b/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs index cf15c816..da9f18c2 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs @@ -1,4 +1,5 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Generators; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; @@ -1215,10 +1216,12 @@ public class HideoutController( var countOfItemsToReward = recipe.Count; for (var index = 1; index < countOfItemsToReward; index++) { - var itemAndMods = _itemHelper.ReplaceIDs( - _cloner.Clone(itemAndChildrenToSendToPlayer.FirstOrDefault()) - ); - itemAndChildrenToSendToPlayer.AddRange([itemAndMods]); + var firstItemWithChildrenClone = _cloner + .Clone(itemAndChildrenToSendToPlayer.FirstOrDefault()) + .ReplaceIDs() + .ToList(); + + itemAndChildrenToSendToPlayer.AddRange([firstItemWithChildrenClone]); } } @@ -1231,12 +1234,12 @@ public class HideoutController( var defaultPreset = _presetHelper.GetDefaultPreset(recipe.EndProduct); // Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory - var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(defaultPreset.Items)); + var presetAndModsClone = _cloner.Clone(defaultPreset.Items).ReplaceIDs().ToList(); - _itemHelper.RemapRootItemId(presetAndMods); + _itemHelper.RemapRootItemId(presetAndModsClone); // Store preset items in array - return [presetAndMods]; + return [presetAndModsClone]; } /// diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs index 6f1074c7..f6212eb0 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs @@ -382,5 +382,36 @@ namespace SPTarkov.Server.Core.Extensions // Return all items returned and exclude the secure container item itself return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToList(); } + + /// + /// Regenerate all GUIDs with new IDs, except special item types (e.g. quest, sorting table, etc.) + /// + /// + /// + public static IEnumerable ReplaceIDs(this IEnumerable items) + { + foreach (var item in items) + { + // Generate new id + var newId = new MongoId(); + + // Keep copy of original id + var originalId = item.Id; + + // Update items id to new one we generated + item.Id = newId; + + // Find all children of item and update their parent ids to match + var childItems = items.Where(x => + string.Equals(x.ParentId, originalId, StringComparison.OrdinalIgnoreCase) + ); + foreach (var childItem in childItems) + { + childItem.ParentId = newId; + } + } + + return items; + } } } diff --git a/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs index 7a497cac..cc633db3 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs @@ -1,4 +1,5 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; @@ -162,7 +163,7 @@ public class FenceBaseAssortGenerator( } // Construct preset + mods - var itemAndChildren = itemHelper.ReplaceIDs(_cloner.Clone(defaultPreset.Items)); + var itemAndChildren = _cloner.Clone(defaultPreset.Items).ReplaceIDs().ToList(); // Find root item and add some properties to it for (var i = 0; i < itemAndChildren.Count; i++) diff --git a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs index 06d00e1f..bcffd5fb 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs @@ -1171,7 +1171,7 @@ public class LocationLootGenerator( var itemWithChildren = lootItems.FindAndReturnChildrenAsItems(chosenItem.Id); // Ensure all IDs are unique - itemWithChildren = _itemHelper.ReplaceIDs(_cloner.Clone(itemWithChildren)); + itemWithChildren = _cloner.Clone(itemWithChildren).ReplaceIDs().ToList(); if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template)) // Strip children from parent before adding @@ -1280,12 +1280,12 @@ public class LocationLootGenerator( var defaultPreset = _presetHelper.GetDefaultPreset(chosenTpl); if (defaultPreset is not null) { - var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(defaultPreset.Items)); - _itemHelper.RemapRootItemId(presetAndMods); + var presetAndModsClone = _cloner.Clone(defaultPreset.Items).ReplaceIDs().ToList(); + _itemHelper.RemapRootItemId(presetAndModsClone); // Use original items parentId otherwise item doesn't get added to container correctly - presetAndMods.FirstOrDefault().ParentId = rootItem.ParentId; - items = presetAndMods; + presetAndModsClone.FirstOrDefault().ParentId = rootItem.ParentId; + items = presetAndModsClone; } else { diff --git a/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs index d795b9f2..39b81d22 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; @@ -200,12 +201,13 @@ public class LootGenerator( for (var i = 0; i < randomisedItemCount; i++) { // Clone preset and alter Ids to be unique - var presetWithUniqueIds = _itemHelper.ReplaceIDs( - _cloner.Clone(preset.Items) - ); + var presetWithUniqueIdsClone = _cloner + .Clone(preset.Items) + .ReplaceIDs() + .ToList(); // Add to results - result.Add(presetWithUniqueIds); + result.Add(presetWithUniqueIdsClone); } } @@ -497,13 +499,13 @@ public class LootGenerator( return false; } - var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(chosenPreset.Items)); - _itemHelper.RemapRootItemId(presetAndMods); + var presetAndModsClone = _cloner.Clone(chosenPreset.Items).ReplaceIDs().ToList(); + _itemHelper.RemapRootItemId(presetAndModsClone); - _itemHelper.SetFoundInRaid(presetAndMods); + _itemHelper.SetFoundInRaid(presetAndModsClone); // Add chosen preset tpl to result array - result.Add(presetAndMods); + result.Add(presetAndModsClone); if (itemLimitCount is not null) // Increment item count as item has been chosen and its inside itemLimitCount dictionary @@ -565,13 +567,13 @@ public class LootGenerator( } // Clean up Ids to ensure they're all unique and prevent collisions - var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(chosenWeaponPreset.Items)); - _itemHelper.RemapRootItemId(presetAndMods); + var presetAndModsClone = _cloner.Clone(chosenWeaponPreset.Items).ReplaceIDs().ToList(); + _itemHelper.RemapRootItemId(presetAndModsClone); // Add preset to return object - itemsToReturn.Add(presetAndMods); + itemsToReturn.Add(presetAndModsClone); - // Get a random collection of weapon mods related to chosen weawpon and add them to result array + // Get a random collection of weapon mods related to chosen weapon and add them to result array var linkedItemsToWeapon = _ragfairLinkedItemService.GetLinkedDbItems(chosenWeaponTpl); itemsToReturn.AddRange( GetSealedContainerWeaponModRewards( @@ -763,7 +765,7 @@ public class LootGenerator( var preset = _presetHelper.GetDefaultPreset(chosenRewardItemTpl); // Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory - var presetAndMods = _itemHelper.ReplaceIDs(preset.Items); + var presetAndMods = preset.Items.ReplaceIDs().ToList(); _itemHelper.RemapRootItemId(presetAndMods); itemsToReturn.Add(presetAndMods); diff --git a/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs index 9ecfef97..e1a1fc58 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs @@ -1,4 +1,5 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; @@ -65,22 +66,22 @@ public class RagfairAssortGenerator( foreach (var preset in presets) { // Update Ids and clone - var presetAndMods = itemHelper.ReplaceIDs(cloner.Clone(preset.Items)); - itemHelper.RemapRootItemId(presetAndMods); + var presetAndModsClone = cloner.Clone(preset.Items).ReplaceIDs().ToList(); + itemHelper.RemapRootItemId(presetAndModsClone); // Add presets base item tpl to the processed list so its skipped later on when processing items processedArmorItems.Add(preset.Items[0].Template); - presetAndMods[0].ParentId = "hideout"; - presetAndMods[0].SlotId = "hideout"; - presetAndMods[0].Upd = new Upd + presetAndModsClone[0].ParentId = "hideout"; + presetAndModsClone[0].SlotId = "hideout"; + presetAndModsClone[0].Upd = new Upd { StackObjectsCount = 99999999, UnlimitedCount = true, SptPresetId = preset.Id, }; - results.Add(presetAndMods); + results.Add(presetAndModsClone); } foreach (var item in dbItemsClone) diff --git a/Libraries/SPTarkov.Server.Core/Generators/ScavCaseRewardGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/ScavCaseRewardGenerator.cs index acb57e46..107364be 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/ScavCaseRewardGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/ScavCaseRewardGenerator.cs @@ -1,5 +1,6 @@ using SPTarkov.Common.Extensions; using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; @@ -403,7 +404,7 @@ public class ScavCaseRewardGenerator( } // Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory - var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(preset.Items)); + var presetAndMods = _cloner.Clone(preset.Items).ReplaceIDs().ToList(); _itemHelper.RemapRootItemId(presetAndMods); resultItem = presetAndMods; diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs index 2a86244d..a2c169e6 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs @@ -234,7 +234,7 @@ public class GiveSptCommand( for (var i = 0; i < quantity; i++) { var items = _cloner.Clone(preset.Items); - items = _itemHelper.ReplaceIDs(items); + items = items.ReplaceIDs().ToList(); itemsToSend.AddRange(items); } } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs index 1c0a97d0..2e2f6618 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs @@ -837,7 +837,7 @@ public class ItemHelper( while (remainingCount.Value != 0) { // Clone item and make IDs unique - var itemWithChildrenClone = ReplaceIDs(_cloner.Clone(itemWithChildren)); + var itemWithChildrenClone = _cloner.Clone(itemWithChildren).ReplaceIDs().ToList(); // Set stack count to new value var amount = Math.Min(remainingCount ?? 0, maxStackSize ?? 0); @@ -1017,37 +1017,6 @@ public class ItemHelper( } } - /// - /// Regenerate all GUIDs with new IDs, except special item types (e.g. quest, sorting table, etc.) - /// - /// - /// - public List ReplaceIDs(List items) - { - foreach (var item in items) - { - // Generate new id - var newId = new MongoId(); - - // Keep copy of original id - var originalId = item.Id; - - // Update items id to new one we generated - item.Id = newId; - - // Find all children of item and update their parent ids to match - var childItems = items.Where(x => - string.Equals(x.ParentId, originalId, StringComparison.OrdinalIgnoreCase) - ); - foreach (var childItem in childItems) - { - childItem.ParentId = newId; - } - } - - return items; - } - /// /// Regenerate all GUIDs with new IDs, except special item types (e.g. quest, sorting table, etc.) This /// function will not mutate the original items list, but will return a new list with new GUIDs. diff --git a/Libraries/SPTarkov.Server.Core/Helpers/RewardHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/RewardHelper.cs index ce617594..23ac0f6d 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/RewardHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/RewardHelper.cs @@ -383,7 +383,7 @@ public class RewardHelper( if (defaultPreset is not null) { // Found preset, use mods to hydrate reward item - var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(defaultPreset.Items)); + var presetAndMods = _cloner.Clone(defaultPreset.Items).ReplaceIDs().ToList(); var newRootId = _itemHelper.RemapRootItemId(presetAndMods); reward.Items = presetAndMods; diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/InventoryConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/InventoryConfig.cs index 96d7291e..64af64a8 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/InventoryConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/InventoryConfig.cs @@ -66,7 +66,7 @@ public record SealedAirdropContainerSettings public Dictionary? ExtensionData { get; set; } [JsonPropertyName("weaponRewardWeight")] - public required Dictionary WeaponRewardWeight { get; set; } + public required Dictionary WeaponRewardWeight { get; set; } [JsonPropertyName("defaultPresetsOnly")] public bool DefaultPresetsOnly { get; set; } diff --git a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs index fbd46713..95212670 100644 --- a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs @@ -399,7 +399,7 @@ public class CircleOfCultistService( } // Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory - var presetAndMods = _itemHelper.ReplaceIDs(defaultPreset.Items); + var presetAndMods = defaultPreset.Items.ReplaceIDs().ToList(); _itemHelper.RemapRootItemId(presetAndMods); // Set item as FiR @@ -494,7 +494,7 @@ public class CircleOfCultistService( } // Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory - var presetAndMods = _itemHelper.ReplaceIDs(defaultPreset.Items); + var presetAndMods = defaultPreset.Items.ReplaceIDs().ToList(); _itemHelper.RemapRootItemId(presetAndMods); // Set item as FiR diff --git a/Libraries/SPTarkov.Server.Core/Services/FenceService.cs b/Libraries/SPTarkov.Server.Core/Services/FenceService.cs index 242e6809..6d2aecae 100644 --- a/Libraries/SPTarkov.Server.Core/Services/FenceService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/FenceService.cs @@ -858,9 +858,10 @@ public class FenceService( } // MUST randomise Ids as its possible to add the same base fence assort twice = duplicate IDs = dead client - desiredAssortItemAndChildrenClone = itemHelper.ReplaceIDs( - _cloner.Clone(desiredAssortItemAndChildrenClone) - ); + desiredAssortItemAndChildrenClone = _cloner + .Clone(desiredAssortItemAndChildrenClone) + .ReplaceIDs() + .ToList(); itemHelper.RemapRootItemId(desiredAssortItemAndChildrenClone); var rootItemBeingAdded = desiredAssortItemAndChildrenClone[0]; diff --git a/Libraries/SPTarkov.Server.Core/Services/MailSendService.cs b/Libraries/SPTarkov.Server.Core/Services/MailSendService.cs index 1d776eee..98534efd 100644 --- a/Libraries/SPTarkov.Server.Core/Services/MailSendService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/MailSendService.cs @@ -516,7 +516,7 @@ public class MailSendService( }; // Ensure Ids are unique and cont collide with items in player inventory later - messageDetails.Items = _itemHelper.ReplaceIDs(_cloner.Clone(messageDetails.Items)); + messageDetails.Items = _cloner.Clone(messageDetails.Items).ReplaceIDs().ToList(); // Ensure item exits in items db foreach (var reward in messageDetails.Items)