diff --git a/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs
index d522baf7..92a07782 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs
@@ -32,6 +32,8 @@ public class TradeHelper(
ICloner _cloner
)
{
+ protected static Lock buyLock = new();
+
///
/// Buy item from flea or trader
///
@@ -48,203 +50,207 @@ public class TradeHelper(
ItemEventRouterResponse output
)
{
- List- offerItems = [];
- Action? buyCallback;
-
- if (string.Equals(buyRequestData.TransactionId, "ragfair", StringComparison.OrdinalIgnoreCase))
+ lock (buyLock)
{
- // Called when player purchases PMC offer from ragfair
- buyCallback = buyCount =>
+ List
- offerItems = [];
+ Action? buyCallback;
+
+ if (string.Equals(buyRequestData.TransactionId, "ragfair", StringComparison.OrdinalIgnoreCase))
{
- var allOffers = _ragfairServer.GetOffers();
-
- // We store ragfair offerId in buyRequestData.item_id
- var offerWithItem = allOffers.FirstOrDefault(x => x.Id == buyRequestData.ItemId);
- var itemPurchased = offerWithItem.Items.FirstOrDefault();
-
- // Ensure purchase does not exceed trader item limit
- var assortHasBuyRestrictions = _itemHelper.HasBuyRestrictions(itemPurchased);
- if (assortHasBuyRestrictions)
+ // Called when player purchases PMC offer from ragfair
+ buyCallback = buyCount =>
{
- CheckPurchaseIsWithinTraderItemLimit(
- sessionID,
- pmcData,
- buyRequestData.TransactionId,
- itemPurchased,
- buyRequestData.ItemId,
- buyCount
- );
+ var allOffers = _ragfairServer.GetOffers();
+
+ // We store ragfair offerId in buyRequestData.item_id
+ var offerWithItem = allOffers.FirstOrDefault(x => x.Id == buyRequestData.ItemId);
+ var itemPurchased = offerWithItem.Items.FirstOrDefault();
+
+ // Ensure purchase does not exceed trader item limit
+ var assortHasBuyRestrictions = _itemHelper.HasBuyRestrictions(itemPurchased);
+ if (assortHasBuyRestrictions)
+ {
+ CheckPurchaseIsWithinTraderItemLimit(
+ sessionID,
+ pmcData,
+ buyRequestData.TransactionId,
+ itemPurchased,
+ buyRequestData.ItemId,
+ buyCount
+ );
+
+ // Decrement trader item count
+ var itemPurchaseDetails = new PurchaseDetails
+ {
+ Items =
+ [
+ new PurchaseItems
+ {
+ ItemId = buyRequestData.ItemId,
+ Count = buyCount
+ }
+ ],
+ TraderId = buyRequestData.TransactionId
+ };
+ _traderHelper.AddTraderPurchasesToPlayerProfile(sessionID, itemPurchaseDetails, itemPurchased);
+ }
+ };
+
+ // buyCallback = BuyCallback1;
+ // Get raw offer from ragfair, clone to prevent altering offer itself
+ var allOffers = _ragfairServer.GetOffers();
+ var offerWithItemCloned = _cloner.Clone(allOffers.FirstOrDefault(x => x.Id == buyRequestData.ItemId));
+ offerItems = offerWithItemCloned.Items;
+ }
+ else if (buyRequestData.TransactionId == Traders.FENCE)
+ {
+ buyCallback = buyCount =>
+ {
+ // Update assort/flea item values
+ var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(buyRequestData.TransactionId).Items;
+ var itemPurchased = traderAssorts.FirstOrDefault(assort => assort.Id == buyRequestData.ItemId);
// Decrement trader item count
- var itemPurchaseDetails = new PurchaseDetails
- {
- Items =
- [
- new PurchaseItems
- {
- ItemId = buyRequestData.ItemId,
- Count = buyCount
- }
- ],
- TraderId = buyRequestData.TransactionId
- };
- _traderHelper.AddTraderPurchasesToPlayerProfile(sessionID, itemPurchaseDetails, itemPurchased);
- }
- };
+ itemPurchased.Upd.StackObjectsCount -= buyCount;
- // buyCallback = BuyCallback1;
- // Get raw offer from ragfair, clone to prevent altering offer itself
- var allOffers = _ragfairServer.GetOffers();
- var offerWithItemCloned = _cloner.Clone(allOffers.FirstOrDefault(x => x.Id == buyRequestData.ItemId));
- offerItems = offerWithItemCloned.Items;
- }
- else if (buyRequestData.TransactionId == Traders.FENCE)
- {
- buyCallback = buyCount =>
- {
- // Update assort/flea item values
- var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(buyRequestData.TransactionId).Items;
- var itemPurchased = traderAssorts.FirstOrDefault(assort => assort.Id == buyRequestData.ItemId);
+ _fenceService.AmendOrRemoveFenceOffer(buyRequestData.ItemId, buyCount);
+ };
- // Decrement trader item count
- itemPurchased.Upd.StackObjectsCount -= buyCount;
-
- _fenceService.AmendOrRemoveFenceOffer(buyRequestData.ItemId, buyCount);
- };
-
- var fenceItems = _fenceService.GetRawFenceAssorts().Items;
- var rootItemIndex = fenceItems.FindIndex(item => item.Id == buyRequestData.ItemId);
- if (rootItemIndex == -1)
- {
- if (_logger.IsLogEnabled(LogLevel.Debug))
+ var fenceItems = _fenceService.GetRawFenceAssorts().Items;
+ var rootItemIndex = fenceItems.FindIndex(item => item.Id == buyRequestData.ItemId);
+ if (rootItemIndex == -1)
{
- _logger.Debug($"Tried to buy item {buyRequestData.ItemId} from fence that no longer exists");
+ if (_logger.IsLogEnabled(LogLevel.Debug))
+ {
+ _logger.Debug($"Tried to buy item {buyRequestData.ItemId} from fence that no longer exists");
+ }
+
+ var message = _localisationService.GetText("ragfair-offer_no_longer_exists");
+ _httpResponseUtil.AppendErrorToOutput(output, message);
+
+ return;
}
- var message = _localisationService.GetText("ragfair-offer_no_longer_exists");
- _httpResponseUtil.AppendErrorToOutput(output, message);
+ offerItems = _itemHelper.FindAndReturnChildrenAsItems(fenceItems, buyRequestData.ItemId);
+ }
+ else
+ {
+ buyCallback = buyCount =>
+ {
+ // Update assort/flea item values
+ var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(buyRequestData.TransactionId).Items;
+ var itemPurchased = traderAssorts.FirstOrDefault(item => item.Id == buyRequestData.ItemId);
+ // Ensure purchase does not exceed trader item limit
+ var assortHasBuyRestrictions = _itemHelper.HasBuyRestrictions(itemPurchased);
+ if (assortHasBuyRestrictions)
+ // Will throw error if check fails
+ {
+ CheckPurchaseIsWithinTraderItemLimit(
+ sessionID,
+ pmcData,
+ buyRequestData.TransactionId,
+ itemPurchased,
+ buyRequestData.ItemId,
+ buyCount
+ );
+ }
+
+ // Check if trader has enough stock
+ if (itemPurchased.Upd.StackObjectsCount < buyCount)
+ {
+ throw new Exception(
+ $"Unable to purchase {buyCount} items, this would exceed the remaining stock left {itemPurchased.Upd.StackObjectsCount} from the traders assort: {buyRequestData.TransactionId} this refresh"
+ );
+ }
+
+ // Decrement trader item count
+ itemPurchased.Upd.StackObjectsCount -= buyCount;
+
+ if (assortHasBuyRestrictions)
+ {
+ var itemPurchaseDat = new PurchaseDetails
+ {
+ Items =
+ [
+ new PurchaseItems
+ {
+ ItemId = buyRequestData.ItemId,
+ Count = buyCount
+ }
+ ],
+ TraderId = buyRequestData.TransactionId
+ };
+
+ _traderHelper.AddTraderPurchasesToPlayerProfile(sessionID, itemPurchaseDat, itemPurchased);
+ }
+ };
+
+ // Get all trader assort items
+ var traderItems = _traderAssortHelper.GetAssort(sessionID, buyRequestData.TransactionId).Items;
+
+ // Get item + children for purchase
+ var relevantItems = _itemHelper.FindAndReturnChildrenAsItems(traderItems, buyRequestData.ItemId);
+ if (relevantItems.Count == 0)
+ {
+ _logger.Error(
+ $"Purchased trader: {buyRequestData.TransactionId} offer: {buyRequestData.ItemId} has no items");
+ }
+
+ offerItems.AddRange(relevantItems);
+ }
+
+ // Get item details from db
+ var itemDbDetails = _itemHelper.GetItem(offerItems.FirstOrDefault().Template).Value;
+ var itemMaxStackSize = itemDbDetails.Properties.StackMaxSize;
+ var itemsToSendTotalCount = buyRequestData.Count;
+ var itemsToSendRemaining = itemsToSendTotalCount;
+
+ // Construct array of items to send to player
+ List
> itemsToSendToPlayer = [];
+ while (itemsToSendRemaining > 0)
+ {
+ var offerClone = _cloner.Clone(offerItems);
+ // Handle stackable items that have a max stack size limit
+ var itemCountToSend = Math.Min(itemMaxStackSize ?? 0, itemsToSendRemaining ?? 0);
+ offerClone.FirstOrDefault().Upd.StackObjectsCount = itemCountToSend;
+
+ // Prevent any collisions
+ _itemHelper.RemapRootItemId(offerClone);
+ if (offerClone.Count > 1)
+ {
+ _itemHelper.ReparentItemAndChildren(offerClone.FirstOrDefault(), offerClone);
+ }
+
+ itemsToSendToPlayer.Add(offerClone);
+
+ // Remove amount of items added to player stash
+ itemsToSendRemaining -= itemCountToSend;
+ }
+
+ // Construct request
+ var request = new AddItemsDirectRequest
+ {
+ ItemsWithModsToAdd = itemsToSendToPlayer,
+ FoundInRaid = foundInRaid,
+ Callback = buyCallback,
+ UseSortingTable = false
+ };
+
+ // Add items + their children to stash
+ _inventoryHelper.AddItemsToStash(sessionID, request, pmcData, output);
+ if (output.Warnings?.Count > 0)
+ {
return;
}
- offerItems = _itemHelper.FindAndReturnChildrenAsItems(fenceItems, buyRequestData.ItemId);
- }
- else
- {
- buyCallback = buyCount =>
+ /// Pay for purchase
+ _paymentService.PayMoney(pmcData, buyRequestData, sessionID, output);
+ if (output.Warnings?.Count > 0)
{
- // Update assort/flea item values
- var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(buyRequestData.TransactionId).Items;
- var itemPurchased = traderAssorts.FirstOrDefault(item => item.Id == buyRequestData.ItemId);
-
- // Ensure purchase does not exceed trader item limit
- var assortHasBuyRestrictions = _itemHelper.HasBuyRestrictions(itemPurchased);
- if (assortHasBuyRestrictions)
- // Will throw error if check fails
- {
- CheckPurchaseIsWithinTraderItemLimit(
- sessionID,
- pmcData,
- buyRequestData.TransactionId,
- itemPurchased,
- buyRequestData.ItemId,
- buyCount
- );
- }
-
- // Check if trader has enough stock
- if (itemPurchased.Upd.StackObjectsCount < buyCount)
- {
- throw new Exception(
- $"Unable to purchase {buyCount} items, this would exceed the remaining stock left {itemPurchased.Upd.StackObjectsCount} from the traders assort: {buyRequestData.TransactionId} this refresh"
- );
- }
-
- // Decrement trader item count
- itemPurchased.Upd.StackObjectsCount -= buyCount;
-
- if (assortHasBuyRestrictions)
- {
- var itemPurchaseDat = new PurchaseDetails
- {
- Items =
- [
- new PurchaseItems
- {
- ItemId = buyRequestData.ItemId,
- Count = buyCount
- }
- ],
- TraderId = buyRequestData.TransactionId
- };
-
- _traderHelper.AddTraderPurchasesToPlayerProfile(sessionID, itemPurchaseDat, itemPurchased);
- }
- };
-
- // Get all trader assort items
- var traderItems = _traderAssortHelper.GetAssort(sessionID, buyRequestData.TransactionId).Items;
-
- // Get item + children for purchase
- var relevantItems = _itemHelper.FindAndReturnChildrenAsItems(traderItems, buyRequestData.ItemId);
- if (relevantItems.Count == 0)
- {
- _logger.Error($"Purchased trader: {buyRequestData.TransactionId} offer: {buyRequestData.ItemId} has no items");
+ var errorMessage = $"Transaction failed: {output.Warnings.FirstOrDefault().ErrorMessage}";
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.UnknownTradingError);
}
-
- offerItems.AddRange(relevantItems);
- }
-
- // Get item details from db
- var itemDbDetails = _itemHelper.GetItem(offerItems.FirstOrDefault().Template).Value;
- var itemMaxStackSize = itemDbDetails.Properties.StackMaxSize;
- var itemsToSendTotalCount = buyRequestData.Count;
- var itemsToSendRemaining = itemsToSendTotalCount;
-
- // Construct array of items to send to player
- List> itemsToSendToPlayer = [];
- while (itemsToSendRemaining > 0)
- {
- var offerClone = _cloner.Clone(offerItems);
- // Handle stackable items that have a max stack size limit
- var itemCountToSend = Math.Min(itemMaxStackSize ?? 0, itemsToSendRemaining ?? 0);
- offerClone.FirstOrDefault().Upd.StackObjectsCount = itemCountToSend;
-
- // Prevent any collisions
- _itemHelper.RemapRootItemId(offerClone);
- if (offerClone.Count > 1)
- {
- _itemHelper.ReparentItemAndChildren(offerClone.FirstOrDefault(), offerClone);
- }
-
- itemsToSendToPlayer.Add(offerClone);
-
- // Remove amount of items added to player stash
- itemsToSendRemaining -= itemCountToSend;
- }
-
- // Construct request
- var request = new AddItemsDirectRequest
- {
- ItemsWithModsToAdd = itemsToSendToPlayer,
- FoundInRaid = foundInRaid,
- Callback = buyCallback,
- UseSortingTable = false
- };
-
- // Add items + their children to stash
- _inventoryHelper.AddItemsToStash(sessionID, request, pmcData, output);
- if (output.Warnings?.Count > 0)
- {
- return;
- }
-
- /// Pay for purchase
- _paymentService.PayMoney(pmcData, buyRequestData, sessionID, output);
- if (output.Warnings?.Count > 0)
- {
- var errorMessage = $"Transaction failed: {output.Warnings.FirstOrDefault().ErrorMessage}";
- _httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.UnknownTradingError);
}
}