2023-03-03 15:23:46 +00:00
|
|
|
import { inject, injectable } from "tsyringe";
|
2024-05-21 17:59:04 +00:00
|
|
|
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
|
|
|
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper";
|
|
|
|
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
|
|
|
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
|
|
|
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
|
|
|
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
2024-05-28 22:24:52 +01:00
|
|
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
2024-05-21 17:59:04 +00:00
|
|
|
import { ItemFilterService } from "@spt/services/ItemFilterService";
|
|
|
|
import { RagfairPriceService } from "@spt/services/RagfairPriceService";
|
|
|
|
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
/**
|
2023-11-15 20:35:05 -05:00
|
|
|
* Handle the generation of dynamic PMC loot in pockets and backpacks
|
2023-03-03 15:23:46 +00:00
|
|
|
* and the removal of blacklisted items
|
|
|
|
*/
|
|
|
|
@injectable()
|
|
|
|
export class PMCLootGenerator
|
|
|
|
{
|
2024-02-21 17:36:27 +00:00
|
|
|
protected pocketLootPool: Record<string, number> = {};
|
|
|
|
protected vestLootPool: Record<string, number> = {};
|
|
|
|
protected backpackLootPool: Record<string, number> = {};
|
2023-10-10 11:03:20 +00:00
|
|
|
protected pmcConfig: IPmcConfig;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-05-06 15:21:35 +01:00
|
|
|
protected roubleTpl = "5449016a4bdc2d6f028b456f";
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
constructor(
|
|
|
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
2024-05-28 22:24:52 +01:00
|
|
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
2023-03-03 15:23:46 +00:00
|
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
|
|
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
2024-02-22 14:02:32 +00:00
|
|
|
@inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService,
|
2023-11-15 20:35:05 -05:00
|
|
|
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
2024-05-06 15:20:44 +01:00
|
|
|
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
|
2023-03-03 15:23:46 +00:00
|
|
|
)
|
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an array of loot items a PMC can have in their pockets
|
|
|
|
* @returns string array of tpls
|
|
|
|
*/
|
2024-02-22 16:34:15 +00:00
|
|
|
public generatePMCPocketLootPool(botRole: string): Record<string, number>
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Hydrate loot dictionary if empty
|
|
|
|
if (Object.keys(this.pocketLootPool).length === 0)
|
|
|
|
{
|
2024-05-28 22:24:52 +01:00
|
|
|
const items = this.databaseService.getItems();
|
2024-05-07 23:57:08 -04:00
|
|
|
const pmcPriceOverrides
|
2024-06-06 16:59:44 +00:00
|
|
|
= this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
2024-02-22 16:34:15 +00:00
|
|
|
.Pockets;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
const allowedItemTypes = this.pmcConfig.pocketLoot.whitelist;
|
|
|
|
const pmcItemBlacklist = this.pmcConfig.pocketLoot.blacklist;
|
2023-03-03 15:23:46 +00:00
|
|
|
const itemBlacklist = this.itemFilterService.getBlacklistedItems();
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2024-03-17 13:33:31 +00:00
|
|
|
// Blacklist inactive seasonal items
|
|
|
|
itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems());
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-05-17 15:32:41 -04:00
|
|
|
const itemsToAdd = Object.values(items).filter(
|
|
|
|
(item) =>
|
|
|
|
allowedItemTypes.includes(item._parent)
|
|
|
|
&& this.itemHelper.isValidItem(item._id)
|
|
|
|
&& !pmcItemBlacklist.includes(item._id)
|
|
|
|
&& !itemBlacklist.includes(item._id)
|
|
|
|
&& item._props.Width === 1
|
|
|
|
&& item._props.Height === 1,
|
2023-11-15 20:35:05 -05:00
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-02-21 17:36:27 +00:00
|
|
|
for (const itemToAdd of itemsToAdd)
|
|
|
|
{
|
2024-02-22 16:34:15 +00:00
|
|
|
// If pmc has override, use that. Otherwise use flea price
|
|
|
|
if (pmcPriceOverrides[itemToAdd._id])
|
|
|
|
{
|
|
|
|
this.pocketLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Set price of item as its weight
|
2024-05-06 15:21:35 +01:00
|
|
|
const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl);
|
2024-02-22 16:34:15 +00:00
|
|
|
this.pocketLootPool[itemToAdd._id] = price;
|
|
|
|
}
|
2024-02-22 14:02:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const highestPrice = Math.max(...Object.values(this.backpackLootPool));
|
|
|
|
for (const key of Object.keys(this.pocketLootPool))
|
|
|
|
{
|
|
|
|
// Invert price so cheapest has a larger weight
|
|
|
|
// Times by highest price so most expensive item has weight of 1
|
2024-05-17 15:32:41 -04:00
|
|
|
this.pocketLootPool[key] = Math.round((1 / this.pocketLootPool[key]) * highestPrice);
|
2024-02-22 14:02:32 +00:00
|
|
|
}
|
|
|
|
|
2024-05-06 15:20:44 +01:00
|
|
|
this.weightedRandomHelper.reduceWeightValues(this.pocketLootPool);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return this.pocketLootPool;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an array of loot items a PMC can have in their vests
|
|
|
|
* @returns string array of tpls
|
|
|
|
*/
|
2024-02-22 16:34:15 +00:00
|
|
|
public generatePMCVestLootPool(botRole: string): Record<string, number>
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Hydrate loot dictionary if empty
|
|
|
|
if (Object.keys(this.vestLootPool).length === 0)
|
|
|
|
{
|
2024-05-28 22:24:52 +01:00
|
|
|
const items = this.databaseService.getItems();
|
2024-05-07 23:57:08 -04:00
|
|
|
const pmcPriceOverrides
|
2024-06-06 16:59:44 +00:00
|
|
|
= this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
2024-02-22 16:34:15 +00:00
|
|
|
.TacticalVest;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
const allowedItemTypes = this.pmcConfig.vestLoot.whitelist;
|
|
|
|
const pmcItemBlacklist = this.pmcConfig.vestLoot.blacklist;
|
2023-03-03 15:23:46 +00:00
|
|
|
const itemBlacklist = this.itemFilterService.getBlacklistedItems();
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2024-03-17 13:33:31 +00:00
|
|
|
// Blacklist seasonal items
|
|
|
|
itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems());
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-05-17 15:32:41 -04:00
|
|
|
const itemsToAdd = Object.values(items).filter(
|
|
|
|
(item) =>
|
|
|
|
allowedItemTypes.includes(item._parent)
|
|
|
|
&& this.itemHelper.isValidItem(item._id)
|
|
|
|
&& !pmcItemBlacklist.includes(item._id)
|
|
|
|
&& !itemBlacklist.includes(item._id)
|
|
|
|
&& this.itemFitsInto2By2Slot(item),
|
2023-11-15 20:35:05 -05:00
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-02-21 17:36:27 +00:00
|
|
|
for (const itemToAdd of itemsToAdd)
|
|
|
|
{
|
2024-02-22 16:34:15 +00:00
|
|
|
// If pmc has override, use that. Otherwise use flea price
|
|
|
|
if (pmcPriceOverrides[itemToAdd._id])
|
|
|
|
{
|
|
|
|
this.vestLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Set price of item as its weight
|
2024-05-06 15:21:35 +01:00
|
|
|
const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl);
|
2024-02-22 16:34:15 +00:00
|
|
|
this.vestLootPool[itemToAdd._id] = price;
|
|
|
|
}
|
2024-02-21 17:36:27 +00:00
|
|
|
}
|
2024-02-22 14:02:32 +00:00
|
|
|
|
|
|
|
const highestPrice = Math.max(...Object.values(this.backpackLootPool));
|
|
|
|
for (const key of Object.keys(this.vestLootPool))
|
|
|
|
{
|
|
|
|
// Invert price so cheapest has a larger weight
|
|
|
|
// Times by highest price so most expensive item has weight of 1
|
2024-05-17 15:32:41 -04:00
|
|
|
this.vestLootPool[key] = Math.round((1 / this.vestLootPool[key]) * highestPrice);
|
2024-02-22 14:02:32 +00:00
|
|
|
}
|
|
|
|
|
2024-05-06 15:20:44 +01:00
|
|
|
this.weightedRandomHelper.reduceWeightValues(this.vestLootPool);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return this.vestLootPool;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-10 11:03:20 +00:00
|
|
|
* Check if item has a width/height that lets it fit into a 2x2 slot
|
|
|
|
* 1x1 / 1x2 / 2x1 / 2x2
|
2023-03-03 15:23:46 +00:00
|
|
|
* @param item Item to check size of
|
|
|
|
* @returns true if it fits
|
|
|
|
*/
|
2023-10-10 11:03:20 +00:00
|
|
|
protected itemFitsInto2By2Slot(item: ITemplateItem): boolean
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
return item._props.Width <= 2 && item._props.Height <= 2;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an array of loot items a PMC can have in their backpack
|
|
|
|
* @returns string array of tpls
|
|
|
|
*/
|
2024-02-22 16:34:15 +00:00
|
|
|
public generatePMCBackpackLootPool(botRole: string): Record<string, number>
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Hydrate loot dictionary if empty
|
|
|
|
if (Object.keys(this.backpackLootPool).length === 0)
|
|
|
|
{
|
2024-05-28 22:24:52 +01:00
|
|
|
const items = this.databaseService.getItems();
|
2024-05-07 23:57:08 -04:00
|
|
|
const pmcPriceOverrides
|
2024-06-06 16:59:44 +00:00
|
|
|
= this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
2024-02-22 16:34:15 +00:00
|
|
|
.Backpack;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
const allowedItemTypes = this.pmcConfig.backpackLoot.whitelist;
|
|
|
|
const pmcItemBlacklist = this.pmcConfig.backpackLoot.blacklist;
|
2023-03-03 15:23:46 +00:00
|
|
|
const itemBlacklist = this.itemFilterService.getBlacklistedItems();
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2024-03-17 13:33:31 +00:00
|
|
|
// Blacklist seasonal items
|
|
|
|
itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems());
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-05-17 15:32:41 -04:00
|
|
|
const itemsToAdd = Object.values(items).filter(
|
|
|
|
(item) =>
|
|
|
|
allowedItemTypes.includes(item._parent)
|
|
|
|
&& this.itemHelper.isValidItem(item._id)
|
|
|
|
&& !pmcItemBlacklist.includes(item._id)
|
|
|
|
&& !itemBlacklist.includes(item._id),
|
2023-11-15 20:35:05 -05:00
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-02-21 17:36:27 +00:00
|
|
|
for (const itemToAdd of itemsToAdd)
|
|
|
|
{
|
2024-04-08 12:31:51 +01:00
|
|
|
// If pmc has price override, use that. Otherwise use flea price
|
2024-02-22 16:34:15 +00:00
|
|
|
if (pmcPriceOverrides[itemToAdd._id])
|
|
|
|
{
|
|
|
|
this.backpackLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Set price of item as its weight
|
2024-05-06 15:21:35 +01:00
|
|
|
const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl);
|
2024-02-22 16:34:15 +00:00
|
|
|
this.backpackLootPool[itemToAdd._id] = price;
|
|
|
|
}
|
2024-02-22 14:02:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const highestPrice = Math.max(...Object.values(this.backpackLootPool));
|
|
|
|
for (const key of Object.keys(this.backpackLootPool))
|
|
|
|
{
|
|
|
|
// Invert price so cheapest has a larger weight
|
|
|
|
// Times by highest price so most expensive item has weight of 1
|
2024-05-17 15:32:41 -04:00
|
|
|
this.backpackLootPool[key] = Math.round((1 / this.backpackLootPool[key]) * highestPrice);
|
2024-02-21 17:36:27 +00:00
|
|
|
}
|
2024-02-22 14:02:32 +00:00
|
|
|
|
2024-05-06 15:20:44 +01:00
|
|
|
this.weightedRandomHelper.reduceWeightValues(this.backpackLootPool);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return this.backpackLootPool;
|
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
}
|