Reworked how the flea market categories are calculated, instead of trying to be smart and add/remove in a cache as offers are created, calculate the categories when needed

Categories:
Are now much more accurate
take into account when player is below flea unlock level
Any with a (1) and no offers have been fixed
Take into account when offers are barters + barters are filtered out

Skip items with a type of `node` during flea assort generation
This commit is contained in:
Dev 2023-12-05 20:41:43 +00:00
parent 56366068e0
commit 7f995de5d1
10 changed files with 83 additions and 127 deletions

View File

@ -95,7 +95,11 @@ export class RagfairController
const pmcProfile = this.profileHelper.getPmcProfile(sessionID);
result.offers = this.getOffersForSearchType(searchRequest, itemsToAdd, traderAssorts, pmcProfile);
result.categories = this.getSpecificCategories(searchRequest, result.offers);
if (searchRequest.updateOfferCount)
{
result.categories = this.getSpecificCategories(pmcProfile, searchRequest, result.offers);
}
// Client requested "required search"
if (searchRequest.neededSearchId)
@ -131,10 +135,8 @@ export class RagfairController
}
}
// Set categories count (needed for categories to show when choosing 'Linked search')
this.ragfairHelper.countCategories(result);
result.offersCount = result.offers.length;
// Handle paging before returning results only if searching for general items, not preset items
if (searchRequest.buildCount === 0)
{
@ -176,21 +178,28 @@ export class RagfairController
* @param offers ragfair offers to get categories for
* @returns record with tpls + counts
*/
protected getSpecificCategories(searchRequest: ISearchRequestData, offers: IRagfairOffer[]): Record<string, number>
protected getSpecificCategories(pmcProfile: IPmcData, searchRequest: ISearchRequestData, offers: IRagfairOffer[]): Record<string, number>
{
// Linked/required search categories
const playerHasFleaUnlocked = pmcProfile.Info.Level > this.databaseServer.getTables().globals.config.RagFair.minUserLevel;
let offerPool = [];
if (this.isLinkedSearch(searchRequest) || this.isRequiredSearch(searchRequest))
{
return this.ragfairServer.getBespokeCategories(offers);
offerPool = offers;
}
// Get all categories
if ((searchRequest.linkedSearchId === "" && searchRequest.neededSearchId === ""))
else if ((searchRequest.linkedSearchId === "" && searchRequest.neededSearchId === ""))
{
return this.ragfairServer.getAllCategories();
// Get all categories
offerPool = this.ragfairOfferService.getOffers();
}
else
{
this.logger.error("Unable to get categories from search criteria, see log for request data");
this.logger.debug(JSON.stringify(searchRequest));
return {};
}
return {};
return this.ragfairServer.getAllActiveCategories(playerHasFleaUnlocked, searchRequest, offerPool);
}
/**

View File

@ -79,6 +79,11 @@ export class RagfairAssortGenerator
const seasonalItemTplBlacklist = this.seasonalEventService.getAllSeasonalEventItems();
for (const item of items)
{
if (item._type === "Node")
{
continue;
}
if (!this.itemHelper.isValidItem(item._id, ragfairItemInvalidBaseTypes))
{
continue;

View File

@ -21,7 +21,6 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { SaveServer } from "@spt-aki/servers/SaveServer";
import { FenceService } from "@spt-aki/services/FenceService";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RagfairCategoriesService } from "@spt-aki/services/RagfairCategoriesService";
import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService";
import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
@ -51,7 +50,6 @@ export class RagfairOfferGenerator
@inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("RagfairCategoriesService") protected ragfairCategoriesService: RagfairCategoriesService,
@inject("FenceService") protected fenceService: FenceService,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("ConfigServer") protected configServer: ConfigServer,
@ -431,8 +429,6 @@ export class RagfairOfferGenerator
1,
isPreset || isPackOffer,
); // sellAsOnePiece
this.ragfairCategoriesService.incrementCategory(offer);
}
/**
@ -510,8 +506,6 @@ export class RagfairOfferGenerator
const offer = this.createFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel, false);
this.ragfairCategoriesService.incrementCategory(offer);
// Refresh complete, reset flag to false
trader.base.refreshTraderRagfairOffers = false;
}

View File

@ -1,5 +1,6 @@
import { inject, injectable } from "tsyringe";
import { Category } from "@spt-aki/models/eft/common/tables/IHandbookBase";
import { Money } from "@spt-aki/models/enums/Money";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
@ -164,4 +165,9 @@ export class HandbookHelper
const price = this.getTemplatePrice(currencyTypeTo);
return price ? Math.round(roubleCurrencyCount / price) : 0;
}
public getCategoryById(handbookId: string): Category
{
return this.databaseServer.getTables().templates.handbook.Categories.find(x => x.Id === handbookId);
}
}

View File

@ -147,31 +147,6 @@ export class RagfairHelper
return result;
}
/* Because of presets, categories are not always 1 */
public countCategories(result: IGetOffersResult): void
{
const categories = {};
for (const offer of result.offers)
{
// only the first item can have presets
const item = offer.items[0];
categories[item._tpl] = categories[item._tpl] || 0;
categories[item._tpl]++;
}
// not in search mode, add back non-weapon items
for (const category in result.categories)
{
if (!categories[category])
{
categories[category] = 1;
}
}
result.categories = categories;
}
/**
* Merges Root Items
* Ragfair allows abnormally large stacks.

View File

@ -58,6 +58,7 @@ export class TraderAssortHelper
* Filter out assorts not unlocked due to level OR quest completion
* @param sessionId session id
* @param traderId traders id
* @param flea Should assorts player hasn't unlocked be returned - default false
* @returns a traders' assorts
*/
public getAssort(sessionId: string, traderId: string, flea = false): ITraderAssort

View File

@ -154,7 +154,7 @@ export interface Blacklist
enableBsgList: boolean;
/** Should quest items be blacklisted from flea */
enableQuestList: boolean;
/** Should trader items that are blacklisted by bsg */
/** Should trader items that are blacklisted by bsg be listed on flea */
traderItems: boolean;
}

View File

@ -4,6 +4,7 @@ import { RagfairOfferGenerator } from "@spt-aki/generators/RagfairOfferGenerator
import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IRagfairOffer } from "@spt-aki/models/eft/ragfair/IRagfairOffer";
import { ISearchRequestData } from "@spt-aki/models/eft/ragfair/ISearchRequestData";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { Traders } from "@spt-aki/models/enums/Traders";
import { IRagfairConfig } from "@spt-aki/models/spt/config/IRagfairConfig";
@ -82,14 +83,9 @@ export class RagfairServer
return Object.keys(this.ragfairConfig.traders).filter((x) => this.ragfairConfig.traders[x]);
}
public getAllCategories(): Record<string, number>
public getAllActiveCategories(fleaUnlocked: boolean, searchRequestData: ISearchRequestData, offers: IRagfairOffer[]): Record<string, number>
{
return this.ragfairCategoriesService.getAllCategories();
}
public getBespokeCategories(offers: IRagfairOffer[]): Record<string, number>
{
return this.ragfairCategoriesService.getBespokeCategories(offers);
return this.ragfairCategoriesService.getCategoriesFromOffers(offers, searchRequestData, fleaUnlocked);
}
/**

View File

@ -1,96 +1,71 @@
import { inject, injectable } from "tsyringe";
import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper";
import { IRagfairOffer } from "@spt-aki/models/eft/ragfair/IRagfairOffer";
import { ISearchRequestData, OfferOwnerType } from "@spt-aki/models/eft/ragfair/ISearchRequestData";
import { MemberCategory } from "@spt-aki/models/enums/MemberCategory";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
@injectable()
export class RagfairCategoriesService
{
protected categories: Record<string, number> = {};
constructor(@inject("WinstonLogger") protected logger: ILogger)
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
)
{}
/**
* Get all flea categories and their count of offers
* @returns item categories and count
* Get a dictionary of each item the play can see in their flea menu, filtered by what is available for them to buy
* @param offers All offers in flea
* @param searchRequestData Search criteria requested
* @param fleaUnlocked Can player see full flea yet (level 15 by default)
* @returns KVP of item tpls + count of offers
*/
public getAllCategories(): Record<string, number>
public getCategoriesFromOffers(offers: IRagfairOffer[], searchRequestData: ISearchRequestData, fleaUnlocked: boolean): Record<string, number>
{
return this.categories;
}
/**
* With the supplied items, get custom categories
* @returns a custom list of categories
*/
public getBespokeCategories(offers: IRagfairOffer[]): Record<string, number>
{
return this.processOffersIntoCategories(offers);
}
/**
* Take an array of ragfair offers and create a dictionary of items with thier corrisponding offer count
* @param offers ragfair offers
* @returns categories and count
*/
protected processOffersIntoCategories(offers: IRagfairOffer[]): Record<string, number>
{
const result = {};
const validOffersForPlayerToSee = {};
for (const offer of offers)
{
this.addOrIncrementCategory(offer, result);
}
const isTraderOffer = offer.user.memberType === MemberCategory.TRADER;
return result;
}
/**
* Increment or decrement a category array
* @param offer Offer to process
* @param categories Categories to update
* @param increment (Optional) Should item be incremented or decremented
*/
protected addOrIncrementCategory(offer: IRagfairOffer, categories: Record<string, number>, increment = true): void
{
const itemId = offer.items[0]._tpl;
if (increment)
{
categories[itemId] = categories[itemId] ? categories[itemId] + 1 : 1;
}
else
{
// No category, no work to do
if (categories[itemId])
// Not level 15 and offer is from player, skip
if (!fleaUnlocked && !isTraderOffer)
{
categories[itemId]--;
continue;
}
// Remove category entirely as its 0 or less
if (categories[itemId] < 1)
{
delete categories[itemId];
}
// Remove items not for money when `removeBartering` is enabled
if (searchRequestData.removeBartering && (offer.requirements.length > 1 || !this.paymentHelper.isMoneyTpl(offer.requirements[0]._tpl)))
{
continue;
}
// Remove when filter set to players only + offer is from trader
if (searchRequestData.offerOwnerType === OfferOwnerType.PLAYEROWNERTYPE && isTraderOffer)
{
continue;
}
// Remove when filter set to traders only + offer is not from trader
if (searchRequestData.offerOwnerType === OfferOwnerType.TRADEROWNERTYPE && !isTraderOffer)
{
continue;
}
const itemTpl = offer.items[0]._tpl
if (!validOffersForPlayerToSee[itemTpl])
{
validOffersForPlayerToSee[itemTpl] = 1;
}
else
{
validOffersForPlayerToSee[itemTpl]++;
}
}
}
/**
* Increase category count by 1
* @param offer
*/
public incrementCategory(offer: IRagfairOffer): void
{
this.addOrIncrementCategory(offer, this.categories);
this.categories[offer.items[0]._tpl]++;
}
return validOffersForPlayerToSee;
/**
* Reduce category count by 1
* @param offer
*/
public decrementCategory(offer: IRagfairOffer): void
{
this.addOrIncrementCategory(offer, this.categories, false);
this.categories[offer.items[0]._tpl]--;
}
}

View File

@ -13,7 +13,6 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { SaveServer } from "@spt-aki/servers/SaveServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RagfairCategoriesService } from "@spt-aki/services/RagfairCategoriesService";
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
import { RagfairOfferHolder } from "@spt-aki/utils/RagfairOfferHolder";
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@ -33,7 +32,6 @@ export class RagfairOfferService
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("SaveServer") protected saveServer: SaveServer,
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
@inject("RagfairCategoriesService") protected ragfairCategoriesService: RagfairCategoriesService,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
@ -222,9 +220,6 @@ export class RagfairOfferService
this.returnPlayerOffer(staleOffer);
}
// Reduce category count by 1 as offer is now stale and about to be removed
this.ragfairCategoriesService.decrementCategory(staleOffer);
// Remove expired existing offer from global offers
this.removeOfferById(staleOffer._id);
}