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 { App } from "@spt-aki/utils/App";
|
||||
import { AsyncQueue } from "@spt-aki/utils/AsyncQueue";
|
||||
import { CompareUtil } from "@spt-aki/utils/CompareUtil";
|
||||
import { DatabaseImporter } from "@spt-aki/utils/DatabaseImporter";
|
||||
import { EncodingUtil } from "@spt-aki/utils/EncodingUtil";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
@ -410,6 +411,7 @@ export class Container
|
||||
depContainer.register<HttpFileUtil>("HttpFileUtil", HttpFileUtil, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<ModLoadOrder>("ModLoadOrder", ModLoadOrder, { 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
|
||||
|
@ -14,6 +14,7 @@ import { ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { FenceService } from "@spt-aki/services/FenceService";
|
||||
import { ItemFilterService } from "@spt-aki/services/ItemFilterService";
|
||||
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
@ -35,6 +36,7 @@ export class FenceBaseAssortGenerator
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("FenceService") protected fenceService: FenceService,
|
||||
)
|
||||
{
|
||||
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||
@ -123,7 +125,7 @@ export class FenceBaseAssortGenerator
|
||||
|
||||
// Create barter scheme (price)
|
||||
const barterSchemeToAdd: IBarterScheme = {
|
||||
count: Math.round(this.getItemPrice(rootItemDb._id, itemWithChildrenToAdd)),
|
||||
count: Math.round(this.fenceService.getItemPrice(rootItemDb._id, itemWithChildrenToAdd)),
|
||||
_tpl: Money.ROUBLES,
|
||||
};
|
||||
|
||||
@ -235,27 +237,6 @@ export class FenceBaseAssortGenerator
|
||||
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
|
||||
* @param armor Armor item array to add mods into
|
||||
|
@ -208,7 +208,11 @@ export class GiveSptCommand implements ISptCommand
|
||||
}
|
||||
|
||||
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);
|
||||
if (!preset)
|
||||
@ -220,37 +224,58 @@ export class GiveSptCommand implements ISptCommand
|
||||
);
|
||||
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))
|
||||
{
|
||||
for (let i = 0; i < +quantity; i++)
|
||||
for (let i = 0; i < quantity; i++)
|
||||
{
|
||||
const ammoBoxArray: Item[] = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const item: Item = {
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: checkedItem[1]._id,
|
||||
upd: { StackObjectsCount: +quantity, SpawnedInSession: true },
|
||||
};
|
||||
try
|
||||
if (checkedItem[1]._props.StackMaxSize === 1)
|
||||
{
|
||||
itemsToSend.push(...this.itemHelper.splitStack(item));
|
||||
for (let i = 0; i < quantity; i++)
|
||||
{
|
||||
itemsToSend.push({
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: checkedItem[1]._id,
|
||||
upd: this.itemHelper.generateUpdForItem(checkedItem[1]),
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Too many items requested. Please lower the amount and try again.",
|
||||
);
|
||||
return request.dialogId;
|
||||
const item: Item = {
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: checkedItem[1]._id,
|
||||
upd: this.itemHelper.generateUpdForItem(checkedItem[1]),
|
||||
};
|
||||
item.upd.StackObjectsCount = quantity;
|
||||
try
|
||||
{
|
||||
itemsToSend.push(...this.itemHelper.splitStack(item));
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Too many items requested. Please lower the amount and try again.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { inject, injectable } from "tsyringe";
|
||||
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper";
|
||||
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
||||
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 { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||
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 { LocaleService } from "@spt-aki/services/LocaleService";
|
||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
import { CompareUtil } from "@spt-aki/utils/CompareUtil";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { MathUtil } from "@spt-aki/utils/MathUtil";
|
||||
@ -46,9 +47,137 @@ export class ItemHelper
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@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
|
||||
* @param {string} tpl the template id / tpl
|
||||
|
@ -274,6 +274,14 @@ export class TradeHelper
|
||||
|
||||
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
|
||||
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 { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||
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 { IItemDurabilityCurrentMax, ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
||||
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() */
|
||||
protected desiredAssortCounts: IFenceAssortGenerationValues;
|
||||
|
||||
protected fenceItemUpdCompareProperties = new Set<string>([
|
||||
"Buff",
|
||||
"Repairable",
|
||||
"RecodableComponent",
|
||||
"Key",
|
||||
"Resource",
|
||||
"MedKit",
|
||||
"FoodDrink",
|
||||
"Dogtag",
|
||||
"RepairKit",
|
||||
]);
|
||||
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@ -142,6 +155,72 @@ export class FenceService
|
||||
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
|
||||
* @param assort (clone)Assort that contains items with prices to adjust
|
||||
@ -325,18 +404,36 @@ export class FenceService
|
||||
// Check if same type of item exists + its on list of item types to always stack
|
||||
if (existingRootItem && this.itemInPreventDupeCategoryList(newRootItem._tpl))
|
||||
{
|
||||
// Guard against a missing stack count
|
||||
if (!existingRootItem.upd.StackObjectsCount)
|
||||
const existingFullItemTree = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
existingFenceAssorts.items,
|
||||
existingRootItem._id,
|
||||
);
|
||||
if (
|
||||
this.itemHelper.isSameItems(
|
||||
itemWithChildren,
|
||||
existingFullItemTree,
|
||||
this.fenceItemUpdCompareProperties,
|
||||
)
|
||||
)
|
||||
{
|
||||
existingRootItem.upd.StackObjectsCount = 1;
|
||||
// Guard against a missing stack count
|
||||
if (!existingRootItem.upd.StackObjectsCount)
|
||||
{
|
||||
existingRootItem.upd.StackObjectsCount = 1;
|
||||
}
|
||||
|
||||
// Merge new items count into existing, dont add new loyalty/barter data as it already exists
|
||||
existingRootItem.upd.StackObjectsCount += newRootItem?.upd?.StackObjectsCount ?? 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Merge new items count into existing, dont add new loyalty/barter data as it already exists
|
||||
existingRootItem.upd.StackObjectsCount += newRootItem.upd.StackObjectsCount;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the upd doesnt exist just initialize it
|
||||
if (newRootItem.upd === undefined)
|
||||
{
|
||||
newRootItem.upd = {};
|
||||
}
|
||||
// New assort to be added to existing assorts
|
||||
existingFenceAssorts.items.push(...itemWithChildren);
|
||||
existingFenceAssorts.barter_scheme[newRootItem._id] = newFenceAssorts.barter_scheme[newRootItem._id];
|
||||
@ -366,7 +463,7 @@ export class FenceService
|
||||
): IGenerationAssortValues
|
||||
{
|
||||
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
|
||||
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