partial implementation of RagfairOfferHelper

This commit is contained in:
Chomp
2025-01-25 13:17:55 +00:00
parent fe8e820e93
commit c6e774a063
2 changed files with 411 additions and 41 deletions
+410 -40
View File
@@ -1,4 +1,3 @@
using SptCommon.Annotations;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.ItemEvent;
@@ -7,10 +6,11 @@ using Core.Models.Eft.Ragfair;
using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Models.Utils;
using Core.Utils;
using SptCommon.Extensions;
using Core.Models.Spt.Services;
using Core.Servers;
using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
using SptCommon.Extensions;
namespace Core.Helpers;
@@ -18,11 +18,20 @@ namespace Core.Helpers;
public class RagfairOfferHelper(
ISptLogger<RagfairOfferHelper> _logger,
TimeUtil _timeUtil,
RagfairSortHelper _ragfairSortHelper,
PresetHelper _presetHelper,
PaymentHelper _paymentHelper,
TraderHelper _traderHelper,
ItemHelper _itemHelper,
DatabaseService _databaseService,
ProfileHelper _profileHelper)
RagfairOfferService _ragfairOfferService,
ProfileHelper _profileHelper,
ConfigServer _configServer)
{
protected RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
/// <summary>
/// Passthrough to ragfairOfferService.getOffers(), get flea offers a player should see
/// Passthrough to ragfairOfferService.getOffers(), get flea offers a player should see
/// </summary>
/// <param name="searchRequest">Data from client</param>
/// <param name="itemsToAdd">ragfairHelper.filterCategories()</param>
@@ -39,7 +48,7 @@ public class RagfairOfferHelper(
}
/// <summary>
/// Disable offer if item is flagged by tiered flea config
/// Disable offer if item is flagged by tiered flea config
/// </summary>
/// <param name="tieredFlea">Tiered flea settings from ragfair config</param>
/// <param name="offer">Ragfair offer to check</param>
@@ -48,14 +57,15 @@ public class RagfairOfferHelper(
protected void CheckAndLockOfferFromPlayerTieredFlea(
TieredFlea tieredFlea,
RagfairOffer offer,
string[] tieredFleaLimitTypes,
List<string> tieredFleaLimitTypes,
int playerLevel)
{
throw new NotImplementedException();
}
/// <summary>
/// Get matching offers that require the desired item and filter out offers from non traders if player is below ragfair unlock level
/// Get matching offers that require the desired item and filter out offers from non traders if player is below ragfair
/// unlock level
/// </summary>
/// <param name="searchRequest">Search request from client</param>
/// <param name="pmcData">Player profile</param>
@@ -66,7 +76,7 @@ public class RagfairOfferHelper(
}
/// <summary>
/// Get offers from flea/traders specifically when building weapon preset
/// Get offers from flea/traders specifically when building weapon preset
/// </summary>
/// <param name="searchRequest">Search request data</param>
/// <param name="itemsToAdd">string array of item tpls to search for</param>
@@ -79,64 +89,381 @@ public class RagfairOfferHelper(
Dictionary<string, TraderAssort> traderAssorts,
PmcData pmcData)
{
throw new NotImplementedException();
var offersMap = new Dictionary<string, List<RagfairOffer>>();
var offersToReturn = new List<RagfairOffer>();
var playerIsFleaBanned = _profileHelper.PlayerIsFleaBanned(pmcData);
var tieredFlea = _ragfairConfig.TieredFlea;
var tieredFleaLimitTypes = tieredFlea.UnlocksType;
foreach (var desiredItemTpl in searchRequest.BuildItems)
{
var matchingOffers = _ragfairOfferService.GetOffersOfType(desiredItemTpl.Key);
if (matchingOffers is null)
{
// No offers found for this item, skip
continue;
}
foreach (var offer in matchingOffers)
{
// Don't show pack offers
if (offer.SellInOnePiece.GetValueOrDefault(false))
{
continue;
}
if (!PassesSearchFilterCriteria(searchRequest, offer, pmcData))
{
continue;
}
if (
!IsDisplayableOffer(
searchRequest,
itemsToAdd,
traderAssorts,
offer,
pmcData,
playerIsFleaBanned
)
)
{
continue;
}
if (OfferIsFromTrader(offer))
{
if (TraderBuyRestrictionReached(offer))
{
continue;
}
if (TraderOutOfStock(offer))
{
continue;
}
if (TraderOfferItemQuestLocked(offer, traderAssorts))
{
continue;
}
if (TraderOfferLockedBehindLoyaltyLevel(offer, pmcData))
{
continue;
}
}
// Tiered flea and not trader offer
if (tieredFlea.Enabled && !OfferIsFromTrader(offer))
{
CheckAndLockOfferFromPlayerTieredFlea(
tieredFlea,
offer,
tieredFleaLimitTypes.Keys.ToList(),
pmcData.Info.Level.Value
);
// Do not add offer to build if user does not have access to it
if (offer.Locked.GetValueOrDefault(false))
{
continue;
}
}
var key = offer.Items[0].Template;
if (!offersMap.ContainsKey(key))
{
offersMap.Add(key, []);
}
offersMap[key].Add(offer);
}
}
// Get best offer for each item to show on screen
var offersToSort = new List<RagfairOffer>();
foreach (var possibleOffers in offersMap.Values)
{
// prepare temp list for offers
offersToSort.Clear();
// Remove offers with locked = true (quest locked) when > 1 possible offers
// single trader item = shows greyed out
// multiple offers for item = is greyed out
if (possibleOffers.Count > 1)
{
var lockedOffers = GetLoyaltyLockedOffers(possibleOffers, pmcData);
// Exclude locked offers + above loyalty locked offers if at least 1 was found
offersToSort = possibleOffers.Where(
offer => !(offer.Locked.GetValueOrDefault(false) || lockedOffers.Contains(offer.Id))
)
.ToList();
// Exclude trader offers over their buy restriction limit
offersToSort = GetOffersInsideBuyRestrictionLimits(possibleOffers);
}
// Sort offers by price and pick the best
var offer = _ragfairSortHelper.SortOffers(offersToSort, RagfairSort.PRICE)[0];
offersToReturn.Add(offer);
}
return offersToReturn;
}
/**
* Should a ragfair offer be visible to the player
* @param searchRequest Search request
* @param itemsToAdd ?
* @param traderAssorts Trader assort items - used for filtering out locked trader items
* @param offer The flea offer
* @param pmcProfile Player profile
* @returns True = should be shown to player
*/
private bool IsDisplayableOffer(SearchRequestData searchRequest, List<string> itemsToAdd,
Dictionary<string, TraderAssort> traderAssorts, RagfairOffer offer, PmcData pmcProfile,
bool playerIsFleaBanned = false)
{
var offerRootItem = offer.Items[0];
/** Currency offer is sold for */
var moneyTypeTpl = offer.Requirements[0].Template;
var isTraderOffer = _databaseService.GetTraders().ContainsKey(offer.User.Id);
if (!isTraderOffer && playerIsFleaBanned)
{
return false;
}
// Offer root items tpl not in searched for array
if (!itemsToAdd.Contains(offerRootItem.Template))
{
// skip items we shouldn't include
return false;
}
// Performing a required search and offer doesn't have requirement for item
if (
searchRequest.NeededSearchId is not null &&
!offer.Requirements.Any(requirement => requirement.Template == searchRequest.NeededSearchId)
)
{
return false;
}
// Weapon/equipment search + offer is preset
if (
searchRequest.BuildItems.Count == 0 && // Prevent equipment loadout searches filtering out presets
searchRequest.BuildCount is not null &&
_presetHelper.HasPreset(offerRootItem.Template))
{
return false;
}
// commented out as required search "which is for checking offers that are barters"
// has info.removeBartering as true, this if statement removed barter items.
if (searchRequest.RemoveBartering.GetValueOrDefault(false) && !_paymentHelper.IsMoneyTpl(moneyTypeTpl))
{
// Don't include barter offers
return false;
}
if (offer.RequirementsCost is null)
{
// Don't include offers with undefined or NaN in it
return false;
}
// Handle trader items to remove items that are not available to the user right now
// e.g. required search for "lamp" shows 4 items, 3 of which are not available to a new player
// filter those out
if (isTraderOffer)
{
if (!traderAssorts.ContainsKey(offer.User.Id))
{
// trader not visible on flea market
return false;
}
if (
!traderAssorts[offer.User.Id].Items.Any(item => { return item.Id == offer.Root; })
)
{
// skip (quest) locked items
return false;
}
}
return true;
}
/// <summary>
/// Get offers that have not exceeded buy limits
/// Get offers that have not exceeded buy limits
/// </summary>
/// <param name="possibleOffers">offers to process</param>
/// <returns>Offers</returns>
protected List<RagfairOffer> GetOffersInsideBuyRestrictionLimits(List<RagfairOffer> possibleOffers)
{
throw new NotImplementedException();
// Check offer has buy limit + is from trader + current buy count is at or over max
return possibleOffers.Where(
offer =>
{
if (
offer.BuyRestrictionMax is null &&
OfferIsFromTrader(offer) &&
offer.BuyRestrictionCurrent >= offer.BuyRestrictionMax
)
{
if (offer.BuyRestrictionCurrent >= offer.BuyRestrictionMax)
{
return false;
}
}
// Doesnt have buy limits, retrun offer
return true;
}
)
.ToList();
}
/// <summary>
/// Check if offer is from trader standing the player does not have
/// Check if offer is from trader standing the player does not have
/// </summary>
/// <param name="offer">Offer to check</param>
/// <param name="pmcProfile">Player profile</param>
/// <returns>True if item is locked, false if item is purchaseable</returns>
protected bool TraderOfferLockedBehindLoyaltyLevel(RagfairOffer offer, PmcData pmcProfile)
{
throw new NotImplementedException();
if (!pmcProfile.TradersInfo.TryGetValue(offer.User.Id, out var userTraderSettings))
{
_logger.Warning(
$"Trader: {offer.User.Id} not found in profile, assuming offer is not locked being loyalty level"
);
return false;
}
return userTraderSettings.LoyaltyLevel < offer.LoyaltyLevel;
}
/// <summary>
/// Check if offer item is quest locked for current player by looking at sptQuestLocked property in traders barter_scheme
/// Check if offer item is quest locked for current player by looking at sptQuestLocked property in traders
/// barter_scheme
/// </summary>
/// <param name="offer">Offer to check is quest locked</param>
/// <param name="traderAssorts">all trader assorts for player</param>
/// <returns>true if quest locked</returns>
public bool TraderOfferItemQuestLocked(RagfairOffer offer, Dictionary<string, TraderAssort> traderAssorts)
{
throw new NotImplementedException();
var itemIds = offer.Items.Select(x => x.Id).ToList();
//foreach (var item in offer.Items)
//{
// traderAssorts.TryGetValue(offer.User.Id, out var assorts);
// foreach (var barterKvP in assorts.BarterScheme.Where(x => itemIds.Contains(x.Key)))
// {
// foreach (var subBarter in barterKvP.Value)
// {
// if (subBarter.Any(subBarter => subBarter.SptQuestLocked.GetValueOrDefault(false)))
// {
// return true;
// }
// }
// }
//}
foreach (var item in offer.Items)
{
traderAssorts.TryGetValue(offer.User.Id, out var assorts);
if (assorts.BarterScheme
.Where(x => itemIds.Contains(x.Key))
.Any(
barterKvP => barterKvP.Value
.Any(
subBarter => subBarter
.Any(subBarter => subBarter.SptQuestLocked.GetValueOrDefault(false))
)
))
{
return true;
}
}
// Fallback, nothing found
return false;
}
/// <summary>
/// Has trader offer ran out of stock to sell to player
/// Has trader offer ran out of stock to sell to player
/// </summary>
/// <param name="offer">Offer to check stock of</param>
/// <returns>true if out of stock</returns>
protected bool TraderOutOfStock(RagfairOffer offer)
{
throw new NotImplementedException();
if (offer?.Items?.Count == 0)
{
return true;
}
return offer.Items[0]?.Upd?.StackObjectsCount == 0;
}
/// <summary>
/// Check if trader offers' BuyRestrictionMax value has been reached
/// Check if trader offers' BuyRestrictionMax value has been reached
/// </summary>
/// <param name="offer">Offer to check restriction properties of</param>
/// <returns>true if restriction reached, false if no restrictions/not reached</returns>
protected bool TraderBuyRestrictionReached(RagfairOffer offer)
{
throw new NotImplementedException();
var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(offer.User.Id).Items;
// Find item being purchased from traders assorts
var assortData = traderAssorts.FirstOrDefault(item => item.Id == offer.Items[0].Id);
if (assortData is null)
{
// No trader assort data
_logger.Warning(
$"Unable to find trader: " +
$"${offer.User.Nickname}assort for item: ${_itemHelper.GetItemName(offer.Items[0].Template)} " +
$"{offer.Items[0].Template}, cannot check if buy restriction reached"
);
return false;
}
if (assortData.Upd is null)
{
// No Upd = no chance of limits
return false;
}
// No restriction values
// Can't use !assortData.upd.BuyRestrictionX as value could be 0
if (assortData.Upd.BuyRestrictionMax is null || assortData.Upd.BuyRestrictionCurrent is null)
{
return false;
}
// Current equals max, limit reached
if (assortData.Upd.BuyRestrictionCurrent >= assortData.Upd.BuyRestrictionMax)
{
return true;
}
return false;
}
protected List<string> GetLoyaltyLockedOffers(List<RagfairOffer> offers, PmcData pmcProfile)
{
throw new NotImplementedException();
var loyaltyLockedOffers = new List<string>();
foreach (var offer in offers.Where(offer => OfferIsFromTrader(offer)))
if (pmcProfile.TradersInfo.TryGetValue(offer.User.Id, out var traderDetails) &&
traderDetails.LoyaltyLevel < offer.LoyaltyLevel)
{
loyaltyLockedOffers.Add(offer.Id);
}
return loyaltyLockedOffers;
}
/**
@@ -155,7 +482,7 @@ public class RagfairOfferHelper(
return true;
}
foreach (var offer in profileOffers) {
foreach (var offer in profileOffers)
if (offer.SellResults?.Count > 0 && timestamp >= offer.SellResults[0].SellTime)
{
// Checks first item, first is spliced out of array after being processed
@@ -170,13 +497,12 @@ public class RagfairOfferHelper(
boughtAmount = offer.SellResults[0].Amount.Value;
}
var ratingToAdd = (offer.SummaryCost / totalItemsCount) * boughtAmount;
var ratingToAdd = offer.SummaryCost / totalItemsCount * boughtAmount;
IncreaseProfileRagfairRating(_profileHelper.GetFullProfile(sessionId), ratingToAdd.Value);
CompleteOffer(sessionId, offer, boughtAmount);
offer.SellResults.Splice(0, 1); // Remove the sell result object now its been processed
}
}
return true;
}
@@ -189,10 +515,9 @@ public class RagfairOfferHelper(
public double GetTotalStackCountSize(List<List<Item>> itemsInInventoryToList)
{
var total = 0d;
foreach (var itemAndChildren in itemsInInventoryToList) {
foreach (var itemAndChildren in itemsInInventoryToList)
// Only count the root items stack count in total
total += itemAndChildren[0]?.Upd?.StackObjectsCount.GetValueOrDefault(1) ?? 1;
}
return total;
}
@@ -209,12 +534,14 @@ public class RagfairOfferHelper(
profile.CharacterData.PmcData.RagfairInfo.IsRatingGrowing = true;
if (amountToIncrementBy is null)
{
_logger.Warning($"Unable to increment ragfair rating, value was not a number: { amountToIncrementBy}");
_logger.Warning($"Unable to increment ragfair rating, value was not a number: {amountToIncrementBy}");
return;
}
profile.CharacterData.PmcData.RagfairInfo.Rating +=
(ragfairGlobalsConfig.RatingIncreaseCount / ragfairGlobalsConfig.RatingSumForIncrease) *
ragfairGlobalsConfig.RatingIncreaseCount /
ragfairGlobalsConfig.RatingSumForIncrease *
amountToIncrementBy;
}
@@ -288,11 +615,29 @@ public class RagfairOfferHelper(
*/
public bool IsItemFunctional(Item offerRootItem, RagfairOffer offer)
{
throw new NotImplementedException();
// Non-preset weapons/armor are always functional
if (!_presetHelper.HasPreset(offerRootItem.Template))
{
return true;
}
// For armor items that can hold mods, make sure the item count is at least the amount of required plates
if (_itemHelper.ArmorItemCanHoldMods(offerRootItem.Template))
{
var offerRootTemplate = _itemHelper.GetItem(offerRootItem.Template).Value;
var requiredPlateCount = offerRootTemplate.Properties.Slots
?.Where(item => item.Required.GetValueOrDefault(false))
?.Count();
return offer.Items.Count > requiredPlateCount;
}
// For other presets, make sure the offer has more than 1 item
return offer.Items.Count > 1;
}
/// <summary>
/// Should a ragfair offer be visible to the player
/// Should a ragfair offer be visible to the player
/// </summary>
/// <param name="searchRequest">Search request</param>
/// <param name="itemsToAdd">?</param>
@@ -313,23 +658,35 @@ public class RagfairOfferHelper(
throw new NotImplementedException();
}
public bool DisplayableOfferThatNeedsItem(SearchRequestData searchRequest, RagfairOffer offer)
public bool IsDisplayableOfferThatNeedsItem(SearchRequestData searchRequest, RagfairOffer offer)
{
throw new NotImplementedException();
return offer.Requirements.Any(requirement => requirement.Template == searchRequest.NeededSearchId);
}
/// <summary>
/// Does the passed in item have a condition property
/// Does the passed in item have a condition property
/// </summary>
/// <param name="item">Item to check</param>
/// <returns>True if has condition</returns>
protected bool ConditionItem(Item item)
protected bool IsConditionItem(Item item)
{
throw new NotImplementedException();
// thanks typescript, undefined assertion is not returnable since it
// tries to return a multi-type object
if (item.Upd is null)
{
return false;
}
return item.Upd.MedKit is not null ||
item.Upd.Repairable is not null ||
item.Upd.Resource is not null ||
item.Upd.FoodDrink is not null ||
item.Upd.Key is not null ||
item.Upd.RepairKit is not null;
}
/// <summary>
/// Is items quality value within desired range
/// Is items quality value within desired range
/// </summary>
/// <param name="item">Item to check quality of</param>
/// <param name="min">Desired minimum quality</param>
@@ -337,11 +694,24 @@ public class RagfairOfferHelper(
/// <returns>True if in range</returns>
protected bool ItemQualityInRange(Item item, int min, int max)
{
throw new NotImplementedException();
var itemQualityPercentage = 100 * _itemHelper.GetItemQualityModifier(item);
if (min > 0 && min > itemQualityPercentage)
{
// Item condition too low
return false;
}
if (max < 100 && max <= itemQualityPercentage)
{
// Item condition too high
return false;
}
return true;
}
/// <summary>
/// Does this offer come from a trader
/// Does this offer come from a trader
/// </summary>
/// <param name="offer">Offer to check</param>
/// <returns>True = from trader</returns>
@@ -80,7 +80,7 @@ public class CustomLocationWaveService(
locationBase.BossLocationSpawn.Add(bossWave);
_logger.Debug(
$"Added custom boss wave to {mapKvP} of type {bossWave.BossName}, time: {bossWave.Time}, chance: {bossWave.BossChance}, zone: {bossWave.BossZone}"
$"Added custom boss wave to {mapKvP.Key} of type {bossWave.BossName}, time: {bossWave.Time}, chance: {bossWave.BossChance}, zone: {(string.IsNullOrEmpty(bossWave.BossZone) ? "Global" : bossWave.BossZone)}"
);
}
}