From 3951e29a340e917d158ec061ee671c4ae0f9c8ec Mon Sep 17 00:00:00 2001 From: Refringe Date: Mon, 8 Apr 2024 22:00:02 +0000 Subject: [PATCH 01/12] Refresh ItemDB Cache Workflow (!282) This commit adds a workflow that will send an API request to the item DB website that will clear its cache. It will run whenever there is a commit on the `master` branch with a change to one of the files within the `project/assets/database` directory. Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/282 Co-authored-by: Refringe Co-committed-by: Refringe --- .gitea/workflows/clear-itemdb-cache.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .gitea/workflows/clear-itemdb-cache.yaml diff --git a/.gitea/workflows/clear-itemdb-cache.yaml b/.gitea/workflows/clear-itemdb-cache.yaml new file mode 100644 index 00000000..8783caa3 --- /dev/null +++ b/.gitea/workflows/clear-itemdb-cache.yaml @@ -0,0 +1,21 @@ +name: Clear Item DB Website Cache + +on: + push: + branches: [ master ] + +jobs: + clear-cache: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - id: filter + uses: dorny/paths-filter@v3.0.2 + with: + list-files: shell + filters: | + database: + - 'project/assets/database/**' + - name: Send Refresh Request + if: steps.filter.outputs.database == 'true' + run: curl --max-time 30 https://db.sp-tarkov.com/api/refresh From a0981e9297daa5d51f981ffb1a7cc3f88e31ebc9 Mon Sep 17 00:00:00 2001 From: nader Date: Tue, 23 Apr 2024 18:56:42 +0000 Subject: [PATCH 02/12] add getters (and additional setter) to FenceService (!302) Co-authored-by: Nader Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/302 Co-authored-by: nader Co-committed-by: nader --- project/src/services/FenceService.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/project/src/services/FenceService.ts b/project/src/services/FenceService.ts index db4f83b7..4b348cfe 100644 --- a/project/src/services/FenceService.ts +++ b/project/src/services/FenceService.ts @@ -71,6 +71,33 @@ export class FenceService this.fenceAssort = assort; } + /** + * Replace discount fence assort with new assort + * @param assort New assorts to replace old with + */ + public setDiscountFenceAssort(assort: ITraderAssort): void + { + this.fenceDiscountAssort = assort; + } + + /** + * Get main fence assort + * @return ITraderAssort + */ + public getMainFenceAssort(): ITraderAssort + { + return this.fenceAssort; + } + + /** + * Get discount fence assort + * @return ITraderAssort + */ + public getDiscountFenceAssort(): ITraderAssort + { + return this.fenceDiscountAssort; + } + /** * Replace high rep level fence assort with new assort * @param discountAssort New assorts to replace old with From 42db0aca0bf19772778d2608dfdea7410afa9cc5 Mon Sep 17 00:00:00 2001 From: Refringe Date: Tue, 23 Apr 2024 15:07:05 -0400 Subject: [PATCH 03/12] Revert "add getters (and additional setter) to FenceService (!302)" This reverts commit a0981e9297daa5d51f981ffb1a7cc3f88e31ebc9. --- project/src/services/FenceService.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/project/src/services/FenceService.ts b/project/src/services/FenceService.ts index 4b348cfe..db4f83b7 100644 --- a/project/src/services/FenceService.ts +++ b/project/src/services/FenceService.ts @@ -71,33 +71,6 @@ export class FenceService this.fenceAssort = assort; } - /** - * Replace discount fence assort with new assort - * @param assort New assorts to replace old with - */ - public setDiscountFenceAssort(assort: ITraderAssort): void - { - this.fenceDiscountAssort = assort; - } - - /** - * Get main fence assort - * @return ITraderAssort - */ - public getMainFenceAssort(): ITraderAssort - { - return this.fenceAssort; - } - - /** - * Get discount fence assort - * @return ITraderAssort - */ - public getDiscountFenceAssort(): ITraderAssort - { - return this.fenceDiscountAssort; - } - /** * Replace high rep level fence assort with new assort * @param discountAssort New assorts to replace old with From 84d5462955ce8f1e306ceb0a1587658558b2c8dc Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 May 2024 08:01:18 +0000 Subject: [PATCH 04/12] Fixed give command to use dice coefficient making it more precise. Fixed give command giving invalid items to players. (!323) Co-authored-by: clodan Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/323 Co-authored-by: Alex Co-committed-by: Alex --- project/package.json | 2 +- .../SptCommands/GiveCommand/GiveSptCommand.ts | 96 ++++++++++--------- project/src/models/enums/BaseClasses.ts | 1 + 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/project/package.json b/project/package.json index 8df289bc..6052b048 100644 --- a/project/package.json +++ b/project/package.json @@ -33,7 +33,6 @@ "dependencies": { "atomically": "~1.7", "buffer-crc32": "^1.0.0", - "closest-match": "~1.3", "date-fns": "~2.30", "date-fns-tz": "~2.0", "i18n": "~0.15", @@ -44,6 +43,7 @@ "reflect-metadata": "~0.2", "semver": "~7.6", "source-map-support": "~0.5", + "string-similarity-js": "~2.1", "tsyringe": "~4.8", "typescript": "~5.4", "winston": "~3.12", diff --git a/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts b/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts index 06624375..99d48a74 100644 --- a/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts +++ b/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts @@ -3,6 +3,7 @@ import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISpt import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { PresetHelper } from "@spt-aki/helpers/PresetHelper"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; +import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; @@ -13,7 +14,7 @@ import { LocaleService } from "@spt-aki/services/LocaleService"; import { MailSendService } from "@spt-aki/services/MailSendService"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; -import { closestMatch, distance } from "closest-match"; +import { stringSimilarity } from "string-similarity-js"; import { inject, injectable } from "tsyringe"; @injectable() @@ -28,7 +29,7 @@ export class GiveSptCommand implements ISptCommand * spt give 5 <== this is the reply when the algo isn't sure about an item */ private static commandRegex = /^spt give (((([a-z]{2,5}) )?"(.+)"|\w+) )?([0-9]+)$/; - private static maxAllowedDistance = 1.5; + private static acceptableConfidence = 0.9; protected savedCommand: Map = new Map(); @@ -125,44 +126,51 @@ export class GiveSptCommand implements ISptCommand if (isItemName) { - locale = result[4] ? result[4] : this.localeService.getDesiredGameLocale(); - if (!this.localeService.getServerSupportedLocales().includes(locale)) + try + { + locale = result[4] ? result[4] : (this.localeService.getDesiredGameLocale() ?? "en"); + if (!this.localeService.getServerSupportedLocales().includes(locale)) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + commandHandler, + `Unknown locale "${locale}". Use \"help\" for more information.`, + ); + return request.dialogId; + } + } + catch (e) { this.mailSendService.sendUserMessageToPlayer( sessionId, commandHandler, - `Unknown locale "${locale}". Use \"help\" for more information.`, + `An error occurred while trying to use localized text. Locale will be defaulted to 'en'.`, ); - return request.dialogId; + this.logger.error(e); + locale = "en"; } const localizedGlobal = this.databaseServer.getTables().locales.global[locale]; - const closestItemsMatchedByName = closestMatch( - item.toLowerCase(), - this.itemHelper.getItems().filter((i) => i._type !== "Node").filter((i) => - !this.itemFilterService.isItemBlacklisted(i._id) - ).map((i) => localizedGlobal[`${i?._id} Name`]?.toLowerCase()).filter((i) => i !== undefined), - true, - ) as string[]; + const closestItemsMatchedByName = this.itemHelper.getItems() + .filter((i) => this.isItemAllowed(i)) + .map((i) => localizedGlobal[`${i?._id} Name`]?.toLowerCase()) + .filter((i) => i !== undefined) + .map(i => ({match: stringSimilarity(item.toLocaleLowerCase(), i.toLocaleLowerCase()), itemName: i})) + .sort((a1, a2) => a2.match - a1.match); - if (closestItemsMatchedByName === undefined || closestItemsMatchedByName.length === 0) + if (closestItemsMatchedByName[0].match >= GiveSptCommand.acceptableConfidence) { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - commandHandler, - "That item could not be found. Please refine your request and try again.", - ); - return request.dialogId; + item = closestItemsMatchedByName[0].itemName; } - - if (closestItemsMatchedByName.length > 1) - { + else + { let i = 1; const slicedItems = closestItemsMatchedByName.slice(0, 10); // max 10 item names and map them - const itemList = slicedItems.map((itemName) => `${i++}. ${itemName}`).join("\n"); - this.savedCommand.set(sessionId, new SavedCommand(quantity, slicedItems, locale)); + const itemList = slicedItems.map((match) => `${i++}. ${match.itemName} (conf: ${(match.match * 100).toFixed(2)})`) + .join("\n"); + this.savedCommand.set(sessionId, new SavedCommand(quantity, slicedItems.map(i => i.itemName), locale)); this.mailSendService.sendUserMessageToPlayer( sessionId, commandHandler, @@ -170,30 +178,15 @@ export class GiveSptCommand implements ISptCommand ); return request.dialogId; } - - const dist = distance(item, closestItemsMatchedByName[0]); - if (dist > GiveSptCommand.maxAllowedDistance) - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - commandHandler, - `Found a possible match for "${item}" but uncertain. Match: "${ - closestItemsMatchedByName[0] - }". Please refine your request and try again.`, - ); - return request.dialogId; - } - // Only one available so we get that entry and use it - item = closestItemsMatchedByName[0]; } } // If item is an item name, we need to search using that item name and the locale which one we want otherwise // item is just the tplId. const tplId = isItemName - ? this.itemHelper.getItems().filter((i) => !this.itemFilterService.isItemBlacklisted(i._id)).find((i) => - this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item - )._id + ? this.itemHelper.getItems() + .filter((i) => this.isItemAllowed(i)) + .find((i) => this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item)._id : item; const checkedItem = this.itemHelper.getItem(tplId); @@ -285,4 +278,21 @@ export class GiveSptCommand implements ISptCommand this.mailSendService.sendSystemMessageToPlayer(sessionId, "SPT GIVE", itemsToSend); return request.dialogId; } + + /** + * A "simple" function that checks if an item is supposed to be given to a player or not + * @param templateItem the template item to check + * @returns true if its obtainable, false if its not + */ + protected isItemAllowed(templateItem: ITemplateItem): boolean + { + return templateItem._type !== "Node" && + !this.itemHelper.isQuestItem(templateItem._id) && + !this.itemFilterService.isItemBlacklisted(templateItem._id) && + (templateItem._props?.Prefab?.path ?? "") !== "" && + !this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.HIDEOUT_AREA_CONTAINER) && + !this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.LOOT_CONTAINER) && + !this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.RANDOM_LOOT_CONTAINER) && + !this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.MOB_CONTAINER); + } } diff --git a/project/src/models/enums/BaseClasses.ts b/project/src/models/enums/BaseClasses.ts index bd3ffa95..7fe74d52 100644 --- a/project/src/models/enums/BaseClasses.ts +++ b/project/src/models/enums/BaseClasses.ts @@ -111,4 +111,5 @@ export enum BaseClasses BARREL = "555ef6e44bdc2de9068b457e", CHARGING_HANDLE = "55818a6f4bdc2db9688b456b", COMB_MUZZLE_DEVICE = "550aa4dd4bdc2dc9348b4569 ", + HIDEOUT_AREA_CONTAINER = "63da6da4784a55176c018dba" } From 6d8311150e9bd8bd6024b703e4a7c58f9629b320 Mon Sep 17 00:00:00 2001 From: Dev Date: Sun, 5 May 2024 11:50:24 +0100 Subject: [PATCH 05/12] Made `traderOffersNeedRefreshing()` more resilent to incorrectly installed trader mods --- project/src/services/RagfairOfferService.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/project/src/services/RagfairOfferService.ts b/project/src/services/RagfairOfferService.ts index 6ac95cfb..ab8fda7d 100644 --- a/project/src/services/RagfairOfferService.ts +++ b/project/src/services/RagfairOfferService.ts @@ -168,6 +168,13 @@ export class RagfairOfferService { const trader = this.databaseServer.getTables().traders[traderID]; + if (!trader || !trader.base) + { + this.logger.error(`Trader ${traderID} lacks a base file, cannot check for refresh status`); + + return false; + } + // No value, occurs when first run, trader offers need to be added to flea if (typeof trader.base.refreshTraderRagfairOffers !== "boolean") { From c552c7ad67859dd48ed34ab554439050b75501f3 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 May 2024 11:33:18 +0000 Subject: [PATCH 06/12] Removed exception thrown in compare util and instead assume two objects dont match (!324) Co-authored-by: clodan Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/324 Co-authored-by: Alex Co-committed-by: Alex --- project/src/utils/CompareUtil.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/src/utils/CompareUtil.ts b/project/src/utils/CompareUtil.ts index 80b32053..2bd62e74 100644 --- a/project/src/utils/CompareUtil.ts +++ b/project/src/utils/CompareUtil.ts @@ -57,6 +57,7 @@ export class CompareUtil { return v1 === v2; } - throw new Error(`could not detect type match for ${typeOfv1} and ${typeOfv2}`); + + return false; } } From c33773a3181beb9400bc950b0d011b2b2422e247 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 May 2024 07:17:40 +0000 Subject: [PATCH 07/12] Added more resiliency to the give command for modders who dont want to add localization for their mods (!325) Fixed cases for modded items causing problems with Commando. Co-authored-by: clodan Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/325 Co-authored-by: Alex Co-committed-by: Alex --- .../SptCommands/GiveCommand/GiveSptCommand.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts b/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts index 99d48a74..3451d574 100644 --- a/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts +++ b/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts @@ -146,16 +146,17 @@ export class GiveSptCommand implements ISptCommand commandHandler, `An error occurred while trying to use localized text. Locale will be defaulted to 'en'.`, ); - this.logger.error(e); + this.logger.warning(e); locale = "en"; } - const localizedGlobal = this.databaseServer.getTables().locales.global[locale]; + const localizedGlobal = this.databaseServer.getTables().locales.global[locale] ?? + this.databaseServer.getTables().locales.global.en; const closestItemsMatchedByName = this.itemHelper.getItems() .filter((i) => this.isItemAllowed(i)) - .map((i) => localizedGlobal[`${i?._id} Name`]?.toLowerCase()) - .filter((i) => i !== undefined) + .map((i) => localizedGlobal[`${i?._id} Name`]?.toLowerCase() ?? i._props.Name) + .filter((i) => i !== undefined && i !== "") .map(i => ({match: stringSimilarity(item.toLocaleLowerCase(), i.toLocaleLowerCase()), itemName: i})) .sort((a1, a2) => a2.match - a1.match); @@ -186,7 +187,7 @@ export class GiveSptCommand implements ISptCommand const tplId = isItemName ? this.itemHelper.getItems() .filter((i) => this.isItemAllowed(i)) - .find((i) => this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item)._id + .find((i) => (this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() ?? i._props.Name) === item)._id : item; const checkedItem = this.itemHelper.getItem(tplId); From 17296fabfde84d506695ab89eaec329e311444d0 Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 6 May 2024 15:20:44 +0100 Subject: [PATCH 08/12] Moved reduceWeightValues from PMCLootGenerator into weightedRandomHelper and made public --- project/src/generators/PMCLootGenerator.ts | 70 ++------------------- project/src/helpers/WeightedRandomHelper.ts | 62 ++++++++++++++++++ project/src/services/RagfairPriceService.ts | 6 +- 3 files changed, 70 insertions(+), 68 deletions(-) diff --git a/project/src/generators/PMCLootGenerator.ts b/project/src/generators/PMCLootGenerator.ts index b4f1b765..e2ed90a1 100644 --- a/project/src/generators/PMCLootGenerator.ts +++ b/project/src/generators/PMCLootGenerator.ts @@ -1,6 +1,7 @@ import { inject, injectable } from "tsyringe"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; +import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { IPmcConfig } from "@spt-aki/models/spt/config/IPmcConfig"; @@ -29,6 +30,7 @@ export class PMCLootGenerator @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, + @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, ) { this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); @@ -87,7 +89,7 @@ export class PMCLootGenerator this.pocketLootPool[key] = Math.round((1 / this.pocketLootPool[key]) * highestPrice); } - this.reduceWeightValues(this.pocketLootPool); + this.weightedRandomHelper.reduceWeightValues(this.pocketLootPool); } return this.pocketLootPool; @@ -145,7 +147,7 @@ export class PMCLootGenerator this.vestLootPool[key] = Math.round((1 / this.vestLootPool[key]) * highestPrice); } - this.reduceWeightValues(this.vestLootPool); + this.weightedRandomHelper.reduceWeightValues(this.vestLootPool); } return this.vestLootPool; @@ -213,71 +215,9 @@ export class PMCLootGenerator this.backpackLootPool[key] = Math.round((1 / this.backpackLootPool[key]) * highestPrice); } - this.reduceWeightValues(this.backpackLootPool); + this.weightedRandomHelper.reduceWeightValues(this.backpackLootPool); } return this.backpackLootPool; } - - /** - * Find the greated common divisor of all weights and use it on the passed in dictionary - * @param weightedDict - */ - protected reduceWeightValues(weightedDict: Record): void - { - // No values, nothing to reduce - if (Object.keys(weightedDict).length === 0) - { - return; - } - - // Only one value, set to 1 and exit - if (Object.keys(weightedDict).length === 1) - { - const key = Object.keys(weightedDict)[0]; - weightedDict[key] = 1; - return; - } - - const weights = Object.values(weightedDict).slice(); - const commonDivisor = this.commonDivisor(weights); - - // No point in dividing by 1 - if (commonDivisor === 1) - { - return; - } - - for (const key in weightedDict) - { - if (Object.hasOwn(weightedDict, key)) - { - weightedDict[key] /= commonDivisor; - } - } - } - - protected commonDivisor(numbers: number[]): number - { - let result = numbers[0]; - for (let i = 1; i < numbers.length; i++) - { - result = this.gcd(result, numbers[i]); - } - - return result; - } - - protected gcd(a: number, b: number): number - { - let x = a; - let y = b; - while (y !== 0) - { - const temp = y; - y = x % y; - x = temp; - } - return x; - } } diff --git a/project/src/helpers/WeightedRandomHelper.ts b/project/src/helpers/WeightedRandomHelper.ts index 61049b2d..f145da59 100644 --- a/project/src/helpers/WeightedRandomHelper.ts +++ b/project/src/helpers/WeightedRandomHelper.ts @@ -92,4 +92,66 @@ export class WeightedRandomHelper } } } + + /** + * Find the greated common divisor of all weights and use it on the passed in dictionary + * @param weightedDict values to reduce + */ + public reduceWeightValues(weightedDict: Record): void + { + // No values, nothing to reduce + if (Object.keys(weightedDict).length === 0) + { + return; + } + + // Only one value, set to 1 and exit + if (Object.keys(weightedDict).length === 1) + { + const key = Object.keys(weightedDict)[0]; + weightedDict[key] = 1; + return; + } + + const weights = Object.values(weightedDict).slice(); + const commonDivisor = this.commonDivisor(weights); + + // No point in dividing by 1 + if (commonDivisor === 1) + { + return; + } + + for (const key in weightedDict) + { + if (Object.hasOwn(weightedDict, key)) + { + weightedDict[key] /= commonDivisor; + } + } + } + + protected commonDivisor(numbers: number[]): number + { + let result = numbers[0]; + for (let i = 1; i < numbers.length; i++) + { + result = this.gcd(result, numbers[i]); + } + + return result; + } + + protected gcd(a: number, b: number): number + { + let x = a; + let y = b; + while (y !== 0) + { + const temp = y; + y = x % y; + x = temp; + } + return x; + } } diff --git a/project/src/services/RagfairPriceService.ts b/project/src/services/RagfairPriceService.ts index 6c3e578b..6f1d4adb 100644 --- a/project/src/services/RagfairPriceService.ts +++ b/project/src/services/RagfairPriceService.ts @@ -253,9 +253,9 @@ export class RagfairPriceService implements OnLoad } /** - * @param itemTemplateId - * @param desiredCurrency - * @param item + * @param itemTemplateId items tpl value + * @param desiredCurrency Currency to return result in + * @param item Item object (used for weapon presets) * @param offerItems * @param isPackOffer * @returns From 9fb1d9728e72e4fddf2cbaec82c5486cf25fe607 Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 6 May 2024 15:21:35 +0100 Subject: [PATCH 09/12] Fixed PMC loot generator only using flea price not flea or handbook price. Now uses whatever is higher --- project/src/generators/PMCLootGenerator.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/project/src/generators/PMCLootGenerator.ts b/project/src/generators/PMCLootGenerator.ts index e2ed90a1..68a16950 100644 --- a/project/src/generators/PMCLootGenerator.ts +++ b/project/src/generators/PMCLootGenerator.ts @@ -23,6 +23,8 @@ export class PMCLootGenerator protected backpackLootPool: Record = {}; protected pmcConfig: IPmcConfig; + protected roubleTpl = "5449016a4bdc2d6f028b456f"; + constructor( @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @@ -76,7 +78,7 @@ export class PMCLootGenerator else { // Set price of item as its weight - const price = this.ragfairPriceService.getFleaPriceForItem(itemToAdd._id); + const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl); this.pocketLootPool[itemToAdd._id] = price; } } @@ -134,7 +136,7 @@ export class PMCLootGenerator else { // Set price of item as its weight - const price = this.ragfairPriceService.getFleaPriceForItem(itemToAdd._id); + const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl); this.vestLootPool[itemToAdd._id] = price; } } @@ -202,7 +204,7 @@ export class PMCLootGenerator else { // Set price of item as its weight - const price = this.ragfairPriceService.getFleaPriceForItem(itemToAdd._id); + const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl); this.backpackLootPool[itemToAdd._id] = price; } } From 77a5b0a4b4ccd0812814e44fecc822ddf1d93180 Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 6 May 2024 15:54:29 +0100 Subject: [PATCH 10/12] Reworked how insurance picks attachments to delete before return Now has a chance to not pick any to remove (default 10%) Now only removes attachments that are above a rouble price (default 2000) Stores attachments in a dictionary weighted by rouble price Picks random amount of attachments to remove and then picks from pool by price, removing items from pool as they're picked --- project/assets/configs/insurance.json | 4 +- .../src/controllers/InsuranceController.ts | 118 +++++++++--------- .../src/models/spt/config/IInsuranceConfig.ts | 4 + 3 files changed, 65 insertions(+), 61 deletions(-) diff --git a/project/assets/configs/insurance.json b/project/assets/configs/insurance.json index 9a57739f..145aaa34 100644 --- a/project/assets/configs/insurance.json +++ b/project/assets/configs/insurance.json @@ -17,5 +17,7 @@ "patron_in_weapon" ], "returnTimeOverrideSeconds": 0, - "runIntervalSeconds": 600 + "runIntervalSeconds": 600, + "minAttachmentRoublePriceToBeTaken": 2000, + "chanceNoAttachmentsTakenPercent": 10 } \ No newline at end of file diff --git a/project/src/controllers/InsuranceController.ts b/project/src/controllers/InsuranceController.ts index 7bb44a3e..5fa4ceb9 100644 --- a/project/src/controllers/InsuranceController.ts +++ b/project/src/controllers/InsuranceController.ts @@ -4,6 +4,7 @@ import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; +import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { IGetInsuranceCostRequestData } from "@spt-aki/models/eft/insurance/IGetInsuranceCostRequestData"; @@ -25,8 +26,9 @@ import { MailSendService } from "@spt-aki/services/MailSendService"; import { PaymentService } from "@spt-aki/services/PaymentService"; import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService"; import { HashUtil } from "@spt-aki/utils/HashUtil"; +import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; -import { RandomUtil } from "@spt-aki/utils/RandomUtil"; +import { ProbabilityObject, ProbabilityObjectArray, RandomUtil } from "@spt-aki/utils/RandomUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @injectable() @@ -39,6 +41,7 @@ export class InsuranceController @inject("WinstonLogger") protected logger: ILogger, @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("MathUtil") protected mathUtil: MathUtil, + @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("HashUtil") protected hashUtil: HashUtil, @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, @inject("TimeUtil") protected timeUtil: TimeUtil, @@ -47,6 +50,7 @@ export class InsuranceController @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("DialogueHelper") protected dialogueHelper: DialogueHelper, + @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("TraderHelper") protected traderHelper: TraderHelper, @inject("PaymentService") protected paymentService: PaymentService, @inject("InsuranceService") protected insuranceService: InsuranceService, @@ -448,83 +452,77 @@ export class InsuranceController */ protected processAttachmentByParent(attachments: Item[], traderId: string, toDelete: Set): void { - const sortedAttachments = this.sortAttachmentsByPrice(attachments); - this.logAttachmentsDetails(sortedAttachments); + // Create dict of item ids + their flea/handbook price (highest is chosen) + const weightedAttachmentByPrice = this.weightAttachmentsByPrice(attachments); - const successfulRolls = this.countSuccessfulRolls(sortedAttachments, traderId); - this.logger.debug(`Number of attachments to be deleted: ${successfulRolls}`); + // Get how many attachments we want to pull off parent + const countOfAttachmentsToRemove = this.getAttachmentCountToRemove(weightedAttachmentByPrice, traderId); - this.attachmentDeletionByValue(sortedAttachments, successfulRolls, toDelete); + // Create prob array and add all attachments with rouble price as the weight + const attachmentsProbabilityArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + for (const attachmentTpl of Object.keys(weightedAttachmentByPrice)) + { + attachmentsProbabilityArray.push( + new ProbabilityObject(attachmentTpl, weightedAttachmentByPrice[attachmentTpl]), + ); + } + + // Draw x attachments from weighted array to remove from parent, remove from pool after being picked + const attachmentIdsToRemove = attachmentsProbabilityArray.draw(countOfAttachmentsToRemove, false); + for (const attachmentId of attachmentIdsToRemove) + { + toDelete.add(attachmentId); + } + + this.logger.debug(`Number of attachments to be deleted: ${attachmentIdsToRemove.length}`); } - /** - * Sorts the attachment items by their dynamic price in descending order. - * - * @param attachments The array of attachments items. - * @returns An array of items enriched with their max price and common locale-name. - */ - protected sortAttachmentsByPrice(attachments: Item[]): EnrichedItem[] + protected weightAttachmentsByPrice(attachments: Item[]): Record { - return attachments.map((item) => ({ - ...item, - name: this.itemHelper.getItemName(item._tpl), - dynamicPrice: this.ragfairPriceService.getDynamicItemPrice(item._tpl, this.roubleTpl, item, null, false), - })).sort((a, b) => b.dynamicPrice - a.dynamicPrice); - } + const result: Record = {}; - /** - * Logs the details of each attachment item. - * - * @param attachments The array of attachment items. - */ - protected logAttachmentsDetails(attachments: EnrichedItem[]): void - { - let index = 1; + // Get a dictionary of item tpls + their rouble price for (const attachment of attachments) { - this.logger.debug(`Attachment ${index}: "${attachment.name}" - Price: ${attachment.dynamicPrice}`); - index++; + const price = this.ragfairPriceService.getDynamicItemPrice(attachment._tpl, this.roubleTpl); + result[attachment._id] = Math.round(price); } + + this.weightedRandomHelper.reduceWeightValues(result); + + return result; } /** - * Counts the number of successful rolls for the attachment items. - * - * @param attachments The array of attachment items. - * @param traderId The ID of the trader that has insured these attachments. - * @returns The number of successful rolls. + * Get count of items to remove from weapon (take into account trader + price of attachment) + * @param weightedAttachmentByPrice Dict of item Tpls and thier rouble price + * @param traderId Trader attachment insured against + * @returns Attachment count to remove */ - protected countSuccessfulRolls(attachments: Item[], traderId: string): number + protected getAttachmentCountToRemove(weightedAttachmentByPrice: Record, traderId: string): number { - const rolls = Array.from({ length: attachments.length }, () => this.rollForDelete(traderId)); - return rolls.filter(Boolean).length; - } + let removeCount = 0; - /** - * Marks the most valuable attachments for deletion based on the number of successful rolls made. - * - * @param attachments The array of attachment items. - * @param successfulRolls The number of successful rolls. - * @param toDelete The array that accumulates the IDs of the items to be deleted. - */ - protected attachmentDeletionByValue( - attachments: EnrichedItem[], - successfulRolls: number, - toDelete: Set, - ): void - { - const valuableToDelete = attachments.slice(0, successfulRolls).map(({ _id }) => _id); - - for (const attachmentsId of valuableToDelete) + if (this.randomUtil.getChance100(this.insuranceConfig.chanceNoAttachmentsTakenPercent)) { - const valuableChild = attachments.find(({ _id }) => _id === attachmentsId); - if (valuableChild) + return removeCount; + } + + for (const attachmentId of Object.keys(weightedAttachmentByPrice)) + { + // Below min price to be taken, skip + if (weightedAttachmentByPrice[attachmentId] < this.insuranceConfig.minAttachmentRoublePriceToBeTaken) { - const { name, dynamicPrice } = valuableChild; - this.logger.debug(`Marked attachment "${name}" for removal - Dyanmic Price: ${dynamicPrice}`); - toDelete.add(attachmentsId); + continue; + } + + if (this.rollForDelete(traderId)) + { + removeCount++; } } + + return removeCount; } /** @@ -588,7 +586,7 @@ export class InsuranceController } /** - * Determines whether a insured item should be removed from the player's inventory based on a random roll and + * Determines whether an insured item should be removed from the player's inventory based on a random roll and * trader-specific return chance. * * @param traderId The ID of the trader who insured the item. diff --git a/project/src/models/spt/config/IInsuranceConfig.ts b/project/src/models/spt/config/IInsuranceConfig.ts index 925280aa..40855ae1 100644 --- a/project/src/models/spt/config/IInsuranceConfig.ts +++ b/project/src/models/spt/config/IInsuranceConfig.ts @@ -15,4 +15,8 @@ export interface IInsuranceConfig extends IBaseConfig returnTimeOverrideSeconds: number; /** How often server should process insurance in seconds */ runIntervalSeconds: number; + // Lowest rouble price for an attachment to be allowed to be taken + minAttachmentRoublePriceToBeTaken: number; + // Chance out of 100% no attachments from a parent are taken + chanceNoAttachmentsTakenPercent: number; } From fa7657888028d94eb98d2d01f145d40f8f3ee5f7 Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 6 May 2024 19:33:44 +0100 Subject: [PATCH 11/12] Updated insurance tests Added logging of items attachments that are being removed Only add items with a price to weight dictionary --- .../src/controllers/InsuranceController.ts | 25 +- .../controllers/InsuranceController.test.ts | 242 +++--------------- 2 files changed, 57 insertions(+), 210 deletions(-) diff --git a/project/src/controllers/InsuranceController.ts b/project/src/controllers/InsuranceController.ts index 5fa4ceb9..572deacf 100644 --- a/project/src/controllers/InsuranceController.ts +++ b/project/src/controllers/InsuranceController.ts @@ -474,9 +474,29 @@ export class InsuranceController toDelete.add(attachmentId); } + this.logAttachmentsBeingRemoved(attachmentIdsToRemove, attachments, weightedAttachmentByPrice); + this.logger.debug(`Number of attachments to be deleted: ${attachmentIdsToRemove.length}`); } + protected logAttachmentsBeingRemoved( + attachmentIdsToRemove: string[], + attachments: Item[], + attachmentPrices: Record, + ): void + { + let index = 1; + for (const attachmentId of attachmentIdsToRemove) + { + this.logger.debug( + `Attachment ${index} Id: ${attachmentId} Tpl: ${ + attachments.find((x) => x._id === attachmentId)?._tpl + } - Price: ${attachmentPrices[attachmentId]}`, + ); + index++; + } + } + protected weightAttachmentsByPrice(attachments: Item[]): Record { const result: Record = {}; @@ -485,7 +505,10 @@ export class InsuranceController for (const attachment of attachments) { const price = this.ragfairPriceService.getDynamicItemPrice(attachment._tpl, this.roubleTpl); - result[attachment._id] = Math.round(price); + if (price) + { + result[attachment._id] = Math.round(price); + } } this.weightedRandomHelper.reduceWeightValues(result); diff --git a/project/tests/controllers/InsuranceController.test.ts b/project/tests/controllers/InsuranceController.test.ts index 7b1fc23b..c36911d7 100644 --- a/project/tests/controllers/InsuranceController.test.ts +++ b/project/tests/controllers/InsuranceController.test.ts @@ -928,7 +928,7 @@ describe("InsuranceController", () => describe("processAttachmentByParent", () => { - it("should handle sorting, rolling, and deleting attachments by calling helper methods", () => + it("should handle weighing and counting of attachments by calling helper methods", () => { const insured = insuranceFixture[0]; const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); @@ -941,49 +941,24 @@ describe("InsuranceController", () => const toDelete = new Set(); // Mock helper methods. - const mockSortAttachmentsByPrice = vi.spyOn(insuranceController, "sortAttachmentsByPrice"); - const mockCountSuccessfulRolls = vi.spyOn(insuranceController, "countSuccessfulRolls").mockReturnValue(4); - const mockAttachmentDeletionByValue = vi.spyOn(insuranceController, "attachmentDeletionByValue"); + const weightAttachmentsByPrice = vi.spyOn(insuranceController, "weightAttachmentsByPrice"); + const getAttachmentCountToRemove = vi.spyOn(insuranceController, "getAttachmentCountToRemove") + .mockReturnValue(4); + const logAttachmentsBeingRemoved = vi.spyOn(insuranceController, "logAttachmentsBeingRemoved"); // Execute the method. insuranceController.processAttachmentByParent(attachments, insured.traderId, toDelete); // Verify that helper methods are called. - expect(mockSortAttachmentsByPrice).toHaveBeenCalledWith(attachments); - expect(mockCountSuccessfulRolls).toHaveBeenCalled(); - expect(mockAttachmentDeletionByValue).toHaveBeenCalled(); - }); - - it("should log attachment details and number of attachments to be deleted", () => - { - const insured = insuranceFixture[0]; - const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); - const parentAttachmentsMap = insuranceController.populateParentAttachmentsMap( - insuranceController.hashUtil.generate(), - insured, - itemsMap, - ); - const attachments = parentAttachmentsMap.entries().next().value; - const toDelete = new Set(); - const successfulRolls = 4; - - // Mock helper methods. - const mockLogAttachmentsDetails = vi.spyOn(insuranceController, "logAttachmentsDetails"); - vi.spyOn(insuranceController, "countSuccessfulRolls").mockReturnValue(successfulRolls); - const mockLoggerDebug = vi.spyOn(insuranceController.logger, "debug").mockImplementation(vi.fn()); - - // Execute the method. - insuranceController.processAttachmentByParent(attachments, insured.traderId, toDelete); - - // Verify that the logs were called/written. - expect(mockLogAttachmentsDetails).toBeCalled(); - expect(mockLoggerDebug).toHaveBeenCalledWith(`Number of attachments to be deleted: ${successfulRolls}`); + expect(weightAttachmentsByPrice).toHaveBeenCalledWith(attachments); + expect(getAttachmentCountToRemove).toHaveBeenCalled(); + expect(logAttachmentsBeingRemoved).toHaveBeenCalled(); }); }); - describe("sortAttachmentsByPrice", () => + describe("getAttachmentCountToRemove", () => { - it("should sort the attachments array by dynamicPrice in descending order", () => + it("should handle returning a count of attachments that should be removed that is below the total attachment count", () => { const insured = insuranceFixture[0]; const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); @@ -995,20 +970,12 @@ describe("InsuranceController", () => const attachments = parentAttachmentsMap.entries().next().value; const attachmentCount = attachments.length; - // Execute the method. - const sortedAttachments = insuranceController.sortAttachmentsByPrice(attachments); + const result = insuranceController.getAttachmentCountToRemove(attachments, insured.traderId); - // Verify the length of the sorted attachments array is unchanged - expect(sortedAttachments.length).toBe(attachmentCount); - - // Verify that the attachments are sorted by dynamicPrice in descending order - for (let i = 1; i < sortedAttachments.length; i++) - { - expect(sortedAttachments[i - 1].dynamicPrice).toBeGreaterThanOrEqual(sortedAttachments[i].dynamicPrice); - } + expect(result).lessThanOrEqual(attachmentCount); }); - it("should place attachments with null dynamicPrice at the bottom of the sorted list", () => + it("should handle returning 0 when chanceNoAttachmentsTakenPercent is 100%", () => { const insured = insuranceFixture[0]; const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); @@ -1018,133 +985,36 @@ describe("InsuranceController", () => itemsMap, ); const attachments = parentAttachmentsMap.entries().next().value; + insuranceController.insuranceConfig.chanceNoAttachmentsTakenPercent = 100; - // Set the dynamicPrice of the first attachment to null. - vi.spyOn(insuranceController.ragfairPriceService, "getDynamicItemPrice").mockReturnValue(666) - .mockReturnValueOnce(null); + const result = insuranceController.getAttachmentCountToRemove(attachments, insured.traderId); - // Execute the method. - const sortedAttachments = insuranceController.sortAttachmentsByPrice(attachments); - - // Verify that the attachments with null dynamicPrice are at the bottom of the list - const nullPriceAttachments = sortedAttachments.slice(-1); - for (const attachment of nullPriceAttachments) - { - expect(attachment.dynamicPrice).toBeNull(); - } - - // Verify that the rest of the attachments are sorted by dynamicPrice in descending order - for (let i = 1; i < sortedAttachments.length - 2; i++) - { - expect(sortedAttachments[i - 1].dynamicPrice).toBeGreaterThanOrEqual(sortedAttachments[i].dynamicPrice); - } - }); - }); - - describe("logAttachmentsDetails", () => - { - it("should log details for each attachment", () => - { - const attachments = [{ _id: "item1", name: "Item 1", dynamicPrice: 100 }, { - _id: "item2", - name: "Item 2", - dynamicPrice: 200, - }]; - - // Mock the logger.debug function. - const loggerDebugSpy = vi.spyOn(insuranceController.logger, "debug"); - - // Execute the method. - insuranceController.logAttachmentsDetails(attachments); - - // Verify that logger.debug was called correctly. - expect(loggerDebugSpy).toHaveBeenCalledTimes(2); - expect(loggerDebugSpy).toHaveBeenNthCalledWith(1, "Attachment 1: \"Item 1\" - Price: 100"); - expect(loggerDebugSpy).toHaveBeenNthCalledWith(2, "Attachment 2: \"Item 2\" - Price: 200"); - }); - - it("should not log anything when there are no attachments", () => - { - const attachments = []; - - // Mock the logger.debug function. - const loggerDebugSpy = vi.spyOn(insuranceController.logger, "debug"); - - // Execute the method. - insuranceController.logAttachmentsDetails(attachments); - - // Verify that logger.debug was called correctly. - expect(loggerDebugSpy).not.toHaveBeenCalled(); - }); - }); - - describe("countSuccessfulRolls", () => - { - it("should count the number of successful rolls made in the rollForDelete method", () => - { - const insured = insuranceFixture[0]; - const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); - const parentAttachmentsMap = insuranceController.populateParentAttachmentsMap( - insuranceController.hashUtil.generate(), - insured, - itemsMap, - ); - const attachments = parentAttachmentsMap.values().next().value; - - // Mock rollForDelete to return true for the first two attachments. - const mockRollForDelete = vi.spyOn(insuranceController, "rollForDelete").mockReturnValue(false) - .mockReturnValueOnce(true).mockReturnValueOnce(true); - - // Execute the method. - const result = insuranceController.countSuccessfulRolls(attachments, insured.traderId); - - // Verify that two successful rolls were counted. - expect(mockRollForDelete).toHaveBeenCalledTimes(attachments.length); - expect(result).toBe(2); - }); - - it("should return zero if no successful rolls were made in the rollForDelete method", () => - { - const insured = insuranceFixture[0]; - const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); - const parentAttachmentsMap = insuranceController.populateParentAttachmentsMap( - insuranceController.hashUtil.generate(), - insured, - itemsMap, - ); - const attachments = parentAttachmentsMap.values().next().value; - - // Mock rollForDelete to return false. - const mockRollForDelete = vi.spyOn(insuranceController, "rollForDelete").mockReturnValue(false); - - // Execute the method. - const result = insuranceController.countSuccessfulRolls(attachments, insured.traderId); - - // Verify that zero successful rolls were counted. - expect(mockRollForDelete).toHaveBeenCalledTimes(attachments.length); expect(result).toBe(0); }); - it("should return zero if there are no attachments", () => + it("should handle returning 0 when all attachments are below configured threshold price", () => { const insured = insuranceFixture[0]; - const attachments = []; + const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); + const parentAttachmentsMap = insuranceController.populateParentAttachmentsMap( + insuranceController.hashUtil.generate(), + insured, + itemsMap, + ); + const attachments = parentAttachmentsMap.values().next().value; + insuranceController.insuranceConfig.minAttachmentRoublePriceToBeTaken = 2; + vi.spyOn(insuranceController.ragfairPriceService, "getDynamicItemPrice").mockReturnValue(1); - // Spy on rollForDelete to ensure it is not called. - const mockRollForDelete = vi.spyOn(insuranceController, "rollForDelete"); + const weightedAttachments = insuranceController.weightAttachmentsByPrice(attachments); + const result = insuranceController.getAttachmentCountToRemove(weightedAttachments, insured.traderId); - // Execute the method. - const result = insuranceController.countSuccessfulRolls(attachments, insured.traderId); - - // Verify that zero successful rolls were returned. - expect(mockRollForDelete).not.toHaveBeenCalled(); expect(result).toBe(0); }); }); - describe("attachmentDeletionByValue", () => + describe("weightAttachmentsByPrice", () => { - it("should add the correct number of attachments to the toDelete set", () => + it("Should create a dictionary of 2 items with weights of 1 for each", () => { const insured = insuranceFixture[0]; const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); @@ -1155,57 +1025,11 @@ describe("InsuranceController", () => ); const attachments = parentAttachmentsMap.values().next().value; - const successfulRolls = 2; - const toDelete = new Set(); + vi.spyOn(insuranceController.ragfairPriceService, "getDynamicItemPrice").mockReturnValue(1); - // Execute the method. - insuranceController.attachmentDeletionByValue(attachments, successfulRolls, toDelete); - - // Should add the first two valuable attachments to the toDelete set. - expect(toDelete.size).toEqual(successfulRolls); - }); - - it("should not add any attachments to toDelete if successfulRolls is zero", () => - { - const insured = insuranceFixture[0]; - const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); - const parentAttachmentsMap = insuranceController.populateParentAttachmentsMap( - insuranceController.hashUtil.generate(), - insured, - itemsMap, - ); - const attachments = parentAttachmentsMap.values().next().value; - - const successfulRolls = 0; - const toDelete = new Set(); - - // Execute the method. - insuranceController.attachmentDeletionByValue(attachments, successfulRolls, toDelete); - - // Should be empty. - expect(toDelete.size).toEqual(successfulRolls); - }); - - it("should add all attachments to toDelete if successfulRolls is greater than the number of attachments", () => - { - const insured = insuranceFixture[0]; - const itemsMap = insuranceController.itemHelper.generateItemsMap(insured.items); - const parentAttachmentsMap = insuranceController.populateParentAttachmentsMap( - insuranceController.hashUtil.generate(), - insured, - itemsMap, - ); - const attachments = parentAttachmentsMap.values().next().value; - - const successfulRolls = 999; - const toDelete = new Set(); - - // Execute the method. - insuranceController.attachmentDeletionByValue(attachments, successfulRolls, toDelete); - - // Should be empty. - expect(toDelete.size).toBeLessThan(successfulRolls); - expect(toDelete.size).toEqual(attachments.length); + const result = insuranceController.weightAttachmentsByPrice(attachments); + expect(Object.keys(result).length).toBe(2); + expect(Object.values(result)).toStrictEqual([1, 1]); }); }); From f991382d664f346d5edb8ee94eee5b46ffd1d8d4 Mon Sep 17 00:00:00 2001 From: TheSparta Date: Mon, 6 May 2024 21:53:30 +0100 Subject: [PATCH 12/12] Fixed unable to send bundle if path had spaces --- project/src/routers/serializers/BundleSerializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/src/routers/serializers/BundleSerializer.ts b/project/src/routers/serializers/BundleSerializer.ts index df8c8866..a12c51e3 100644 --- a/project/src/routers/serializers/BundleSerializer.ts +++ b/project/src/routers/serializers/BundleSerializer.ts @@ -22,7 +22,7 @@ export class BundleSerializer extends Serializer { this.logger.info(`[BUNDLE]: ${req.url}`); - const key = req.url.split("/bundle/")[1]; + const key = decodeURI(req.url.split("/bundle/")[1]); const bundle = this.bundleLoader.getBundle(key); this.httpFileUtil.sendFile(resp, `${bundle.modpath}/bundles/${bundle.filename}`);