Added functionality for Fence to resell items sold to him by PMCs, and fixed give command giving incomplete preset items and bugged ammo boxes (!317)
Fixes https://dev.sp-tarkov.com/SPT-AKI/Issues/issues/625 Co-authored-by: clodan <clodan@clodan.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/317 Co-authored-by: Alex <clodan@noreply.dev.sp-tarkov.com> Co-committed-by: Alex <clodan@noreply.dev.sp-tarkov.com>
This commit is contained in:
parent
ae0b7f83ec
commit
0502257093
@ -241,6 +241,7 @@ import { OnUpdateModService } from "@spt-aki/services/mod/onUpdate/OnUpdateModSe
|
|||||||
import { StaticRouterModService } from "@spt-aki/services/mod/staticRouter/StaticRouterModService";
|
import { StaticRouterModService } from "@spt-aki/services/mod/staticRouter/StaticRouterModService";
|
||||||
import { App } from "@spt-aki/utils/App";
|
import { App } from "@spt-aki/utils/App";
|
||||||
import { AsyncQueue } from "@spt-aki/utils/AsyncQueue";
|
import { AsyncQueue } from "@spt-aki/utils/AsyncQueue";
|
||||||
|
import { CompareUtil } from "@spt-aki/utils/CompareUtil";
|
||||||
import { DatabaseImporter } from "@spt-aki/utils/DatabaseImporter";
|
import { DatabaseImporter } from "@spt-aki/utils/DatabaseImporter";
|
||||||
import { EncodingUtil } from "@spt-aki/utils/EncodingUtil";
|
import { EncodingUtil } from "@spt-aki/utils/EncodingUtil";
|
||||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||||
@ -410,6 +411,7 @@ export class Container
|
|||||||
depContainer.register<HttpFileUtil>("HttpFileUtil", HttpFileUtil, { lifecycle: Lifecycle.Singleton });
|
depContainer.register<HttpFileUtil>("HttpFileUtil", HttpFileUtil, { lifecycle: Lifecycle.Singleton });
|
||||||
depContainer.register<ModLoadOrder>("ModLoadOrder", ModLoadOrder, { lifecycle: Lifecycle.Singleton });
|
depContainer.register<ModLoadOrder>("ModLoadOrder", ModLoadOrder, { lifecycle: Lifecycle.Singleton });
|
||||||
depContainer.register<ModTypeCheck>("ModTypeCheck", ModTypeCheck, { lifecycle: Lifecycle.Singleton });
|
depContainer.register<ModTypeCheck>("ModTypeCheck", ModTypeCheck, { lifecycle: Lifecycle.Singleton });
|
||||||
|
depContainer.register<CompareUtil>("CompareUtil", CompareUtil, { lifecycle: Lifecycle.Singleton });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static registerRouters(depContainer: DependencyContainer): void
|
private static registerRouters(depContainer: DependencyContainer): void
|
||||||
|
@ -14,6 +14,7 @@ import { ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
|||||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||||
|
import { FenceService } from "@spt-aki/services/FenceService";
|
||||||
import { ItemFilterService } from "@spt-aki/services/ItemFilterService";
|
import { ItemFilterService } from "@spt-aki/services/ItemFilterService";
|
||||||
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
|
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
|
||||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||||
@ -35,6 +36,7 @@ export class FenceBaseAssortGenerator
|
|||||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
|
@inject("FenceService") protected fenceService: FenceService,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||||
@ -123,7 +125,7 @@ export class FenceBaseAssortGenerator
|
|||||||
|
|
||||||
// Create barter scheme (price)
|
// Create barter scheme (price)
|
||||||
const barterSchemeToAdd: IBarterScheme = {
|
const barterSchemeToAdd: IBarterScheme = {
|
||||||
count: Math.round(this.getItemPrice(rootItemDb._id, itemWithChildrenToAdd)),
|
count: Math.round(this.fenceService.getItemPrice(rootItemDb._id, itemWithChildrenToAdd)),
|
||||||
_tpl: Money.ROUBLES,
|
_tpl: Money.ROUBLES,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,27 +237,6 @@ export class FenceBaseAssortGenerator
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getItemPrice(itemTpl: string, items: Item[]): number
|
|
||||||
{
|
|
||||||
return this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.AMMO_BOX)
|
|
||||||
? this.getAmmoBoxPrice(items) * this.traderConfig.fence.itemPriceMult
|
|
||||||
: this.handbookHelper.getTemplatePrice(itemTpl) * this.traderConfig.fence.itemPriceMult;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getAmmoBoxPrice(items: Item[]): number
|
|
||||||
{
|
|
||||||
let total = 0;
|
|
||||||
for (const item of items)
|
|
||||||
{
|
|
||||||
if (this.itemHelper.isOfBaseclass(item._tpl, BaseClasses.AMMO))
|
|
||||||
{
|
|
||||||
total += this.handbookHelper.getTemplatePrice(item._tpl) * (item.upd.StackObjectsCount ?? 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add soft inserts + armor plates to an armor
|
* Add soft inserts + armor plates to an armor
|
||||||
* @param armor Armor item array to add mods into
|
* @param armor Armor item array to add mods into
|
||||||
|
@ -208,7 +208,11 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
const itemsToSend: Item[] = [];
|
const itemsToSend: Item[] = [];
|
||||||
if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.WEAPON))
|
if (
|
||||||
|
this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.WEAPON)
|
||||||
|
|| this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.ARMOR)
|
||||||
|
|| this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.VEST)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
const preset = this.presetHelper.getDefaultPreset(checkedItem[1]._id);
|
const preset = this.presetHelper.getDefaultPreset(checkedItem[1]._id);
|
||||||
if (!preset)
|
if (!preset)
|
||||||
@ -220,25 +224,45 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
);
|
);
|
||||||
return request.dialogId;
|
return request.dialogId;
|
||||||
}
|
}
|
||||||
itemsToSend.push(...this.jsonUtil.clone(preset._items));
|
for (let i = 0; i < quantity; i++)
|
||||||
|
{
|
||||||
|
let items = this.jsonUtil.clone(preset._items);
|
||||||
|
items = this.itemHelper.replaceIDs(items);
|
||||||
|
itemsToSend.push(...items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.AMMO_BOX))
|
else if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.AMMO_BOX))
|
||||||
{
|
{
|
||||||
for (let i = 0; i < +quantity; i++)
|
for (let i = 0; i < quantity; i++)
|
||||||
{
|
{
|
||||||
const ammoBoxArray: Item[] = [];
|
const ammoBoxArray: Item[] = [];
|
||||||
ammoBoxArray.push({ _id: this.hashUtil.generate(), _tpl: checkedItem[1]._id });
|
ammoBoxArray.push({ _id: this.hashUtil.generate(), _tpl: checkedItem[1]._id });
|
||||||
this.itemHelper.addCartridgesToAmmoBox(ammoBoxArray, checkedItem[1]);
|
// DO NOT generate the ammo box cartridges, the mail service does it for us! :)
|
||||||
|
// this.itemHelper.addCartridgesToAmmoBox(ammoBoxArray, checkedItem[1]);
|
||||||
itemsToSend.push(...ammoBoxArray);
|
itemsToSend.push(...ammoBoxArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (checkedItem[1]._props.StackMaxSize === 1)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < quantity; i++)
|
||||||
|
{
|
||||||
|
itemsToSend.push({
|
||||||
|
_id: this.hashUtil.generate(),
|
||||||
|
_tpl: checkedItem[1]._id,
|
||||||
|
upd: this.itemHelper.generateUpdForItem(checkedItem[1]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
const item: Item = {
|
const item: Item = {
|
||||||
_id: this.hashUtil.generate(),
|
_id: this.hashUtil.generate(),
|
||||||
_tpl: checkedItem[1]._id,
|
_tpl: checkedItem[1]._id,
|
||||||
upd: { StackObjectsCount: +quantity, SpawnedInSession: true },
|
upd: this.itemHelper.generateUpdForItem(checkedItem[1]),
|
||||||
};
|
};
|
||||||
|
item.upd.StackObjectsCount = quantity;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
itemsToSend.push(...this.itemHelper.splitStack(item));
|
itemsToSend.push(...this.itemHelper.splitStack(item));
|
||||||
@ -253,6 +277,7 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
return request.dialogId;
|
return request.dialogId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Flag the items as FiR
|
// Flag the items as FiR
|
||||||
this.itemHelper.setFoundInRaid(itemsToSend);
|
this.itemHelper.setFoundInRaid(itemsToSend);
|
||||||
|
@ -3,7 +3,7 @@ import { inject, injectable } from "tsyringe";
|
|||||||
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper";
|
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper";
|
||||||
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
||||||
import { InsuredItem } from "@spt-aki/models/eft/common/tables/IBotBase";
|
import { InsuredItem } from "@spt-aki/models/eft/common/tables/IBotBase";
|
||||||
import { Item, Location, Repairable } from "@spt-aki/models/eft/common/tables/IItem";
|
import { Item, Location, Repairable, Upd } from "@spt-aki/models/eft/common/tables/IItem";
|
||||||
import { IStaticAmmoDetails } from "@spt-aki/models/eft/common/tables/ILootBase";
|
import { IStaticAmmoDetails } from "@spt-aki/models/eft/common/tables/ILootBase";
|
||||||
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||||
@ -14,6 +14,7 @@ import { ItemBaseClassService } from "@spt-aki/services/ItemBaseClassService";
|
|||||||
import { ItemFilterService } from "@spt-aki/services/ItemFilterService";
|
import { ItemFilterService } from "@spt-aki/services/ItemFilterService";
|
||||||
import { LocaleService } from "@spt-aki/services/LocaleService";
|
import { LocaleService } from "@spt-aki/services/LocaleService";
|
||||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||||
|
import { CompareUtil } from "@spt-aki/utils/CompareUtil";
|
||||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||||
import { MathUtil } from "@spt-aki/utils/MathUtil";
|
import { MathUtil } from "@spt-aki/utils/MathUtil";
|
||||||
@ -46,9 +47,137 @@ export class ItemHelper
|
|||||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
@inject("LocaleService") protected localeService: LocaleService,
|
@inject("LocaleService") protected localeService: LocaleService,
|
||||||
|
@inject("CompareUtil") protected compareUtil: CompareUtil,
|
||||||
)
|
)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will compare two items (with all its children) and see if the are equivalent.
|
||||||
|
* This method will NOT compare IDs on the items
|
||||||
|
* @param item1 first item with all its children to compare
|
||||||
|
* @param item2 second item with all its children to compare
|
||||||
|
* @param compareUpdProperties Upd properties to compare between the items
|
||||||
|
* @returns true if they are the same, false if they arent
|
||||||
|
*/
|
||||||
|
public isSameItems(item1: Item[], item2: Item[], compareUpdProperties?: Set<string>): boolean
|
||||||
|
{
|
||||||
|
if (item1.length !== item2.length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const itemOf1 of item1)
|
||||||
|
{
|
||||||
|
const itemOf2 = item2.find((i2) => i2._tpl === itemOf1._tpl);
|
||||||
|
if (itemOf2 === undefined)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.isSameItem(itemOf1, itemOf2, compareUpdProperties))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will compare two items and see if the are equivalent.
|
||||||
|
* This method will NOT compare IDs on the items
|
||||||
|
* @param item1 first item to compare
|
||||||
|
* @param item2 second item to compare
|
||||||
|
* @param compareUpdProperties Upd properties to compare between the items
|
||||||
|
* @returns true if they are the same, false if they arent
|
||||||
|
*/
|
||||||
|
public isSameItem(item1: Item, item2: Item, compareUpdProperties?: Set<string>): boolean
|
||||||
|
{
|
||||||
|
if (item1._tpl !== item2._tpl)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compareUpdProperties)
|
||||||
|
{
|
||||||
|
return Array.from(compareUpdProperties.values()).every((p) =>
|
||||||
|
this.compareUtil.recursiveCompare(item1.upd?.[p], item2.upd?.[p])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.compareUtil.recursiveCompare(item1.upd, item2.upd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to generate a Upd based on a template
|
||||||
|
* @param itemTemplate the item template to generate a Upd for
|
||||||
|
* @returns A Upd with all the default properties set
|
||||||
|
*/
|
||||||
|
public generateUpdForItem(itemTemplate: ITemplateItem): Upd
|
||||||
|
{
|
||||||
|
const itemProperties: Upd = {};
|
||||||
|
|
||||||
|
// armors, etc
|
||||||
|
if (itemTemplate._props.MaxDurability)
|
||||||
|
{
|
||||||
|
itemProperties.Repairable = {
|
||||||
|
Durability: itemTemplate._props.MaxDurability,
|
||||||
|
MaxDurability: itemTemplate._props.MaxDurability,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._props.HasHinge)
|
||||||
|
{
|
||||||
|
itemProperties.Togglable = { On: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._props.Foldable)
|
||||||
|
{
|
||||||
|
itemProperties.Foldable = { Folded: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._props.weapFireType?.length)
|
||||||
|
{
|
||||||
|
if (itemTemplate._props.weapFireType.includes("fullauto"))
|
||||||
|
{
|
||||||
|
itemProperties.FireMode = { FireMode: "fullauto" };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemProperties.FireMode = { FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._props.MaxHpResource)
|
||||||
|
{
|
||||||
|
itemProperties.MedKit = { HpResource: itemTemplate._props.MaxHpResource };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._props.MaxResource && itemTemplate._props.foodUseTime)
|
||||||
|
{
|
||||||
|
itemProperties.FoodDrink = { HpPercent: itemTemplate._props.MaxResource };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._parent === BaseClasses.FLASHLIGHT)
|
||||||
|
{
|
||||||
|
itemProperties.Light = { IsActive: false, SelectedMode: 0 };
|
||||||
|
}
|
||||||
|
else if (itemTemplate._parent === BaseClasses.TACTICAL_COMBO)
|
||||||
|
{
|
||||||
|
itemProperties.Light = { IsActive: false, SelectedMode: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate._parent === BaseClasses.NIGHTVISION)
|
||||||
|
{
|
||||||
|
itemProperties.Togglable = { On: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Togglable face shield
|
||||||
|
if (itemTemplate._props.HasHinge && itemTemplate._props.FaceShieldComponent)
|
||||||
|
{
|
||||||
|
itemProperties.Togglable = { On: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemProperties;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an id is a valid item. Valid meaning that it's an item that be stored in stash
|
* Checks if an id is a valid item. Valid meaning that it's an item that be stored in stash
|
||||||
* @param {string} tpl the template id / tpl
|
* @param {string} tpl the template id / tpl
|
||||||
|
@ -274,6 +274,14 @@ export class TradeHelper
|
|||||||
|
|
||||||
this.logger.debug(`Selling: id: ${matchingItemInInventory._id} tpl: ${matchingItemInInventory._tpl}`);
|
this.logger.debug(`Selling: id: ${matchingItemInInventory._id} tpl: ${matchingItemInInventory._tpl}`);
|
||||||
|
|
||||||
|
if (sellRequest.tid === Traders.FENCE)
|
||||||
|
{
|
||||||
|
this.fenceService.addItemsToFenceAssort(
|
||||||
|
profileWithItemsToSell.Inventory.items,
|
||||||
|
matchingItemInInventory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Also removes children
|
// Also removes children
|
||||||
this.inventoryHelper.removeItem(profileWithItemsToSell, itemToBeRemoved.id, sessionID, output);
|
this.inventoryHelper.removeItem(profileWithItemsToSell, itemToBeRemoved.id, sessionID, output);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
|||||||
import { IBarterScheme, ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
|
import { IBarterScheme, ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
|
||||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||||
|
import { Money } from "@spt-aki/models/enums/Money";
|
||||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||||
import { IItemDurabilityCurrentMax, ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
import { IItemDurabilityCurrentMax, ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
||||||
import { ICreateFenceAssortsResult } from "@spt-aki/models/spt/fence/ICreateFenceAssortsResult";
|
import { ICreateFenceAssortsResult } from "@spt-aki/models/spt/fence/ICreateFenceAssortsResult";
|
||||||
@ -46,6 +47,18 @@ export class FenceService
|
|||||||
/** Desired baseline counts - Hydrated on initial assort generation as part of generateFenceAssorts() */
|
/** Desired baseline counts - Hydrated on initial assort generation as part of generateFenceAssorts() */
|
||||||
protected desiredAssortCounts: IFenceAssortGenerationValues;
|
protected desiredAssortCounts: IFenceAssortGenerationValues;
|
||||||
|
|
||||||
|
protected fenceItemUpdCompareProperties = new Set<string>([
|
||||||
|
"Buff",
|
||||||
|
"Repairable",
|
||||||
|
"RecodableComponent",
|
||||||
|
"Key",
|
||||||
|
"Resource",
|
||||||
|
"MedKit",
|
||||||
|
"FoodDrink",
|
||||||
|
"Dogtag",
|
||||||
|
"RepairKit",
|
||||||
|
]);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("WinstonLogger") protected logger: ILogger,
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||||
@ -142,6 +155,72 @@ export class FenceService
|
|||||||
return assort;
|
return assort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds to fence assort a single item (with its children)
|
||||||
|
* @param items the items to add with all its childrens
|
||||||
|
* @param mainItem the most parent item of the array
|
||||||
|
*/
|
||||||
|
public addItemsToFenceAssort(items: Item[], mainItem: Item): void
|
||||||
|
{
|
||||||
|
// HUGE THANKS TO LACYWAY AND LEAVES FOR PROVIDING THIS SOLUTION FOR SPT TO IMPLEMENT!!
|
||||||
|
// Copy the item and its children
|
||||||
|
let clonedItems = this.jsonUtil.clone(this.itemHelper.findAndReturnChildrenAsItems(items, mainItem._id));
|
||||||
|
const root = clonedItems[0];
|
||||||
|
|
||||||
|
const cost = this.getItemPrice(root._tpl, clonedItems);
|
||||||
|
|
||||||
|
// Fix IDs
|
||||||
|
clonedItems = this.itemHelper.reparentItemAndChildren(root, clonedItems);
|
||||||
|
root.parentId = "hideout";
|
||||||
|
if (root.upd?.SpawnedInSession !== undefined)
|
||||||
|
{
|
||||||
|
root.upd.SpawnedInSession = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the items
|
||||||
|
delete root.location;
|
||||||
|
|
||||||
|
const createAssort: ICreateFenceAssortsResult = { sptItems: [], barter_scheme: {}, loyal_level_items: {} };
|
||||||
|
createAssort.barter_scheme[root._id] = [[{ count: cost, _tpl: Money.ROUBLES }]];
|
||||||
|
createAssort.sptItems.push(clonedItems);
|
||||||
|
createAssort.loyal_level_items[root._id] = 1;
|
||||||
|
|
||||||
|
this.updateFenceAssorts(createAssort, this.fenceAssort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the overall price for an item (with all its children)
|
||||||
|
* @param itemTpl the item tpl to calculate the fence price for
|
||||||
|
* @param items the items (with its children) to calculate fence price for
|
||||||
|
* @returns the fence price of the item
|
||||||
|
*/
|
||||||
|
public getItemPrice(itemTpl: string, items: Item[]): number
|
||||||
|
{
|
||||||
|
return this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.AMMO_BOX)
|
||||||
|
? this.getAmmoBoxPrice(items) * this.traderConfig.fence.itemPriceMult
|
||||||
|
: this.handbookHelper.getTemplatePrice(itemTpl) * this.traderConfig.fence.itemPriceMult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the overall price for an ammo box, where only one item is
|
||||||
|
* the ammo box itself and every other items are the bullets in that box
|
||||||
|
* @param items the ammo box (and all its children ammo items)
|
||||||
|
* @returns the price of the ammo box
|
||||||
|
*/
|
||||||
|
protected getAmmoBoxPrice(items: Item[]): number
|
||||||
|
{
|
||||||
|
let total = 0;
|
||||||
|
for (const item of items)
|
||||||
|
{
|
||||||
|
if (this.itemHelper.isOfBaseclass(item._tpl, BaseClasses.AMMO))
|
||||||
|
{
|
||||||
|
total += this.handbookHelper.getTemplatePrice(item._tpl) * (item.upd.StackObjectsCount ?? 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjust all items contained inside an assort by a multiplier
|
* Adjust all items contained inside an assort by a multiplier
|
||||||
* @param assort (clone)Assort that contains items with prices to adjust
|
* @param assort (clone)Assort that contains items with prices to adjust
|
||||||
@ -324,6 +403,18 @@ export class FenceService
|
|||||||
|
|
||||||
// Check if same type of item exists + its on list of item types to always stack
|
// Check if same type of item exists + its on list of item types to always stack
|
||||||
if (existingRootItem && this.itemInPreventDupeCategoryList(newRootItem._tpl))
|
if (existingRootItem && this.itemInPreventDupeCategoryList(newRootItem._tpl))
|
||||||
|
{
|
||||||
|
const existingFullItemTree = this.itemHelper.findAndReturnChildrenAsItems(
|
||||||
|
existingFenceAssorts.items,
|
||||||
|
existingRootItem._id,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
this.itemHelper.isSameItems(
|
||||||
|
itemWithChildren,
|
||||||
|
existingFullItemTree,
|
||||||
|
this.fenceItemUpdCompareProperties,
|
||||||
|
)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// Guard against a missing stack count
|
// Guard against a missing stack count
|
||||||
if (!existingRootItem.upd.StackObjectsCount)
|
if (!existingRootItem.upd.StackObjectsCount)
|
||||||
@ -332,11 +423,17 @@ export class FenceService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge new items count into existing, dont add new loyalty/barter data as it already exists
|
// Merge new items count into existing, dont add new loyalty/barter data as it already exists
|
||||||
existingRootItem.upd.StackObjectsCount += newRootItem.upd.StackObjectsCount;
|
existingRootItem.upd.StackObjectsCount += newRootItem?.upd?.StackObjectsCount ?? 1;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the upd doesnt exist just initialize it
|
||||||
|
if (newRootItem.upd === undefined)
|
||||||
|
{
|
||||||
|
newRootItem.upd = {};
|
||||||
|
}
|
||||||
// New assort to be added to existing assorts
|
// New assort to be added to existing assorts
|
||||||
existingFenceAssorts.items.push(...itemWithChildren);
|
existingFenceAssorts.items.push(...itemWithChildren);
|
||||||
existingFenceAssorts.barter_scheme[newRootItem._id] = newFenceAssorts.barter_scheme[newRootItem._id];
|
existingFenceAssorts.barter_scheme[newRootItem._id] = newFenceAssorts.barter_scheme[newRootItem._id];
|
||||||
@ -366,7 +463,7 @@ export class FenceService
|
|||||||
): IGenerationAssortValues
|
): IGenerationAssortValues
|
||||||
{
|
{
|
||||||
const allRootItems = assortItems.filter((item) => item.slotId === "hideout");
|
const allRootItems = assortItems.filter((item) => item.slotId === "hideout");
|
||||||
const rootPresetItems = allRootItems.filter((item) => item.upd.sptPresetId);
|
const rootPresetItems = allRootItems.filter((item) => item?.upd?.sptPresetId);
|
||||||
|
|
||||||
// Get count of weapons
|
// Get count of weapons
|
||||||
const currentWeaponPresetCount = rootPresetItems.reduce((count, item) =>
|
const currentWeaponPresetCount = rootPresetItems.reduce((count, item) =>
|
||||||
|
62
project/src/utils/CompareUtil.ts
Normal file
62
project/src/utils/CompareUtil.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { injectable } from "tsyringe";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CompareUtil
|
||||||
|
{
|
||||||
|
private static typesToCheckAgainst = new Set<string>([
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"bigint",
|
||||||
|
"symbol",
|
||||||
|
"undefined",
|
||||||
|
"null",
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function does an object comparison, equivalent to applying reflections
|
||||||
|
* and scanning for all possible properties including arrays.
|
||||||
|
* @param v1 value 1 to compare
|
||||||
|
* @param v2 value 2 to compare
|
||||||
|
* @returns true if equal, false if not
|
||||||
|
*/
|
||||||
|
public recursiveCompare(v1: any, v2: any): boolean
|
||||||
|
{
|
||||||
|
const typeOfv1 = typeof v1;
|
||||||
|
const typeOfv2 = typeof v2;
|
||||||
|
if (CompareUtil.typesToCheckAgainst.has(typeOfv1))
|
||||||
|
{
|
||||||
|
return v1 === v2;
|
||||||
|
}
|
||||||
|
if (typeOfv1 === "object" && typeOfv2 === "object")
|
||||||
|
{
|
||||||
|
if (Array.isArray(v1))
|
||||||
|
{
|
||||||
|
if (!Array.isArray(v2))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const arr1 = v1 as Array<any>;
|
||||||
|
const arr2 = v2 as Array<any>;
|
||||||
|
if (arr1.length !== arr2.length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arr1.every((vOf1) => arr2.find((vOf2) => this.recursiveCompare(vOf1, vOf2)));
|
||||||
|
}
|
||||||
|
for (const propOf1 in v1)
|
||||||
|
{
|
||||||
|
if (v2[propOf1] === undefined)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.recursiveCompare(v1[propOf1], v2[propOf1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeOfv1 === typeOfv2)
|
||||||
|
{
|
||||||
|
return v1 === v2;
|
||||||
|
}
|
||||||
|
throw new Error(`could not detect type match for ${typeOfv1} and ${typeOfv2}`);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user