diff --git a/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs b/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs index 7322fab7..3a0100f6 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs @@ -292,7 +292,7 @@ public class BotController( ); } - var maxThreads = botGenerationDetails.BotCountToGenerate.Value; + var maxThreads = botGenerationDetails.BotCountToGenerate; #if DEBUG // Make debugging bot gen easier diff --git a/Libraries/SPTarkov.Server.Core/Controllers/InventoryController.cs b/Libraries/SPTarkov.Server.Core/Controllers/InventoryController.cs index 842b2d5f..5c49fb01 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/InventoryController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/InventoryController.cs @@ -250,9 +250,12 @@ public class InventoryController( } case "ExamineAllItems": { - var itemsToInspect = itemHelper.GetItems().Where(x => x.Type != "Node"); - FlagItemsAsInspectedAndRewardXp(itemsToInspect.Select(x => x.Id), fullProfile); - logger.Success($"Flagged {itemsToInspect.Count()} items as examined"); + var itemIds = databaseService + .GetItems() + .Where(x => x.Value.Type != "Node") + .Select(x => x.Key); + FlagItemsAsInspectedAndRewardXp(itemIds, fullProfile); + logger.Success($"Flagged {itemIds.Count()} items as examined"); break; } diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs index 86cf69a2..743323e2 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs @@ -1020,7 +1020,8 @@ public class RagfairController( formattedRequirements.ToList(), loyalLevel, (int?)items.FirstOrDefault()?.Upd?.StackObjectsCount ?? 1, - sellInOnePiece + sellInOnePiece, + true ); } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs index 295c8803..edbebeb4 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs @@ -207,13 +207,7 @@ namespace SPTarkov.Server.Core.Extensions foreach (var childItem in items) { - if ( - string.Equals( - childItem.ParentId, - baseItemId, - StringComparison.OrdinalIgnoreCase - ) - ) + if (childItem.ParentId == baseItemId.ToString()) { list.AddRange(FindAndReturnChildrenByItems(items, childItem.Id)); } @@ -292,8 +286,9 @@ namespace SPTarkov.Server.Core.Extensions ) { // Use dictionary to make key lookup faster, convert to list before being returned + var itemList = items.ToList(); OrderedDictionary result = []; - foreach (var childItem in items) + foreach (var childItem in itemList) { // Include itself if (childItem.Id == baseItemId) @@ -403,9 +398,7 @@ namespace SPTarkov.Server.Core.Extensions 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) - ); + var childItems = items.Where(item => item.ParentId == originalId.ToString()); foreach (var childItem in childItems) { childItem.ParentId = newId; diff --git a/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs index 59585afe..4fa100b0 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs @@ -12,7 +12,7 @@ namespace SPTarkov.Server.Core.Extensions /// True - offer is stale public static bool IsStale(this RagfairOffer offer, long time) { - return offer.EndTime < time || (offer.Quantity ?? 0) < 1; + return offer.EndTime < time || (offer.Quantity) < 1; } } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs index d9b9a837..e566a958 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs @@ -55,7 +55,7 @@ namespace SPTarkov.Server.Core.Extensions /// Item to look up default plate /// front/back /// Tpl of plate - public static string? GetDefaultPlateTpl(this TemplateItem armorItem, string modSlot) + public static MongoId? GetDefaultPlateTpl(this TemplateItem armorItem, string modSlot) { var relatedItemDbModSlot = armorItem.Properties.Slots?.FirstOrDefault(slot => string.Equals(slot.Name, modSlot, StringComparison.OrdinalIgnoreCase) diff --git a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs index 9bd5c92a..42b3fa57 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs @@ -197,7 +197,8 @@ public class BotEquipmentModGenerator( ); switch (plateSlotFilteringOutcome.Result) { - case Result.UNKNOWN_FAILURE or Result.NO_DEFAULT_FILTER: + case Result.UNKNOWN_FAILURE + or Result.NO_DEFAULT_FILTER: if (logger.IsLogEnabled(LogLevel.Debug)) { logger.Debug( @@ -421,9 +422,9 @@ public class BotEquipmentModGenerator( var defaultPlate = armorItem.GetDefaultPlateTpl(modSlot); if (defaultPlate is not null) { - // Return Default Plates cause couldn't get lowest level available from original selection + // Return Default Plates cause couldn't get the lowest level available from original selection result.Result = Result.SUCCESS; - result.PlateModTemplates = [defaultPlate]; + result.PlateModTemplates = [defaultPlate.Value]; return result; } diff --git a/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs index e8efea15..129e61e5 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/FenceBaseAssortGenerator.cs @@ -37,22 +37,27 @@ public class FenceBaseAssortGenerator( var blockedSeasonalItems = seasonalEventService.GetInactiveSeasonalEventItems(); var baseFenceAssort = databaseService.GetTrader(Traders.FENCE).Assort; - foreach (var rootItemDb in itemHelper.GetItems().Where(IsValidFenceItem)) + foreach (var (itemId, rootItemDb) in databaseService.GetItems()) { + if (!string.Equals(rootItemDb.Type, "Item", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + // Skip blacklisted items - if (itemFilterService.IsItemBlacklisted(rootItemDb.Id)) + if (itemFilterService.IsItemBlacklisted(itemId)) { continue; } // Skip reward item blacklist - if (itemFilterService.IsItemRewardBlacklisted(rootItemDb.Id)) + if (itemFilterService.IsItemRewardBlacklisted(itemId)) { continue; } // Invalid - if (!itemHelper.IsValidItem(rootItemDb.Id)) + if (!itemHelper.IsValidItem(itemId)) { continue; } @@ -61,8 +66,8 @@ public class FenceBaseAssortGenerator( if (traderConfig.Fence.Blacklist.Count > 0) { if ( - traderConfig.Fence.Blacklist.Contains(rootItemDb.Id) - || itemHelper.IsOfBaseclasses(rootItemDb.Id, traderConfig.Fence.Blacklist) + traderConfig.Fence.Blacklist.Contains(itemId) + || itemHelper.IsOfBaseclasses(itemId, traderConfig.Fence.Blacklist) ) { continue; @@ -71,7 +76,7 @@ public class FenceBaseAssortGenerator( // Only allow rigs with no slots (carrier rigs) if ( - itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.VEST) + itemHelper.IsOfBaseclass(itemId, BaseClasses.VEST) && (rootItemDb.Properties?.Slots?.Count ?? 0) > 0 ) { @@ -79,10 +84,7 @@ public class FenceBaseAssortGenerator( } // Skip seasonal event items when not in seasonal event - if ( - traderConfig.Fence.BlacklistSeasonalItems - && blockedSeasonalItems.Contains(rootItemDb.Id) - ) + if (traderConfig.Fence.BlacklistSeasonalItems && blockedSeasonalItems.Contains(itemId)) { continue; } @@ -93,7 +95,7 @@ public class FenceBaseAssortGenerator( new() { Id = new MongoId(), - Template = rootItemDb.Id, + Template = itemId, ParentId = "hideout", SlotId = "hideout", Upd = new Upd { StackObjectsCount = 9999999 }, @@ -101,7 +103,7 @@ public class FenceBaseAssortGenerator( }; // Ensure ammo is not above penetration limit value - if (itemHelper.IsOfBaseclasses(rootItemDb.Id, [BaseClasses.AMMO_BOX, BaseClasses.AMMO])) + if (itemHelper.IsOfBaseclasses(itemId, [BaseClasses.AMMO_BOX, BaseClasses.AMMO])) { if (IsAmmoAbovePenetrationLimit(rootItemDb)) { @@ -109,7 +111,7 @@ public class FenceBaseAssortGenerator( } } - if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX)) + if (itemHelper.IsOfBaseclass(itemId, BaseClasses.AMMO_BOX)) // Only add cartridges to box if box has no children { if (itemWithChildrenToAdd.Count == 1) @@ -130,7 +132,7 @@ public class FenceBaseAssortGenerator( var barterSchemeToAdd = new BarterScheme { Count = Math.Round( - (double)fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd) + (double)fenceService.GetItemPrice(itemId, itemWithChildrenToAdd) ), Template = Money.ROUBLES, }; @@ -185,8 +187,8 @@ public class FenceBaseAssortGenerator( var itemQualityModifier = itemHelper.GetItemQualityModifierForItems(itemAndChildren); // Multiply weapon+mods rouble price by quality modifier - baseFenceAssort.BarterScheme[itemAndChildren[0].Id] = new List> - { + baseFenceAssort.BarterScheme[itemAndChildren[0].Id] = + [ new() { new BarterScheme @@ -195,7 +197,7 @@ public class FenceBaseAssortGenerator( Count = Math.Round(price * itemQualityModifier), }, }, - }; + ]; baseFenceAssort.LoyalLevelItems[itemAndChildren[0].Id] = 1; } diff --git a/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs index 884f663a..49298738 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/LootGenerator.cs @@ -49,7 +49,7 @@ public class LootGenerator( if (sealedWeaponCrateCount > 0) { // Get list of all sealed containers from db - they're all the same, just for flavor - var itemsDb = itemHelper.GetItems(); + var itemsDb = databaseService.GetItems().Values; var sealedWeaponContainerPool = itemsDb.Where(item => item.Name.Contains("event_container_airdrop") ); diff --git a/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs index 5161e009..1dddfc02 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs @@ -55,7 +55,7 @@ public class RagfairAssortGenerator( // Get cloned items from db var dbItemsClone = itemHelper - .GetItems() + .GetItemsClone() .Where(item => !string.Equals(item.Type, "Node", StringComparison.OrdinalIgnoreCase)); // Store processed preset tpls so we don't add them when processing non-preset items @@ -136,7 +136,7 @@ public class RagfairAssortGenerator( /// Hydrated Item object protected Item CreateRagfairAssortRootItem(MongoId tplId, MongoId? id = null) { - if (string.IsNullOrEmpty(id)) + if (id == null || id.Value.IsEmpty()) { id = new MongoId(); } diff --git a/Libraries/SPTarkov.Server.Core/Generators/RagfairOfferGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RagfairOfferGenerator.cs index b5f6dfbf..b76a2334 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/RagfairOfferGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/RagfairOfferGenerator.cs @@ -58,6 +58,7 @@ public class RagfairOfferGenerator( /// Loyalty level needed to buy item /// Amount of item being listed /// Flags sellInOnePiece to be true + /// Offer to create is for a player /// RagfairOffer public RagfairOffer CreateAndAddFleaOffer( MongoId userId, @@ -66,7 +67,8 @@ public class RagfairOfferGenerator( List barterScheme, int loyalLevel, int quantity, - bool sellInOnePiece = false + bool sellInOnePiece = false, + bool isPlayerOffer = false ) { var offer = CreateOffer( @@ -76,8 +78,13 @@ public class RagfairOfferGenerator( barterScheme, loyalLevel, quantity, - sellInOnePiece + sellInOnePiece, + isPlayerOffer ); + + offer.ExtensionData ??= new Dictionary(); + offer.ExtensionData.Add("isPlayerOffer", isPlayerOffer); + ragfairOfferService.AddOffer(offer); return offer; @@ -93,6 +100,7 @@ public class RagfairOfferGenerator( /// Loyalty level needed to buy item /// Amount of item being listed /// Is offer being created flagged as a pack + /// Offer is from a player /// RagfairOffer protected RagfairOffer CreateOffer( MongoId userId, @@ -101,7 +109,8 @@ public class RagfairOfferGenerator( List barterScheme, int loyalLevel, int quantity, - bool isPackOffer = false + bool isPackOffer = false, + bool isPlayerOffer = false ) { var offerRequirements = barterScheme @@ -132,8 +141,8 @@ public class RagfairOfferGenerator( // Hydrate ammo boxes with cartridges + ensure only 1 item is present (ammo box) // On offer refresh don't re-add cartridges to ammo box that already has cartridges if ( - itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX) - && itemsClone.Count == 1 + itemsClone.Count == 1 + && itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX) ) { itemHelper.AddCartridgesToAmmoBox( @@ -151,7 +160,9 @@ public class RagfairOfferGenerator( { Id = new MongoId(), InternalId = offerCounter, - User = CreateUserDataForFleaOffer(userId, ragfairServerHelper.IsTrader(userId)), + User = isPlayerOffer + ? CreatePlayerUserDataForFleaOffer(userId) + : CreateUserDataForFleaOffer(userId, ragfairServerHelper.IsTrader(userId)), Root = rootItem.Id, Items = itemsClone, ItemsCost = Math.Round(handbookHelper.GetTemplatePrice(rootItem.Template)), // Handbook price @@ -185,23 +196,6 @@ public class RagfairOfferGenerator( return new RagfairOfferUser { Id = userId, MemberType = MemberCategory.Trader }; } - var isPlayerOffer = profileHelper.IsPlayer(userId); - if (isPlayerOffer) - { - var playerProfile = profileHelper.GetPmcProfile(userId); - return new RagfairOfferUser - { - Id = playerProfile.Id.Value, - MemberType = playerProfile.Info.MemberCategory, - SelectedMemberCategory = playerProfile.Info.SelectedMemberCategory, - Nickname = playerProfile.Info.Nickname, - Rating = playerProfile.RagfairInfo.Rating ?? 0, - IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing, - Avatar = null, - Aid = playerProfile.Aid, - }; - } - // 'Fake' pmc offer return new RagfairOfferUser { @@ -218,6 +212,27 @@ public class RagfairOfferGenerator( }; } + /// + /// Create the user object stored inside each flea offer object + /// + /// Player id + /// OfferUser object + protected RagfairOfferUser CreatePlayerUserDataForFleaOffer(MongoId userId) + { + var playerProfile = profileHelper.GetPmcProfile(userId); + return new RagfairOfferUser + { + Id = playerProfile.Id.Value, + MemberType = playerProfile.Info.MemberCategory, + SelectedMemberCategory = playerProfile.Info.SelectedMemberCategory, + Nickname = playerProfile.Info.Nickname, + Rating = playerProfile.RagfairInfo.Rating ?? 0, + IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing, + Avatar = null, + Aid = playerProfile.Aid, + }; + } + /// /// Calculate the offer price that's listed on the flea listing /// @@ -1083,15 +1098,15 @@ public class RagfairOfferGenerator( // Filter possible barters to items that match the price range + not itself var min = desiredItemCostRouble - offerCostVarianceRoubles; var max = desiredItemCostRouble + offerCostVarianceRoubles; - var itemsInsidePriceBounds = itemFleaPrices.Where(itemAndPrice => - itemAndPrice.Price >= min - && itemAndPrice.Price <= max - && !string.Equals( - itemAndPrice.Tpl, - offerItems[0].Template, - StringComparison.OrdinalIgnoreCase - ) // Don't allow the item being sold to be chosen - ); + var rootOfferItem = offerItems.FirstOrDefault(); + + var itemsInsidePriceBounds = itemFleaPrices + .Where(itemAndPrice => + itemAndPrice.Price >= min + && itemAndPrice.Price <= max + && itemAndPrice.Tpl != rootOfferItem.Template // Don't allow the item being sold to be chosen + ) + .ToList(); // No items on flea have a matching price, fall back to currency if (!itemsInsidePriceBounds.Any()) @@ -1100,7 +1115,7 @@ public class RagfairOfferGenerator( } // Choose random item from price-filtered flea items - var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds.ToList()); + var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds); return [new BarterScheme { Count = barterItemCount, Template = randomItem.Tpl }]; } 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 5cfbcb3d..72476843 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 @@ -152,7 +152,7 @@ public class GiveSptCommand( localizedGlobal = GetGlobalsLocale(locale); var allAllowedItemNames = _itemHelper - .GetItems() + .GetItemsClone() .Where(IsItemAllowed) .Select(i => localizedGlobal @@ -207,7 +207,7 @@ public class GiveSptCommand( // item is just the tplId. MongoId tplId = isItemName ? _itemHelper - .GetItems() + .GetItemsClone() .Where(IsItemAllowed) .FirstOrDefault(i => (localizedGlobal[$"{i?.Id} Name"]?.ToLowerInvariant() ?? i.Properties.Name) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/DialogueHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/DialogueHelper.cs index 5ec16960..348af721 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/DialogueHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/DialogueHelper.cs @@ -47,7 +47,7 @@ public class DialogueHelper(ISptLogger logger, ProfileHelper pro /// Session/player id /// Item being moved to inventory /// Collection of items from message - public List GetMessageItemContents(string messageID, MongoId sessionID, MongoId itemId) + public List GetMessageItemContents(MongoId messageID, MongoId sessionID, MongoId itemId) { var fullProfile = profileHelper.GetFullProfile(sessionID); var dialogueData = fullProfile.DialogueRecords; diff --git a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs index cd968100..8eacb84c 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs @@ -1013,7 +1013,7 @@ public class InventoryHelper( { // Split requests don't use 'use' but 'splitItem' property fromInventoryItems = dialogueHelper.GetMessageItemContents( - request.FromOwner.Id, + request.FromOwner.Id.Value, sessionId, itemId ); diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs index 42503dfe..2f3a3eb6 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs @@ -121,9 +121,7 @@ public class ItemHelper( ); // Check if any item in the filtered pool matches the provided item - return filteredPool.Any(poolItem => - string.Equals(poolItem.Template, itemTpl, StringComparison.OrdinalIgnoreCase) - ); + return filteredPool.Any(poolItem => poolItem.Template == itemTpl); } /// @@ -283,7 +281,7 @@ public class ItemHelper( /// Template id to check /// OPTIONAL - Base types deemed invalid /// true for items that may be in player possession and not quest items - public bool IsValidItem(MongoId tpl, ICollection? invalidBaseTypes = null) + public bool IsValidItem(MongoId tpl, ISet? invalidBaseTypes = null) { var baseTypes = invalidBaseTypes ?? _defaultInvalidBaseTypes; var itemDetails = GetItem(tpl); @@ -295,9 +293,9 @@ public class ItemHelper( return !(itemDetails.Value.Properties.QuestItem ?? false) && string.Equals(itemDetails.Value.Type, "Item", StringComparison.OrdinalIgnoreCase) - && baseTypes.All(x => !IsOfBaseclass(tpl, x)) && GetItemPrice(tpl) > 0 - && !itemFilterService.IsItemBlacklisted(tpl); + && !itemFilterService.IsItemBlacklisted(tpl) + && baseTypes.All(x => !IsOfBaseclass(tpl, x)); } /// @@ -502,7 +500,7 @@ public class ItemHelper( /// Get cloned copy of all item data from items.json /// /// List of TemplateItem objects - public List GetItems() + public List GetItemsClone() { return cloner.Clone(databaseService.GetItems().Values.ToList()); } @@ -724,14 +722,8 @@ public class ItemHelper( { // Parent matches desired item + all items in list do not match if ( - string.Equals( - itemFromAssort.ParentId, - itemIdToFind, - StringComparison.OrdinalIgnoreCase - ) - && list.All(item => - !string.Equals(itemFromAssort.Id, item.Id, StringComparison.Ordinal) - ) + itemFromAssort.ParentId == itemIdToFind + && list.All(item => itemFromAssort.Id != item.Id) ) { list.Add(itemFromAssort); @@ -937,7 +929,7 @@ public class ItemHelper( // Update all parentIds of items attached to base item to use new id foreach (var item in itemWithChildren) { - if (string.Equals(item.ParentId, oldId, StringComparison.OrdinalIgnoreCase)) + if (item.ParentId == oldId) { item.ParentId = newId; } @@ -992,9 +984,7 @@ public class ItemHelper( item.Id = newId; // Find all children of item and update their parent ids to match - var childItems = inventory.Items.Where(x => - string.Equals(x.ParentId, originalId, StringComparison.OrdinalIgnoreCase) - ); + var childItems = inventory.Items.Where(x => x.ParentId == originalId); foreach (var childItem in childItems) { childItem.ParentId = newId; @@ -1072,9 +1062,7 @@ public class ItemHelper( item.Id = newId; // Find all children of item and update their parent ids to match - var childItems = originalItems.Where(x => - string.Equals(x.ParentId, originalId, StringComparison.OrdinalIgnoreCase) - ); + var childItems = originalItems.Where(x => x.ParentId == originalId); foreach (var childItem in childItems) { childItem.ParentId = newId; diff --git a/Libraries/SPTarkov.Server.Core/Helpers/QuestHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/QuestHelper.cs index ec72eb9a..04f39102 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/QuestHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/QuestHelper.cs @@ -1482,14 +1482,7 @@ public class QuestHelper( return true; } - if ( - condition.Target.IsItem - && string.Equals( - condition.Target.Item, - completedQuestId, - StringComparison.InvariantCultureIgnoreCase - ) - ) + if (condition.Target.IsItem && condition.Target.Item == completedQuestId) { // Not a list, plain string return true; diff --git a/Libraries/SPTarkov.Server.Core/Helpers/RagfairOfferHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/RagfairOfferHelper.cs index a136851b..525f6548 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/RagfairOfferHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/RagfairOfferHelper.cs @@ -92,7 +92,7 @@ public class RagfairOfferHelper( CheckAndLockOfferFromPlayerTieredFlea( tieredFlea, offer, - tieredFleaLimitTypes.Keys.ToList(), + tieredFleaLimitTypes.Keys.ToHashSet(), pmcData.Info.Level.Value ); } @@ -112,7 +112,7 @@ public class RagfairOfferHelper( protected void CheckAndLockOfferFromPlayerTieredFlea( TieredFlea tieredFlea, RagfairOffer offer, - List tieredFleaLimitTypes, + HashSet tieredFleaLimitTypes, int playerLevel ) { @@ -199,7 +199,7 @@ public class RagfairOfferHelper( CheckAndLockOfferFromPlayerTieredFlea( tieredFlea, offer, - tieredFleaLimitTypes.Keys.ToList(), + tieredFleaLimitTypes.Keys.ToHashSet(), pmcData.Info.Level.Value ); } @@ -296,7 +296,7 @@ public class RagfairOfferHelper( CheckAndLockOfferFromPlayerTieredFlea( tieredFlea, offer, - tieredFleaLimitTypes.Keys.ToList(), + tieredFleaLimitTypes.Keys.ToHashSet(), pmcData.Info.Level.Value ); diff --git a/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs b/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs index 1ce73646..5ca1f10a 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs @@ -74,7 +74,7 @@ public readonly struct MongoId : IEquatable { if (other is null) { - return other == this; + return false; } return other.ToString().Equals(ToString(), StringComparison.InvariantCultureIgnoreCase); @@ -142,7 +142,7 @@ public readonly struct MongoId : IEquatable public bool IsEmpty() { - if (_stringId == "000000000000000000000000" || string.IsNullOrEmpty(_stringId)) + if (string.IsNullOrEmpty(_stringId) || _stringId == "000000000000000000000000") { return true; } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/Trader.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/Trader.cs index 279bdfd6..b70ac800 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/Trader.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/Trader.cs @@ -138,11 +138,11 @@ public record ItemBuyData // MongoId [JsonPropertyName("category")] - public required List Category { get; set; } + public required HashSet Category { get; set; } // MongoId [JsonPropertyName("id_list")] - public required List IdList { get; set; } + public required HashSet IdList { get; set; } } public record ItemSellData @@ -151,10 +151,10 @@ public record ItemSellData public Dictionary? ExtensionData { get; set; } [JsonPropertyName("category")] - public required List Category { get; set; } + public required HashSet Category { get; set; } [JsonPropertyName("id_list")] - public required List IdList { get; set; } + public required HashSet IdList { get; set; } } public record TraderInsurance diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Ragfair/RagfairOffer.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Ragfair/RagfairOffer.cs index dd3c41ab..2e674366 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Ragfair/RagfairOffer.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Ragfair/RagfairOffer.cs @@ -88,7 +88,7 @@ public record RagfairOffer /// Tightly bound to offer.items[0].upd.stackObjectsCount /// [JsonPropertyName("quantity")] - public int? Quantity { get; set; } + public int Quantity { get; set; } } public record OfferRequirement diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/TraderConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/TraderConfig.cs index ace6a170..541f2230 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/TraderConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/TraderConfig.cs @@ -106,7 +106,7 @@ public record FenceConfig /// Prevent duplicate offers of items of specific categories by parentId /// [JsonPropertyName("preventDuplicateOffersOfCategory")] - public required List PreventDuplicateOffersOfCategory { get; set; } + public required HashSet PreventDuplicateOffersOfCategory { get; set; } [JsonPropertyName("regenerateAssortsOnRefresh")] public bool RegenerateAssortsOnRefresh { get; set; } diff --git a/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs b/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs index 37996336..5716cd87 100644 --- a/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/AirdropService.cs @@ -242,8 +242,8 @@ public class AirdropService( // Get all items that match the blacklisted types and fold into item blacklist var itemTypeBlacklist = _itemFilterService.GetItemRewardBaseTypeBlacklist(); var itemsMatchingTypeBlacklist = _itemHelper - .GetItems() - .Where(templateItem => !string.IsNullOrEmpty(templateItem.Parent)) + .GetItemsClone() + .Where(templateItem => !templateItem.Parent.IsEmpty()) .Where(templateItem => _itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist) ) diff --git a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs index 9d73b0a6..219388e5 100644 --- a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs @@ -901,7 +901,7 @@ public class CircleOfCultistService( bool itemsShouldBeHighValue ) { - var allItems = itemHelper.GetItems(); + var allItems = itemHelper.GetItemsClone(); var currentItemCount = 0; var attempts = 0; // `currentItemCount` var will look for the correct number of items, `attempts` var will keep this from never stopping if the highValueThreshold is too high diff --git a/Libraries/SPTarkov.Server.Core/Services/FenceService.cs b/Libraries/SPTarkov.Server.Core/Services/FenceService.cs index 15d171c0..acdc7c80 100644 --- a/Libraries/SPTarkov.Server.Core/Services/FenceService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/FenceService.cs @@ -814,7 +814,7 @@ public class FenceService( var childItemsAndSingleRoot = baseFenceAssortClone .Items.Where(item => !string.Equals(item.ParentId, "hideout", StringComparison.Ordinal) - || string.Equals(item.Id, chosenBaseAssortRoot.Id, StringComparison.Ordinal) + || item.Id == chosenBaseAssortRoot.Id ) .ToList(); diff --git a/Libraries/SPTarkov.Server.Core/Services/ItemBaseClassService.cs b/Libraries/SPTarkov.Server.Core/Services/ItemBaseClassService.cs index db2246a5..65bd2116 100644 --- a/Libraries/SPTarkov.Server.Core/Services/ItemBaseClassService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/ItemBaseClassService.cs @@ -89,25 +89,28 @@ public class ItemBaseClassService( return false; } - if (_itemBaseClassesCache.TryGetValue(itemTpl, out var baseClassList)) + var existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out var baseClassList); + if (!existsInCache) + { + // Not found + if (logger.IsLogEnabled(LogLevel.Debug)) + { + logger.Debug( + serverLocalisationService.GetText("baseclass-item_not_found", itemTpl) + ); + } + + // Not found in cache, Hydrate again - some mods add items late in server startup lifecycle + HydrateItemBaseClassCache(); + + existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out baseClassList); + } + + if (existsInCache) { return baseClassList.Overlaps(baseClasses); } - if (logger.IsLogEnabled(LogLevel.Debug)) - { - logger.Debug(serverLocalisationService.GetText("baseclass-item_not_found", itemTpl)); - } - - // Not found in cache, Hydrate again - some mods add items late - HydrateItemBaseClassCache(); - - // Check for item again, return false if item not found a second time - if (_itemBaseClassesCache.TryGetValue(itemTpl, out var value)) - { - return value.Any(baseClasses.Contains); - } - logger.Warning( serverLocalisationService.GetText("baseclass-item_not_found_failed", itemTpl) ); diff --git a/Libraries/SPTarkov.Server.Core/Utils/RagfairOfferHolder.cs b/Libraries/SPTarkov.Server.Core/Utils/RagfairOfferHolder.cs index 61e0efdc..86daca33 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/RagfairOfferHolder.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/RagfairOfferHolder.cs @@ -146,8 +146,11 @@ public class RagfairOfferHolder( var sellerIsTrader = _ragfairServerHelper.IsTrader(sellerId); var itemSoldTemplate = _itemHelper.GetItem(itemTpl); if ( - !string.IsNullOrEmpty(itemTpl) - && !(sellerIsTrader || _profileHelper.IsPlayer(sellerId)) + !itemTpl.IsEmpty() + && !( + sellerIsTrader + || (bool)offer.ExtensionData.GetValueOrDefault("isPlayerOffer", false) + ) && _offersByTemplate.TryGetValue(itemTpl, out var offers) && offers?.Count >= _ragfairServerHelper.GetOfferCountByBaseType(itemSoldTemplate.Value.Parent)