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); const pmcProfile = this.profileHelper.getPmcProfile(sessionID);
result.offers = this.getOffersForSearchType(searchRequest, itemsToAdd, traderAssorts, pmcProfile); 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" // Client requested "required search"
if (searchRequest.neededSearchId) 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; result.offersCount = result.offers.length;
// Handle paging before returning results only if searching for general items, not preset items // Handle paging before returning results only if searching for general items, not preset items
if (searchRequest.buildCount === 0) if (searchRequest.buildCount === 0)
{ {
@ -176,21 +178,28 @@ export class RagfairController
* @param offers ragfair offers to get categories for * @param offers ragfair offers to get categories for
* @returns record with tpls + counts * @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 // 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)) if (this.isLinkedSearch(searchRequest) || this.isRequiredSearch(searchRequest))
{ {
return this.ragfairServer.getBespokeCategories(offers); offerPool = offers;
} }
else if ((searchRequest.linkedSearchId === "" && searchRequest.neededSearchId === ""))
// Get all categories
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(); const seasonalItemTplBlacklist = this.seasonalEventService.getAllSeasonalEventItems();
for (const item of items) for (const item of items)
{ {
if (item._type === "Node")
{
continue;
}
if (!this.itemHelper.isValidItem(item._id, ragfairItemInvalidBaseTypes)) if (!this.itemHelper.isValidItem(item._id, ragfairItemInvalidBaseTypes))
{ {
continue; continue;

View File

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

View File

@ -1,5 +1,6 @@
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { Category } from "@spt-aki/models/eft/common/tables/IHandbookBase";
import { Money } from "@spt-aki/models/enums/Money"; import { Money } from "@spt-aki/models/enums/Money";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
@ -164,4 +165,9 @@ export class HandbookHelper
const price = this.getTemplatePrice(currencyTypeTo); const price = this.getTemplatePrice(currencyTypeTo);
return price ? Math.round(roubleCurrencyCount / price) : 0; 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; 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 * Merges Root Items
* Ragfair allows abnormally large stacks. * 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 * Filter out assorts not unlocked due to level OR quest completion
* @param sessionId session id * @param sessionId session id
* @param traderId traders id * @param traderId traders id
* @param flea Should assorts player hasn't unlocked be returned - default false
* @returns a traders' assorts * @returns a traders' assorts
*/ */
public getAssort(sessionId: string, traderId: string, flea = false): ITraderAssort public getAssort(sessionId: string, traderId: string, flea = false): ITraderAssort

View File

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

View File

@ -4,6 +4,7 @@ import { RagfairOfferGenerator } from "@spt-aki/generators/RagfairOfferGenerator
import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper"; import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IRagfairOffer } from "@spt-aki/models/eft/ragfair/IRagfairOffer"; 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 { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { Traders } from "@spt-aki/models/enums/Traders"; import { Traders } from "@spt-aki/models/enums/Traders";
import { IRagfairConfig } from "@spt-aki/models/spt/config/IRagfairConfig"; 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]); 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(); return this.ragfairCategoriesService.getCategoriesFromOffers(offers, searchRequestData, fleaUnlocked);
}
public getBespokeCategories(offers: IRagfairOffer[]): Record<string, number>
{
return this.ragfairCategoriesService.getBespokeCategories(offers);
} }
/** /**

View File

@ -1,96 +1,71 @@
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper";
import { IRagfairOffer } from "@spt-aki/models/eft/ragfair/IRagfairOffer"; 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"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
@injectable() @injectable()
export class RagfairCategoriesService 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 * Get a dictionary of each item the play can see in their flea menu, filtered by what is available for them to buy
* @returns item categories and count * @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; const validOffersForPlayerToSee = {};
}
/**
* 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 = {};
for (const offer of offers) for (const offer of offers)
{ {
this.addOrIncrementCategory(offer, result); const isTraderOffer = offer.user.memberType === MemberCategory.TRADER;
}
return result; // Not level 15 and offer is from player, skip
} if (!fleaUnlocked && !isTraderOffer)
/**
* 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])
{ {
categories[itemId]--; continue;
}
// Remove category entirely as its 0 or less // Remove items not for money when `removeBartering` is enabled
if (categories[itemId] < 1) if (searchRequestData.removeBartering && (offer.requirements.length > 1 || !this.paymentHelper.isMoneyTpl(offer.requirements[0]._tpl)))
{ {
delete categories[itemId]; 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]++;
} }
} }
}
/** return validOffersForPlayerToSee;
* Increase category count by 1
* @param offer
*/
public incrementCategory(offer: IRagfairOffer): void
{
this.addOrIncrementCategory(offer, this.categories);
this.categories[offer.items[0]._tpl]++;
}
/**
* 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 { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { SaveServer } from "@spt-aki/servers/SaveServer"; import { SaveServer } from "@spt-aki/servers/SaveServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RagfairCategoriesService } from "@spt-aki/services/RagfairCategoriesService";
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
import { RagfairOfferHolder } from "@spt-aki/utils/RagfairOfferHolder"; import { RagfairOfferHolder } from "@spt-aki/utils/RagfairOfferHolder";
import { TimeUtil } from "@spt-aki/utils/TimeUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@ -33,7 +32,6 @@ export class RagfairOfferService
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("SaveServer") protected saveServer: SaveServer, @inject("SaveServer") protected saveServer: SaveServer,
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper, @inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
@inject("RagfairCategoriesService") protected ragfairCategoriesService: RagfairCategoriesService,
@inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, @inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
@ -222,9 +220,6 @@ export class RagfairOfferService
this.returnPlayerOffer(staleOffer); 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 // Remove expired existing offer from global offers
this.removeOfferById(staleOffer._id); this.removeOfferById(staleOffer._id);
} }