using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Profile; using Core.Models.Eft.Ragfair; using Core.Models.Enums; using Core.Models.Spt.Config; using Core.Models.Utils; using Core.Routers; using Core.Servers; using Core.Services; using Core.Utils; using SptCommon.Annotations; using SptCommon.Extensions; namespace Core.Helpers; [Injectable] public class RagfairOfferHelper( ISptLogger _logger, TimeUtil _timeUtil, HashUtil _hashUtil, BotHelper _botHelper, RagfairSortHelper _ragfairSortHelper, PresetHelper _presetHelper, RagfairHelper _ragfairHelper, PaymentHelper _paymentHelper, TraderHelper _traderHelper, QuestHelper _questHelper, RagfairServerHelper _ragfairServerHelper, ItemHelper _itemHelper, DatabaseService _databaseService, RagfairOfferService _ragfairOfferService, LocaleService _localeService, LocalisationService _localisationService, MailSendService _mailSendService, RagfairRequiredItemsService _ragfairRequiredItemsService, ProfileHelper _profileHelper, EventOutputHolder _eventOutputHolder, ConfigServer _configServer) { protected const string _goodSoldTemplate = "5bdabfb886f7743e152e867e 0"; // Your {soldItem} {itemCount} items were bought by {buyerNickname}. protected BotConfig _botConfig = _configServer.GetConfig(); protected RagfairConfig _ragfairConfig = _configServer.GetConfig(); protected static readonly HashSet _currencies = ["all", "RUB", "USD", "EUR"]; /// /// Passthrough to ragfairOfferService.getOffers(), get flea offers a player should see /// /// Data from client /// ragfairHelper.filterCategories() /// Trader assorts /// Player profile /// Offers the player should see public List GetValidOffers( SearchRequestData searchRequest, List itemsToAdd, Dictionary traderAssorts, PmcData pmcData) { var playerIsFleaBanned = _profileHelper.PlayerIsFleaBanned(pmcData); var tieredFlea = _ragfairConfig.TieredFlea; var tieredFleaLimitTypes = tieredFlea.UnlocksType; return _ragfairOfferService.GetOffers() .Where( offer => { if (!PassesSearchFilterCriteria(searchRequest, offer, pmcData)) { return false; } var isDisplayable = IsDisplayableOffer( searchRequest, itemsToAdd, traderAssorts, offer, pmcData, playerIsFleaBanned ); if (!isDisplayable) { return false; } // Not trader offer + tiered flea enabled if (tieredFlea.Enabled && !OfferIsFromTrader(offer)) { CheckAndLockOfferFromPlayerTieredFlea( tieredFlea, offer, tieredFleaLimitTypes.Keys.ToList(), pmcData.Info.Level.Value ); } return true; } ) .ToList(); } /// /// Disable offer if item is flagged by tiered flea config /// /// Tiered flea settings from ragfair config /// Ragfair offer to check /// Dict of item types with player level to be viewable /// Level of player viewing offer protected void CheckAndLockOfferFromPlayerTieredFlea( TieredFlea tieredFlea, RagfairOffer offer, List tieredFleaLimitTypes, int playerLevel) { var offerItemTpl = offer.Items.FirstOrDefault().Template; if (tieredFlea.AmmoTplUnlocks is not null && _itemHelper.IsOfBaseclass(offerItemTpl, BaseClasses.AMMO)) { if (tieredFlea.AmmoTplUnlocks.TryGetValue(offerItemTpl, out var unlockLevel) && playerLevel < unlockLevel) { offer.Locked = true; return; } } // Check for a direct level requirement for the offer item if (tieredFlea.UnlocksTpl.TryGetValue(offerItemTpl, out var itemLevelRequirement)) { if (playerLevel < itemLevelRequirement) { offer.Locked = true; return; } } // Optimisation - Ensure the item has at least one of the limited base types if (_itemHelper.IsOfBaseclasses(offerItemTpl, tieredFleaLimitTypes)) // Loop over flea types { foreach (var tieredItemType in tieredFleaLimitTypes .Where(tieredItemType => _itemHelper.IsOfBaseclass(offerItemTpl, tieredItemType))) { if (playerLevel < tieredFlea.UnlocksType[tieredItemType]) { offer.Locked = true; } break; } } } /// /// Get matching offers that require the desired item and filter out offers from non traders if player is below ragfair /// unlock level /// /// Search request from client /// Player profile /// Matching RagfairOffer objects public List GetOffersThatRequireItem(SearchRequestData searchRequest, PmcData pmcData) { // Get all offers that require the desired item and filter out offers from non traders if player below ragifar unlock var requiredOffers = _ragfairRequiredItemsService.GetRequiredItemsById(searchRequest.NeededSearchId); var tieredFlea = _ragfairConfig.TieredFlea; var tieredFleaLimitTypes = tieredFlea.UnlocksType; return requiredOffers.Where( offer => { if (!PassesSearchFilterCriteria(searchRequest, offer, pmcData)) { return false; } if (tieredFlea.Enabled && !OfferIsFromTrader(offer)) { CheckAndLockOfferFromPlayerTieredFlea( tieredFlea, offer, tieredFleaLimitTypes.Keys.ToList(), pmcData.Info.Level.Value ); } return true; } ) .ToList(); } /// /// Get offers from flea/traders specifically when building weapon preset /// /// Search request data /// string array of item tpls to search for /// All trader assorts player can access/buy /// Player profile /// RagfairOffer array public List GetOffersForBuild( SearchRequestData searchRequest, List itemsToAdd, Dictionary traderAssorts, PmcData pmcData) { var offersMap = new Dictionary>(); var offersToReturn = new List(); 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(); foreach (var possibleOffers in offersMap.Values) { // prepare temp list for offers offersToSort.Clear(); offersToSort.AddRange(possibleOffers); // 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(offersToSort); } // 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 itemsToAdd, Dictionary 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 ( !string.IsNullOrEmpty(searchRequest.NeededSearchId) && !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.GetValueOrDefault(0) > 0 && _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; } /// /// Get offers that have not exceeded buy limits /// /// offers to process /// Offers protected List GetOffersInsideBuyRestrictionLimits(List possibleOffers) { // 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(); } /// /// Check if offer is from trader standing the player does not have /// /// Offer to check /// Player profile /// True if item is locked, false if item is purchaseable protected bool TraderOfferLockedBehindLoyaltyLevel(RagfairOffer offer, PmcData pmcProfile) { 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; } /// /// Check if offer item is quest locked for current player by looking at sptQuestLocked property in traders /// barter_scheme /// /// Offer to check is quest locked /// all trader assorts for player /// true if quest locked public bool TraderOfferItemQuestLocked(RagfairOffer offer, Dictionary traderAssorts) { var itemIds = offer.Items.Select(x => x.Id).ToHashSet(); //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; } /// /// Has trader offer ran out of stock to sell to player /// /// Offer to check stock of /// true if out of stock protected bool TraderOutOfStock(RagfairOffer offer) { if (offer?.Items?.Count == 0) { return true; } return offer.Items[0]?.Upd?.StackObjectsCount == 0; } /// /// Check if trader offers' BuyRestrictionMax value has been reached /// /// Offer to check restriction properties of /// true if restriction reached, false if no restrictions/not reached protected bool TraderBuyRestrictionReached(RagfairOffer offer) { 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 HashSet GetLoyaltyLockedOffers(List offers, PmcData pmcProfile) { var loyaltyLockedOffers = new HashSet(); 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; } /** * Process all player-listed flea offers for a desired profile * @param sessionId Session id to process offers for * @returns true = complete */ public bool ProcessOffersOnProfile(string sessionId) { var timestamp = _timeUtil.GetTimeStamp(); var profileOffers = GetProfileOffers(sessionId); // No offers, don't do anything if (profileOffers?.Count == 0) { return true; } // Index backwards as CompleteOffer() can delete offer object 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) { // 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); } } 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> itemsInInventoryToList) { 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; } /** * 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) */ public void IncreaseProfileRagfairRating(SptProfile profile, double? amountToIncrementBy) { var ragfairGlobalsConfig = _databaseService.GetGlobals().Configuration.RagFair; profile.CharacterData.PmcData.RagfairInfo.IsRatingGrowing = true; if (amountToIncrementBy is null) { _logger.Warning($"Unable to increment ragfair rating, value was not a number: {amountToIncrementBy}"); return; } profile.CharacterData.PmcData.RagfairInfo.Rating += ragfairGlobalsConfig.RatingIncreaseCount / ragfairGlobalsConfig.RatingSumForIncrease * amountToIncrementBy; } /** * Return all offers a player has listed on a desired profile * @param sessionId Session id * @returns List of ragfair offers */ protected List GetProfileOffers(string sessionId) { var profile = _profileHelper.GetPmcProfile(sessionId); if (profile.RagfairInfo?.Offers is null) { return []; } return profile.RagfairInfo.Offers; } /** * Delete an offer from a desired profile and from ragfair offers * @param sessionId Session id of profile to delete offer from * @param offerId Id of offer to delete */ protected void DeleteOfferById(string sessionId, string offerId) { var profileRagfairInfo = _profileHelper.GetPmcProfile(sessionId).RagfairInfo; var offerIndex = profileRagfairInfo.Offers.FindIndex(o => o.Id == offerId); if (offerIndex == -1) { _logger.Warning($"Unable to find offer: {offerId} in profile: {sessionId}, unable to delete"); } if (offerIndex >= 0) { profileRagfairInfo.Offers.Splice(offerIndex, 1); } // Also delete from ragfair _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 */ public ItemEventRouterResponse CompleteOffer(string offerOwnerSessionId, RagfairOffer offer, int boughtAmount) { var rootItem = offer.Items.FirstOrDefault(); var itemTpl = rootItem.Template; var paymentItemsToSendToPlayer = new List(); 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) { DeleteOfferById(offerOwnerSessionId, offer.Id); } else { // Partial purchase, reduce quantity by amount purchased offer.Quantity -= boughtAmount; } // Assemble payment to send to seller now offer was purchased foreach (var requirement in offer.Requirements) { // Create an item template item var requestedItem = new Item { Id = _hashUtil.Generate(), Template = requirement.Template, Upd = new Upd { StackObjectsCount = requirement.Count * boughtAmount } }; var stacks = _itemHelper.SplitStack(requestedItem); foreach (var item in stacks) { var outItems = new List { item }; // TODO - is this code used?, may have been when adding barters to flea was still possible for player if (requirement.OnlyFunctional.GetValueOrDefault(false)) { var presetItems = _ragfairServerHelper.GetPresetItemsByTpl(item); if (presetItems.Count > 0) { outItems.Add(presetItems[0]); } } paymentItemsToSendToPlayer.AddRange(outItems); } } var ragfairDetails = new MessageContentRagfair { OfferId = offer.Id, // pack-offers NEED to be the full item count, // otherwise it only removes 1 from the pack, leaving phantom offer on client ui Count = offer.SellInOnePiece.GetValueOrDefault(false) ? offerStackCount.Value : boughtAmount, HandbookId = itemTpl }; _mailSendService.SendDirectNpcMessageToPlayer( offerOwnerSessionId, _traderHelper.GetTraderById(Traders.RAGMAN).ToString(), MessageType.FLEAMARKET_MESSAGE, GetLocalisedOfferSoldMessage(itemTpl, boughtAmount), paymentItemsToSendToPlayer, _timeUtil.GetHoursAsSeconds((int) _questHelper.GetMailItemRedeemTimeHoursForProfile(sellerProfile).Value), null, ragfairDetails ); // Adjust sellers sell sum values sellerProfile.RagfairInfo.SellSum ??= 0; sellerProfile.RagfairInfo.SellSum += offer.SummaryCost; return _eventOutputHolder.GetOutput(offerOwnerSessionId); } /** * Get a localised message for when players offer has sold on flea * @param itemTpl Item sold * @param boughtAmount How many were purchased * @returns Localised message text */ protected string GetLocalisedOfferSoldMessage(string itemTpl, int boughtAmount) { // Generate a message to inform that item was sold var globalLocales = _localeService.GetLocaleDb(); if (!globalLocales.TryGetValue(_goodSoldTemplate, out var soldMessageLocaleGuid)) { _logger.Error( _localisationService.GetText("ragfair-unable_to_find_locale_by_key", _goodSoldTemplate) ); } // Used to replace tokens in sold message sent to player var messageKey = $"{itemTpl} Name"; var hasKey = globalLocales.TryGetValue(messageKey, out var value); var tplVars = new SystemData { SoldItem = hasKey ? value : itemTpl, BuyerNickname = _botHelper.GetPmcNicknameOfMaxLength(_botConfig.BotNameLengthLimit), ItemCount = boughtAmount }; // Node searches for anything inside {property}: e.g.: "Your {soldItem} {itemCount} items were bought by {buyerNickname}." // each part the takes the inside "Key" and gets it from the tplVars object // 'Your Kalashnikov AKS-74U 5.45x39 assault rifle 1 items were bought by HB.' // then seems to replace any " with nothing // Seems to be much simpler just replacing each key like this. soldMessageLocaleGuid = soldMessageLocaleGuid.Replace("{soldItem}", tplVars.SoldItem); soldMessageLocaleGuid = soldMessageLocaleGuid.Replace("{itemCount}", tplVars.ItemCount.ToString()); soldMessageLocaleGuid = soldMessageLocaleGuid.Replace("{buyerNickname}", tplVars.BuyerNickname); return soldMessageLocaleGuid; } /** * Check an offer passes the various search criteria the player requested * @param searchRequest Client search request * @param offer Offer to check * @param pmcData Player profile * @returns True if offer passes criteria */ protected bool PassesSearchFilterCriteria(SearchRequestData searchRequest, RagfairOffer offer, PmcData pmcData) { var isDefaultUserOffer = offer.User.MemberType == MemberCategory.Default; var offerRootItem = offer.Items[0]; var offerMoneyTypeTpl = offer.Requirements[0].Template; var isTraderOffer = OfferIsFromTrader(offer); if (pmcData.Info.Level < _databaseService.GetGlobals().Configuration.RagFair.MinUserLevel && isDefaultUserOffer) // Skip item if player is < global unlock level (default is 15) and item is from a dynamically generated source { return false; } if (searchRequest.OfferOwnerType == OfferOwnerType.TRADEROWNERTYPE && !isTraderOffer) // don't include player offers { return false; } if (searchRequest.OfferOwnerType == OfferOwnerType.PLAYEROWNERTYPE && isTraderOffer) // don't include trader offers { return false; } if ( searchRequest.OneHourExpiration.GetValueOrDefault(false) && offer.EndTime - _timeUtil.GetTimeStamp() > TimeUtil.OneHourAsSeconds ) // offer expires within an hour { return false; } if (searchRequest.QuantityFrom > 0 && offerRootItem.Upd.StackObjectsCount < searchRequest.QuantityFrom) // too little items to offer { return false; } if (searchRequest.QuantityTo > 0 && offerRootItem.Upd.StackObjectsCount > searchRequest.QuantityTo) // Too many items to offer { return false; } if (searchRequest.OnlyFunctional.GetValueOrDefault(false) && !IsItemFunctional(offerRootItem, offer)) // Don't include non-functional items { return false; } if (offer.Items.Count == 1) { // Counts quality % using the offer items current durability compared to its possible max, not current max // Single item if ( IsConditionItem(offerRootItem) && !ItemQualityInRange(offerRootItem, searchRequest.ConditionFrom.Value, searchRequest.ConditionTo.Value) ) { return false; } } else { var itemQualityPercent = _itemHelper.GetItemQualityModifierForItems(offer.Items) * 100; if (itemQualityPercent < searchRequest.ConditionFrom) { return false; } if (itemQualityPercent > searchRequest.ConditionTo) { return false; } } if (searchRequest.Currency > 0 && _paymentHelper.IsMoneyTpl(offerMoneyTypeTpl)) { // Use 'currencies' as mapping for the money choice dropdown, e.g. 0 = all, 2 = "USD; if(!_currencies.Contains(_ragfairHelper.GetCurrencyTag(offerMoneyTypeTpl))) // Don't include item paid in wrong currency { return false; } } if (searchRequest.PriceFrom > 0 && searchRequest.PriceFrom >= offer.RequirementsCost) // price is too low { return false; } if (searchRequest.PriceTo > 0 && searchRequest.PriceTo <= offer.RequirementsCost) // price is too high { return false; } // Passes above checks, search criteria filters have not filtered offer out return true; } /** * Check that the passed in offer item is functional * @param offerRootItem The root item of the offer * @param offer Flea offer to check * @returns True if the given item is functional */ public bool IsItemFunctional(Item offerRootItem, RagfairOffer offer) { // 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; } /// /// Does the passed in item have a condition property /// /// Item to check /// True if has condition protected bool IsConditionItem(Item item) { // 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; } /// /// Is items quality value within desired range /// /// Item to check quality of /// Desired minimum quality /// Desired maximum quality /// True if in range protected bool ItemQualityInRange(Item item, int min, int max) { 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; } /// /// Does this offer come from a trader /// /// Offer to check /// True = from trader public bool OfferIsFromTrader(RagfairOffer offer) { return offer.User.MemberType == MemberCategory.Trader; } }