Refactored ragfair code for improved readability

This commit is contained in:
Chomp
2025-06-16 13:00:06 +01:00
parent 40ace1712d
commit e869f6ebef
5 changed files with 130 additions and 131 deletions
@@ -1088,7 +1088,7 @@ public class RagfairController
var output = _eventOutputHolder.GetOutput(sessionId);
var pmcData = _profileHelper.GetPmcProfile(sessionId);
var playerProfileOffers = pmcData.RagfairInfo.Offers;
var playerProfileOffers = pmcData?.RagfairInfo?.Offers;
if (playerProfileOffers is null)
{
_logger.Warning(
@@ -1133,6 +1133,8 @@ public class RagfairController
playerOffer.EndTime = (long?) Math.Round((double) newEndTime);
}
_logger.Debug($"Flagged player offer: {offerId} for expiry in: {TimeSpan.FromTicks(playerOffer.EndTime.Value).ToString()}");
return output;
}
@@ -599,7 +599,7 @@ public class RagfairOfferHelper(
*/
public bool ProcessOffersOnProfile(string sessionId)
{
var timestamp = _timeUtil.GetTimeStamp();
var currentTimestamp = _timeUtil.GetTimeStamp();
var profileOffers = GetProfileOffers(sessionId);
// No offers, don't do anything
@@ -612,56 +612,59 @@ public class RagfairOfferHelper(
for (var index = profileOffers.Count - 1; index >= 0; index--)
{
var offer = profileOffers[index];
var firstSellResult = offer.SellResults?.FirstOrDefault();
if (offer.SellResults?.Count > 0 && timestamp >= offer.SellResults[0].SellTime)
if (offer.SellResults is null || offer.SellResults.Count == 0 || currentTimestamp < offer.SellResults.FirstOrDefault()?.SellTime)
{
// Checks first item, first is spliced out of array after being processed
// Item sold
var totalItemsCount = 1d;
var boughtAmount = 1;
if (!offer.SellInOnePiece.GetValueOrDefault(false))
{
// offer.items.reduce((sum, item) => sum + item.upd?.StackObjectsCount ?? 0, 0);
totalItemsCount = GetTotalStackCountSize([offer.Items]);
boughtAmount = firstSellResult.Amount.Value;
}
var ratingToAdd = offer.SummaryCost / totalItemsCount * boughtAmount;
IncreaseProfileRagfairRating(_profileHelper.GetFullProfile(sessionId), ratingToAdd.Value);
offer.SellResults.Remove(firstSellResult); // Remove the sell result object now it has been processed
// Can delete offer object, must run last
CompleteOffer(sessionId, offer, boughtAmount);
// Not sold / too early to check
continue;
}
var firstSellResult = offer.SellResults?.FirstOrDefault();
if (firstSellResult is null)
{
continue;
}
// Checks first item, first is spliced out of array after being processed
// Item sold
var totalItemsCount = 1d;
var boughtAmount = 1;
// Does item need to be re-stacked
if (!offer.SellInOnePiece.GetValueOrDefault(false))
{
// offer.items.reduce((sum, item) => sum + item.upd?.StackObjectsCount ?? 0, 0);
totalItemsCount = GetTotalStackCountSize([offer.Items]);
boughtAmount = firstSellResult.Amount ?? boughtAmount;
}
var ratingToAdd = offer.SummaryCost / totalItemsCount * boughtAmount;
IncreaseProfileRagfairRating(_profileHelper.GetFullProfile(sessionId), ratingToAdd.Value);
// Remove the sell result object now it has been processed
offer.SellResults.Remove(firstSellResult);
// Can delete offer object, must run last
CompleteOffer(sessionId, offer, boughtAmount);
}
return true;
}
/**
* Count up all rootitem StackObjectsCount properties of an array of items
* @param itemsInInventoryToList items to sum up
* @returns Total stack count
*/
public double GetTotalStackCountSize(List<List<Item>> itemsInInventoryToList)
/// <summary>
/// Count up all root item StackObjectsCount properties of an array of items
/// </summary>
/// <param name="itemsInInventoryToSumStackCount">items to sum up</param>
/// <returns>Total stack count</returns>
public double GetTotalStackCountSize(List<List<Item>> itemsInInventoryToSumStackCount)
{
var total = 0d;
foreach (var itemAndChildren in itemsInInventoryToList)
// Only count the root items stack count in total
{
total += itemAndChildren[0]?.Upd?.StackObjectsCount.GetValueOrDefault(1) ?? 1;
}
return total;
return itemsInInventoryToSumStackCount.Sum(itemAndChildren => itemAndChildren.FirstOrDefault()?.Upd?.StackObjectsCount.GetValueOrDefault(1) ?? 1);
}
/**
* Add amount to players ragfair rating
* @param sessionId Profile to update
* @param amountToIncrementBy Raw amount to add to players ragfair rating (excluding the reputation gain multiplier)
*/
/// <summary>
/// Add amount to players ragfair rating
/// </summary>
/// <param name="profile">Profile to update</param>
/// <param name="amountToIncrementBy">Raw amount to add to players ragfair rating (excluding the reputation gain multiplier)</param>
public void IncreaseProfileRagfairRating(SptProfile profile, double? amountToIncrementBy)
{
var ragfairGlobalsConfig = _databaseService.GetGlobals().Configuration.RagFair;
@@ -680,11 +683,11 @@ public class RagfairOfferHelper(
amountToIncrementBy;
}
/**
* Return all offers a player has listed on a desired profile
* @param sessionId Session id
* @returns List of ragfair offers
*/
/// <summary>
/// Return all offers a player has listed on a desired profile
/// </summary>
/// <param name="sessionId">Session/Player id</param>
/// <returns>List of ragfair offers</returns>
protected List<RagfairOffer> GetProfileOffers(string sessionId)
{
var profile = _profileHelper.GetPmcProfile(sessionId);
@@ -721,21 +724,15 @@ public class RagfairOfferHelper(
_ragfairOfferService.RemoveOfferById(offerId);
}
/**
* Complete the selling of players' offer
* @param sessionID Session id
* @param offer Sold offer details
* @param boughtAmount Amount item was purchased for
* @returns ItemEventRouterResponse
*/
/// <summary>
/// Complete the selling of players' offer
/// </summary>
/// <param name="offerOwnerSessionId">Session/Player id</param>
/// <param name="offer">Sold offer details</param>
/// <param name="boughtAmount">Amount item was purchased for</param>
/// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse CompleteOffer(string offerOwnerSessionId, RagfairOffer offer, int boughtAmount)
{
var rootItem = offer.Items.FirstOrDefault();
var itemTpl = rootItem.Template;
var paymentItemsToSendToPlayer = new List<Item>();
var offerStackCount = rootItem.Upd.StackObjectsCount;
var sellerProfile = _profileHelper.GetPmcProfile(offerOwnerSessionId);
// Pack or ALL items of a multi-offer were bought - remove entire offer
if (offer.SellInOnePiece.GetValueOrDefault(false) || boughtAmount == offer.Quantity)
{
@@ -748,6 +745,11 @@ public class RagfairOfferHelper(
}
// Assemble payment to send to seller now offer was purchased
var sellerProfile = _profileHelper.GetPmcProfile(offerOwnerSessionId);
var rootItem = offer.Items.FirstOrDefault();
var itemTpl = rootItem.Template;
var offerStackCount = rootItem.Upd.StackObjectsCount;
var paymentItemsToSendToPlayer = new List<Item>();
foreach (var requirement in offer.Requirements)
{
// Create an item template item
@@ -792,14 +794,14 @@ public class RagfairOfferHelper(
HandbookId = itemTpl
};
var storagetime = _timeUtil.GetHoursAsSeconds((int) _questHelper.GetMailItemRedeemTimeHoursForProfile(sellerProfile));
var storageTimeSeconds = _timeUtil.GetHoursAsSeconds((int) _questHelper.GetMailItemRedeemTimeHoursForProfile(sellerProfile));
_mailSendService.SendDirectNpcMessageToPlayer(
offerOwnerSessionId,
Traders.RAGMAN,
MessageType.FleamarketMessage,
GetLocalisedOfferSoldMessage(itemTpl, boughtAmount),
paymentItemsToSendToPlayer,
storagetime,
storageTimeSeconds,
null,
ragfairDetails
);
@@ -22,7 +22,7 @@ public class RagfairServer(
ConfigServer _configServer
)
{
protected RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
protected readonly RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
public void Load()
{
@@ -33,7 +33,7 @@ public class RagfairServer(
public void Update()
{
// Generate trader offers
// Generate/refresh trader offers
var traders = GetUpdateableTraders();
foreach (var traderId in traders)
{
@@ -45,6 +45,7 @@ public class RagfairServer(
if (_ragfairOfferService.TraderOffersNeedRefreshing(traderId))
{
// Trader has passed its offer cycle time, update stock and set offer times
_ragfairOfferGenerator.GenerateFleaOffersForTrader(traderId);
}
}
@@ -8,6 +8,7 @@ using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Services;
@@ -92,7 +93,7 @@ public class RagfairOfferService(
if (offer.Quantity <= 0)
{
// Offer is gone and now 'stale', need to be flagged as stale or removed if PMC offer
ProcessStaleOffer(offerId);
ProcessStaleOffer(offer.Id);
}
}
@@ -162,34 +163,35 @@ public class RagfairOfferService(
ProcessStaleOffer(offerId);
}
// Clear out expired offer ids now we've regenerated them
// Clear out expired offer ids now we've processed them above
ragfairOfferHolder.ResetExpiredOfferIds();
}
/// <summary>
/// Remove stale offer from flea
/// Remove stale offer from flea
/// Send offer items back when its player offer
/// Skip trader offers - we want those to remain in 'expired' state until trader refresh
/// </summary>
/// <param name="staleOfferId"> Stale offer id to process </param>
protected void ProcessStaleOffer(string staleOfferId)
{
var staleOffer = ragfairOfferHolder.GetOfferById(staleOfferId);
var isTrader = ragfairServerHelper.IsTrader(staleOffer.User.Id);
var isPlayer = profileHelper.IsPlayer(staleOffer.User.Id.RegexReplace("^pmc", ""));
// Skip trader offers, managed by RagfairServer.Update() + should remain on flea as 'expired'
if (isTrader)
if (ragfairServerHelper.IsTrader(staleOffer.User.Id))
{
return;
}
// Handle dynamic offer from PMCs
var isPlayer = profileHelper.IsPlayer(staleOffer.User.Id.RegexReplace("^pmc", ""));
if (!isPlayer)
{
// Not trader/player offer
ragfairOfferHolder.FlagOfferAsExpired(staleOfferId);
ragfairOfferHolder.FlagOfferAsExpired(staleOffer.Id);
}
// Handle player offer - item(s) need returning/XP/rep adjusting. Checking if offer has actually expired or not.
// Handle player offer: item(s) need returning/XP/rep adjusting. Checking if offer has actually expired or not.
if (isPlayer && staleOffer.EndTime <= timeUtil.GetTimeStamp())
{
ReturnUnsoldPlayerOffer(staleOffer);
@@ -198,7 +200,7 @@ public class RagfairOfferService(
}
// Remove expired offer from global flea pool
RemoveOfferById(staleOfferId);
RemoveOfferById(staleOffer.Id);
}
/// <summary>
@@ -268,6 +270,11 @@ public class RagfairOfferService(
ragfairServerHelper.ReturnItems(offerCreatorProfile.SessionId, unstackedItems);
offerCreatorProfile.RagfairInfo.Offers.Splice(indexOfOfferInProfile, 1);
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Returned offer: {{playerOffer.Id}} items to player");
}
}
/// <summary>
@@ -318,7 +325,7 @@ public class RagfairOfferService(
// Ensure items IDs are unique to prevent collisions when added to player inventory
var reparentedItemAndChildren = itemHelper.ReparentItemAndChildren(
itemAndChildrenClone[0],
itemAndChildrenClone.FirstOrDefault(),
itemAndChildrenClone
);
itemHelper.RemapRootItemId(reparentedItemAndChildren);
@@ -22,9 +22,9 @@ public class RagfairOfferHolder(
protected readonly Lock _ragfairOperationLock = new();
protected readonly HashSet<string> _expiredOfferIds = [];
protected ConcurrentDictionary<string, RagfairOffer> _offersById = new();
protected ConcurrentDictionary<string, HashSet<string>> _offersByTemplate = new(); // key = tplId, value = list of offerIds
protected ConcurrentDictionary<string, HashSet<string>> _offersByTrader = new(); // key = traderId, value = list of offerIds
protected readonly ConcurrentDictionary<string, RagfairOffer> _offersById = new();
protected readonly ConcurrentDictionary<string, HashSet<string>> _offersByTemplate = new(); // key = tplId, value = list of offerIds
protected readonly ConcurrentDictionary<string, HashSet<string>> _offersByTrader = new(); // key = traderId, value = list of offerIds
/// <summary>
/// Get a ragfair offer by its id
@@ -71,11 +71,11 @@ public class RagfairOfferHolder(
/// </summary>
/// <param name="traderId">Id of trader to get offers for</param>
/// <returns>RagfairOffer list</returns>
public List<RagfairOffer>? GetOffersByTrader(string traderId)
public List<RagfairOffer> GetOffersByTrader(string traderId)
{
if (!_offersByTrader.TryGetValue(traderId, out var offerIds))
{
return null;
return [];
}
return offerIds
@@ -118,7 +118,6 @@ public class RagfairOfferHolder(
{
lock (_ragfairOperationLock)
{
var sellerId = offer.User.Id;
// Keep generating IDs until we get a unique one
while (_offersById.ContainsKey(offer.Id))
{
@@ -126,17 +125,19 @@ public class RagfairOfferHolder(
}
var itemTpl = offer.Items?.FirstOrDefault()?.Template;
// If it is an NPC PMC offer AND we have already reached the maximum amount of possible offers
// for this template, just don't add in more
var sellerId = offer.User.Id;
var sellerIsTrader = _ragfairServerHelper.IsTrader(sellerId);
var itemSoldDb = _itemHelper.GetItem(itemTpl);
var itemSoldTemplate = _itemHelper.GetItem(itemTpl);
if (
!string.IsNullOrEmpty(itemTpl)
&& !(sellerIsTrader || _profileHelper.IsPlayer(sellerId))
&& _offersByTemplate.TryGetValue(itemTpl, out var offers)
&& offers?.Count >= _ragfairServerHelper.GetOfferCountByBaseType(itemSoldDb.Value.Parent)
&& offers?.Count >= _ragfairServerHelper.GetOfferCountByBaseType(itemSoldTemplate.Value.Parent)
)
{
// If it is an NPC PMC offer AND we have already reached the maximum amount of possible offers
// for this template, just don't add in more
return;
}
@@ -164,32 +165,32 @@ public class RagfairOfferHolder(
if (!_offersById.TryGetValue(offerId, out var offer))
{
_logger.Warning(_localisationService.GetText("ragfair-unable_to_remove_offer_doesnt_exist", offerId));
return;
}
if (!_offersById.TryRemove(offer.Id, out _))
{
_logger.Warning($"Unable to remove offer: {offer.Id}");
_logger.Warning($"Unable to remove offer by id: {offer.Id} not found");
}
if (checkTraderOffers && _offersByTrader.ContainsKey(offer.User.Id))
if (checkTraderOffers && _offersByTrader.TryGetValue(offer.User.Id, out var traderOfferIds))
{
_offersByTrader[offer.User.Id].Remove(offer.Id);
// This was causing a memory leak, we need to make sure that we remove
// the user ID from the cached offers after they dont have anything else
// on the flea placed. We regenerate the ID for the NPC users, making it
// continuously grow otherwise
if (_offersByTrader[offer.User.Id].Count == 0)
traderOfferIds.Remove(offer.Id);
if (traderOfferIds.Count == 0)
{
// Potential memory leak
// Users with no offers were never cleaned up
if (!_offersByTrader.TryRemove(offer.User.Id, out _))
{
_logger.Warning($"Unable to remove Trader offer: {offer.Id}");
_logger.Warning($"Unable to remove Trader offer: {offer.Id} not found");
}
}
}
var firstItem = offer.Items.FirstOrDefault();
if (_offersByTemplate.TryGetValue(firstItem.Template, out var offers))
var rootItem = offer.Items.FirstOrDefault();
if (_offersByTemplate.TryGetValue(rootItem.Template, out var offers))
{
offers.Remove(offer.Id);
}
@@ -201,19 +202,22 @@ public class RagfairOfferHolder(
/// <param name="traderId">Trader id to remove offers from</param>
public void RemoveAllOffersByTrader(string traderId)
{
if (_offersByTrader.TryGetValue(traderId, out var offerIdsToRemove))
if (!_offersByTrader.TryGetValue(traderId, out var offerIdsToRemove))
{
foreach (var offerId in offerIdsToRemove)
{
if (!_offersById.TryRemove(offerId, out _))
{
_logger.Warning($"Unable to remove offer: {offerId}");
}
}
// Clear out linking table
_offersByTrader[traderId].Clear();
// No trader, nothing to do
return;
}
foreach (var offerId in offerIdsToRemove)
{
if (!_offersById.TryRemove(offerId, out _))
{
_logger.Warning($"Unable to remove offer: {offerId}");
}
}
// Clear out linking table
_offersByTrader[traderId].Clear();
}
/// <summary>
@@ -223,9 +227,10 @@ public class RagfairOfferHolder(
/// <param name="offerId">Offer to store against tpl</param>
protected void AddOfferByTemplates(string template, string offerId)
{
if (_offersByTemplate.ContainsKey(template))
if (_offersByTemplate.TryGetValue(template, out var offerIds))
{
_offersByTemplate[template].Add(offerId);
offerIds.Add(offerId);
return;
}
@@ -242,9 +247,10 @@ public class RagfairOfferHolder(
/// <param name="offerId">Offer to store against</param>
protected void AddOfferByTrader(string trader, string offerId)
{
if (_offersByTrader.ContainsKey(trader))
if (_offersByTrader.TryGetValue(trader, out var traderOfferIds))
{
_offersByTrader[trader].Add(offerId);
traderOfferIds.Add(offerId);
return;
}
@@ -260,18 +266,13 @@ public class RagfairOfferHolder(
/// <param name="offer">Offer to check</param>
/// <param name="time">Time to check offer against</param>
/// <returns>True - offer is stale</returns>
protected bool IsStale(RagfairOffer? offer, long time)
protected bool IsStale(RagfairOffer offer, long time)
{
if (offer is null)
{
return false;
}
return offer.EndTime < time || (offer.Quantity ?? 0) < 1;
}
/// <summary>
/// Add a stale offers id to collection for later use
/// Add a stale offers id to _expiredOfferIds collection for later processing
/// </summary>
/// <param name="staleOfferId">Id of offer to add to stale collection</param>
public void FlagOfferAsExpired(string staleOfferId)
@@ -366,18 +367,4 @@ public class RagfairOfferHolder(
}
}
}
/// <summary>
/// Remove all offers flagged as stale/expired
/// </summary>
public void RemoveExpiredOffers()
{
lock (_expiredOfferIdsLock)
{
foreach (var expiredOfferId in _expiredOfferIds)
{
RemoveOffer(expiredOfferId, false);
}
}
}
}