2024-09-01 13:18:30 +01:00
|
|
|
import { IHandbookCategory } from "@spt/models/eft/common/tables/IHandbookBase";
|
2024-09-24 12:47:29 +01:00
|
|
|
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
2024-05-21 17:59:04 +00:00
|
|
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
|
|
|
import { Money } from "@spt/models/enums/Money";
|
|
|
|
import { IItemConfig } from "@spt/models/spt/config/IItemConfig";
|
|
|
|
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 { ICloner } from "@spt/utils/cloners/ICloner";
|
2024-07-23 11:12:53 -04:00
|
|
|
import { inject, injectable } from "tsyringe";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
class LookupItem<T, I> {
|
2023-04-22 19:33:47 +01:00
|
|
|
readonly byId: Map<string, T>;
|
|
|
|
readonly byParent: Map<string, I[]>;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
constructor() {
|
2023-04-22 19:33:47 +01:00
|
|
|
this.byId = new Map();
|
|
|
|
this.byParent = new Map();
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
export class LookupCollection {
|
2023-04-22 19:33:47 +01:00
|
|
|
readonly items: LookupItem<number, string>;
|
|
|
|
readonly categories: LookupItem<string, string>;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
constructor() {
|
2023-04-22 19:33:47 +01:00
|
|
|
this.items = new LookupItem<number, string>();
|
|
|
|
this.categories = new LookupItem<string, string>();
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@injectable()
|
2024-07-23 11:12:53 -04:00
|
|
|
export class HandbookHelper {
|
2024-02-06 12:16:26 +00:00
|
|
|
protected itemConfig: IItemConfig;
|
2023-03-03 15:23:46 +00:00
|
|
|
protected lookupCacheGenerated = false;
|
|
|
|
protected handbookPriceCache = new LookupCollection();
|
|
|
|
|
2023-11-20 13:47:47 +00:00
|
|
|
constructor(
|
2024-05-28 22:24:52 +01:00
|
|
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
2024-02-06 12:16:26 +00:00
|
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
2024-05-28 14:04:20 +00:00
|
|
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
2024-07-23 11:12:53 -04:00
|
|
|
) {
|
2024-02-06 12:16:26 +00:00
|
|
|
this.itemConfig = this.configServer.getConfig(ConfigTypes.ITEM);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-07-24 15:52:55 +01:00
|
|
|
/**
|
|
|
|
* Create an in-memory cache of all items with associated handbook price in handbookPriceCache class
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public hydrateLookup(): void {
|
2024-05-28 22:24:52 +01:00
|
|
|
const handbook = this.databaseService.getHandbook();
|
2024-02-06 12:52:56 +00:00
|
|
|
// Add handbook overrides found in items.json config into db
|
2024-11-05 09:27:21 +00:00
|
|
|
for (const itemTplKey of Object.keys(this.itemConfig.handbookPriceOverride)) {
|
|
|
|
const data = this.itemConfig.handbookPriceOverride[itemTplKey];
|
|
|
|
|
|
|
|
let itemToUpdate = handbook.Items.find((item) => item.Id === itemTplKey);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!itemToUpdate) {
|
2024-05-28 22:24:52 +01:00
|
|
|
handbook.Items.push({
|
2024-11-05 09:27:21 +00:00
|
|
|
Id: itemTplKey,
|
|
|
|
ParentId: data.parentId,
|
|
|
|
Price: data.price,
|
2024-02-06 12:52:56 +00:00
|
|
|
});
|
2024-11-05 09:27:21 +00:00
|
|
|
itemToUpdate = handbook.Items.find((item) => item.Id === itemTplKey);
|
2024-02-06 12:52:56 +00:00
|
|
|
}
|
|
|
|
|
2024-11-05 09:27:21 +00:00
|
|
|
itemToUpdate.Price = data.price;
|
2024-02-06 12:52:56 +00:00
|
|
|
}
|
|
|
|
|
2024-05-28 22:24:52 +01:00
|
|
|
const handbookDbClone = this.cloner.clone(handbook);
|
2024-07-23 11:12:53 -04:00
|
|
|
for (const handbookItem of handbookDbClone.Items) {
|
2023-04-22 19:33:47 +01:00
|
|
|
this.handbookPriceCache.items.byId.set(handbookItem.Id, handbookItem.Price);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!this.handbookPriceCache.items.byParent.has(handbookItem.ParentId)) {
|
2023-04-22 19:33:47 +01:00
|
|
|
this.handbookPriceCache.items.byParent.set(handbookItem.ParentId, []);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
this.handbookPriceCache.items.byParent.get(handbookItem.ParentId).push(handbookItem.Id);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
for (const handbookCategory of handbookDbClone.Categories) {
|
2024-05-27 20:06:07 +00:00
|
|
|
this.handbookPriceCache.categories.byId.set(handbookCategory.Id, handbookCategory.ParentId || undefined);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (handbookCategory.ParentId) {
|
|
|
|
if (!this.handbookPriceCache.categories.byParent.has(handbookCategory.ParentId)) {
|
2023-04-22 19:33:47 +01:00
|
|
|
this.handbookPriceCache.categories.byParent.set(handbookCategory.ParentId, []);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
this.handbookPriceCache.categories.byParent.get(handbookCategory.ParentId).push(handbookCategory.Id);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get price from internal cache, if cache empty look up price directly in handbook (expensive)
|
2024-05-20 07:58:13 +00:00
|
|
|
* If no values found, return 0
|
2024-05-28 22:24:52 +01:00
|
|
|
* @param tpl Item tpl to look up price for
|
2023-03-03 15:23:46 +00:00
|
|
|
* @returns price in roubles
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getTemplatePrice(tpl: string): number {
|
|
|
|
if (!this.lookupCacheGenerated) {
|
2023-03-03 15:23:46 +00:00
|
|
|
this.hydrateLookup();
|
|
|
|
this.lookupCacheGenerated = true;
|
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
if (this.handbookPriceCache.items.byId.has(tpl)) {
|
2023-04-23 11:13:53 +01:00
|
|
|
return this.handbookPriceCache.items.byId.get(tpl);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-05-28 22:24:52 +01:00
|
|
|
const handbookItem = this.databaseService.getHandbook().Items.find((item) => item.Id === tpl);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!handbookItem) {
|
2023-11-07 21:21:34 +00:00
|
|
|
const newValue = 0;
|
|
|
|
this.handbookPriceCache.items.byId.set(tpl, newValue);
|
2023-10-10 11:03:20 +00:00
|
|
|
|
2023-11-07 21:21:34 +00:00
|
|
|
return newValue;
|
2023-10-10 11:03:20 +00:00
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-05-20 07:58:13 +00:00
|
|
|
this.handbookPriceCache.items.byId.set(tpl, handbookItem.Price);
|
2023-10-10 11:03:20 +00:00
|
|
|
return handbookItem.Price;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-09-24 12:47:29 +01:00
|
|
|
public getTemplatePriceForItems(items: IItem[]): number {
|
2024-02-05 19:52:46 +00:00
|
|
|
let total = 0;
|
2024-07-23 11:12:53 -04:00
|
|
|
for (const item of items) {
|
2024-02-05 19:52:46 +00:00
|
|
|
total += this.getTemplatePrice(item._tpl);
|
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
/**
|
2023-07-24 15:52:55 +01:00
|
|
|
* Get all items in template with the given parent category
|
2023-11-16 21:42:06 +00:00
|
|
|
* @param parentId
|
2023-03-03 15:23:46 +00:00
|
|
|
* @returns string array
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public templatesWithParent(parentId: string): string[] {
|
2023-07-24 15:52:55 +01:00
|
|
|
return this.handbookPriceCache.items.byParent.get(parentId) ?? [];
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Does category exist in handbook cache
|
2023-11-16 21:42:06 +00:00
|
|
|
* @param category
|
2023-03-03 15:23:46 +00:00
|
|
|
* @returns true if exists in cache
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public isCategory(category: string): boolean {
|
2023-04-22 19:33:47 +01:00
|
|
|
return this.handbookPriceCache.categories.byId.has(category);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-24 15:52:55 +01:00
|
|
|
/**
|
|
|
|
* Get all items associated with a categories parent
|
2023-11-16 21:42:06 +00:00
|
|
|
* @param categoryParent
|
2023-07-24 15:52:55 +01:00
|
|
|
* @returns string array
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public childrenCategories(categoryParent: string): string[] {
|
2023-07-24 15:52:55 +01:00
|
|
|
return this.handbookPriceCache.categories.byParent.get(categoryParent) ?? [];
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert non-roubles into roubles
|
|
|
|
* @param nonRoubleCurrencyCount Currency count to convert
|
|
|
|
* @param currencyTypeFrom What current currency is
|
|
|
|
* @returns Count in roubles
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public inRUB(nonRoubleCurrencyCount: number, currencyTypeFrom: string): number {
|
|
|
|
if (currencyTypeFrom === Money.ROUBLES) {
|
2023-03-03 15:23:46 +00:00
|
|
|
return nonRoubleCurrencyCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Math.round(nonRoubleCurrencyCount * (this.getTemplatePrice(currencyTypeFrom) || 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert roubles into another currency
|
|
|
|
* @param roubleCurrencyCount roubles to convert
|
|
|
|
* @param currencyTypeTo Currency to convert roubles into
|
|
|
|
* @returns currency count in desired type
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public fromRUB(roubleCurrencyCount: number, currencyTypeTo: string): number {
|
|
|
|
if (currencyTypeTo === Money.ROUBLES) {
|
2023-03-03 15:23:46 +00:00
|
|
|
return roubleCurrencyCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get price of currency from handbook
|
|
|
|
const price = this.getTemplatePrice(currencyTypeTo);
|
2024-03-18 16:25:04 +00:00
|
|
|
return price ? Math.max(1, Math.round(roubleCurrencyCount / price)) : 0;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-12-05 20:41:43 +00:00
|
|
|
|
2024-09-01 13:18:30 +01:00
|
|
|
public getCategoryById(handbookId: string): IHandbookCategory {
|
2024-05-28 22:24:52 +01:00
|
|
|
return this.databaseService.getHandbook().Categories.find((category) => category.Id === handbookId);
|
2023-12-05 20:41:43 +00:00
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
}
|