diff --git a/project/src/generators/FenceBaseAssortGenerator.ts b/project/src/generators/FenceBaseAssortGenerator.ts index c10de6f1..ef7a25c9 100644 --- a/project/src/generators/FenceBaseAssortGenerator.ts +++ b/project/src/generators/FenceBaseAssortGenerator.ts @@ -2,6 +2,7 @@ import { inject, injectable } from "tsyringe"; import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper"; 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 { IBarterScheme } from "@spt-aki/models/eft/common/tables/ITrader"; @@ -14,6 +15,8 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { HashUtil } from "@spt-aki/utils/HashUtil"; +import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() export class FenceBaseAssortGenerator @@ -22,9 +25,12 @@ export class FenceBaseAssortGenerator constructor( @inject("WinstonLogger") protected logger: ILogger, + @inject("HashUtil") protected hashUtil: HashUtil, + @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("HandbookHelper") protected handbookHelper: HandbookHelper, @inject("ItemHelper") protected itemHelper: ItemHelper, + @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("ConfigServer") protected configServer: ConfigServer, @@ -39,29 +45,27 @@ export class FenceBaseAssortGenerator public generateFenceBaseAssorts(): void { const blockedSeasonalItems = this.seasonalEventService.getInactiveSeasonalEventItems(); - const baseFenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort; - const dbItems = Object.values(this.databaseServer.getTables().templates.items); - for (const item of dbItems.filter((x) => this.isValidFenceItem(x))) + for (const rootItemDb of this.itemHelper.getItems().filter((item) => this.isValidFenceItem(item))) { // Skip blacklisted items - if (this.itemFilterService.isItemBlacklisted(item._id)) + if (this.itemFilterService.isItemBlacklisted(rootItemDb._id)) { continue; } - if (!this.itemHelper.isValidItem(item._id)) + // Invalid + if (!this.itemHelper.isValidItem(rootItemDb._id)) { continue; } - // Skip items on fence ignore list + // Item base type blacklisted if (this.traderConfig.fence.blacklist.length > 0) { - if ( - this.traderConfig.fence.blacklist.includes(item._id) - || this.itemHelper.isOfBaseclasses(item._id, this.traderConfig.fence.blacklist) + if (this.traderConfig.fence.blacklist.includes(rootItemDb._id) + || this.itemHelper.isOfBaseclasses(rootItemDb._id, this.traderConfig.fence.blacklist) ) { continue; @@ -69,37 +73,200 @@ export class FenceBaseAssortGenerator } // Skip seasonal event items when not in seasonal event - if (this.traderConfig.fence.blacklistSeasonalItems && blockedSeasonalItems.includes(item._id)) + if (this.traderConfig.fence.blacklistSeasonalItems && blockedSeasonalItems.includes(rootItemDb._id)) { continue; } - // Create barter scheme object + // Create item object in array + const itemWithChildrenToAdd: Item[] = [{ + _id: this.hashUtil.generate(), + _tpl: rootItemDb._id, + parentId: "hideout", + slotId: "hideout", + upd: { StackObjectsCount: 9999999, UnlimitedCount: true }, + }]; + + // Need to add mods to armors so they dont show as red in the trade screen + let price = this.handbookHelper.getTemplatePrice(rootItemDb._id); + if (this.itemHelper.itemRequiresSoftInserts(rootItemDb._id)) + { + this.addChildrenToArmorModSlots(itemWithChildrenToAdd, rootItemDb); + price = this.getHandbookItemPriceWithChildren(itemWithChildrenToAdd); + } + + // Ensure IDs are unique + this.itemHelper.remapRootItemId(itemWithChildrenToAdd); + if (itemWithChildrenToAdd.length > 1) + { + this.itemHelper.reparentItemAndChildren(itemWithChildrenToAdd[0], itemWithChildrenToAdd); + } + + // Create barter scheme (price) const barterSchemeToAdd: IBarterScheme = { count: Math.round( - this.handbookHelper.getTemplatePrice(item._id) * this.traderConfig.fence.itemPriceMult, + this.handbookHelper.getTemplatePrice(rootItemDb._id) * this.traderConfig.fence.itemPriceMult, ), _tpl: Money.ROUBLES, }; // Add barter data to base - baseFenceAssort.barter_scheme[item._id] = [[barterSchemeToAdd]]; - - // Create item object - const itemToAdd: Item = { - _id: item._id, - _tpl: item._id, - parentId: "hideout", - slotId: "hideout", - upd: { StackObjectsCount: 9999999, UnlimitedCount: true }, - }; + baseFenceAssort.barter_scheme[itemWithChildrenToAdd[0]._id] = [[barterSchemeToAdd]]; // Add item to base - baseFenceAssort.items.push(itemToAdd); + baseFenceAssort.items.push(...itemWithChildrenToAdd); // Add loyalty data to base - baseFenceAssort.loyal_level_items[item._id] = 1; + baseFenceAssort.loyal_level_items[itemWithChildrenToAdd[0]._id] = 1; } + + // Add all default presets to base fence assort + const defaultPresets = Object.values(this.presetHelper.getDefaultPresets()); + for (const defaultPreset of defaultPresets) + { + // Skip presets we've already added + if (baseFenceAssort.items.some((item) => item.upd && item.upd.sptPresetId === defaultPreset._id)) + { + continue; + } + + // Construct preset + mods + const presetAndMods: Item[] = this.itemHelper.replaceIDs( + null, + this.jsonUtil.clone(defaultPreset._items), + ); + + for (let i = 0; i < presetAndMods.length; i++) + { + const mod = presetAndMods[i]; + + // Build root Item info + if (!("parentId" in mod)) + { + mod._id = presetAndMods[0]._id; + mod.parentId = "hideout"; + mod.slotId = "hideout"; + mod.upd = { + UnlimitedCount: false, + StackObjectsCount: 1, + BuyRestrictionCurrent: 0, + sptPresetId: defaultPreset._id, // Store preset id here so we can check it later to prevent preset dupes + }; + + // Updated root item, exit loop + break; + } + } + + const presetDbItem = this.itemHelper.getItem(presetAndMods[0]._tpl)[1]; + + // Add constructed preset to assorts + baseFenceAssort.items.push(...presetAndMods); + + // Calculate preset price + let rub = 0; + for (const it of presetAndMods) + { + rub += this.handbookHelper.getTemplatePrice(it._tpl); + } + + // Multiply weapon+mods rouble price by multipler in config + baseFenceAssort.barter_scheme[presetAndMods[0]._id] = [[]]; + baseFenceAssort.barter_scheme[presetAndMods[0]._id][0][0] = { _tpl: Money.ROUBLES, count: Math.round(rub) }; + + baseFenceAssort.loyal_level_items[presetAndMods[0]._id] = 1; + } + } + + /** + * Add soft inserts + armor plates to an armor + * @param armor Armor item array to add mods into + * @param itemDbDetails Armor items db template + */ + protected addChildrenToArmorModSlots(armor: Item[], itemDbDetails: ITemplateItem): void + { + // Armor has no mods, make no additions + const hasMods = itemDbDetails._props.Slots.length > 0; + if (!hasMods) + { + return; + } + + // Check for and add required soft inserts to armors + const requiredSlots = itemDbDetails._props.Slots.filter(slot => slot._required); + const hasRequiredSlots = requiredSlots.length > 0; + if (hasRequiredSlots) + { + for (const requiredSlot of requiredSlots) + { + const modItemDbDetails = this.itemHelper.getItem(requiredSlot._props.filters[0].Plate)[1]; + const plateTpl = requiredSlot._props.filters[0].Plate; // `Plate` property appears to be the 'default' item for slot + if (plateTpl === "") + { + // Some bsg plate properties are empty, skip mod + continue; + } + + const mod: Item = { + _id: this.hashUtil.generate(), + _tpl: plateTpl, + parentId: armor[0]._id, + slotId: requiredSlot._name, + upd: { + Repairable: { + Durability: modItemDbDetails._props.MaxDurability, + MaxDurability: modItemDbDetails._props.MaxDurability + } + } + }; + + armor.push(mod); + } + } + + // Check for and add plate items + const plateSlots = itemDbDetails._props.Slots.filter(slot => this.itemHelper.isRemovablePlateSlot(slot._name)); + if (plateSlots.length > 0) + { + for (const plateSlot of plateSlots) + { + const plateTpl = plateSlot._props.filters[0].Plate + if (!plateTpl) + { + // Bsg data lacks a default plate, skip adding mod + continue; + } + const modItemDbDetails = this.itemHelper.getItem(plateTpl)[1]; + armor.push({ + _id: this.hashUtil.generate(), + _tpl: plateSlot._props.filters[0].Plate, // `Plate` property appears to be the 'default' item for slot + parentId: armor[0]._id, + slotId: plateSlot._name, + upd: { + Repairable: { + Durability: modItemDbDetails._props.MaxDurability, + MaxDurability: modItemDbDetails._props.MaxDurability + } + } + }); + } + } + } + + /** + * Calculate and return the price of an item and its child mods + * @param itemWithChildren Item + mods to calcualte price of + * @returns price + */ + protected getHandbookItemPriceWithChildren(itemWithChildren: Item[]): number + { + let price = 0; + for (const item of itemWithChildren) + { + price += this.handbookHelper.getTemplatePrice(item._tpl); + } + + return price; } /** diff --git a/project/src/helpers/ItemHelper.ts b/project/src/helpers/ItemHelper.ts index 84ad9454..cd88dcbf 100644 --- a/project/src/helpers/ItemHelper.ts +++ b/project/src/helpers/ItemHelper.ts @@ -507,7 +507,8 @@ export class ItemHelper for (const itemFromAssort of assort) { - if (itemFromAssort.parentId === itemIdToFind && !list.find((item) => itemFromAssort._id === item._id)) + if (itemFromAssort.parentId === itemIdToFind + && !list.find((item) => itemFromAssort._id === item._id)) { list.push(itemFromAssort); list = list.concat(this.findAndReturnChildrenByAssort(itemFromAssort._id, assort)); diff --git a/project/src/services/FenceService.ts b/project/src/services/FenceService.ts index b3d713a3..41b602b6 100644 --- a/project/src/services/FenceService.ts +++ b/project/src/services/FenceService.ts @@ -114,7 +114,7 @@ export class FenceService /** * Adjust all items contained inside an assort by a multiplier - * @param assort Assort that contains items with prices to adjust + * @param assort (clone)Assort that contains items with prices to adjust * @param itemMultipler multipler to use on items * @param presetMultiplier preset multipler to use on presets */ @@ -325,12 +325,16 @@ export class FenceService itemToRemove = this.randomUtil.getArrayValue(assort.items); } - const indexOfItemToRemove = assort.items.findIndex((x) => x._id === itemToRemove._id); + const indexOfItemToRemove = assort.items.findIndex((item) => item._id === itemToRemove._id); assort.items.splice(indexOfItemToRemove, 1); // Clean up any mods if item removed was a weapon - // TODO: also check for mods attached down the item chain - assort.items = assort.items.filter((x) => x.parentId !== itemToRemove._id); + const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, itemToRemove._id); + for (const itemToDelete of itemWithChildren) + { + // Delete item from assort items array + assort.items.splice(assort.items.indexOf(itemToDelete), 1); + } delete assort.barter_scheme[itemToRemove._id]; delete assort.loyal_level_items[itemToRemove._id]; @@ -402,22 +406,50 @@ export class FenceService */ protected createAssorts(assortCount: number, assorts: ITraderAssort, loyaltyLevel: number): void { - const fenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort; - const fenceAssortIds = Object.keys(fenceAssort.loyal_level_items); + const baseFenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort; const itemTypeCounts = this.initItemLimitCounter(this.traderConfig.fence.itemTypeLimits); - this.addItemAssorts(assortCount, fenceAssortIds, assorts, fenceAssort, itemTypeCounts, loyaltyLevel); + this.addItemAssorts(assortCount, assorts, baseFenceAssort, itemTypeCounts, loyaltyLevel); // Add presets const maxPresetCount = Math.round(assortCount * (this.traderConfig.fence.maxPresetsPercent / 100)); const randomisedPresetCount = this.randomUtil.getInt(0, maxPresetCount); - const defaultPresets = this.presetHelper.getDefaultPresets(); - this.addPresets(randomisedPresetCount, defaultPresets, assorts, loyaltyLevel); + this.addPresetsToAssort(randomisedPresetCount, assorts, baseFenceAssort); + } + + protected addPresetsToAssort(desiredPresetCount: number, assorts: ITraderAssort, baseFenceAssort: ITraderAssort): void + { + let presetsAddedCount = 0; + if (desiredPresetCount <= 0) + { + return; + } + + const presetRootItems = baseFenceAssort.items.filter(item => item.upd?.sptPresetId); + while (presetsAddedCount < desiredPresetCount) + { + const randomPresetRoot = this.randomUtil.getArrayValue(presetRootItems); + const rootItemDb = this.itemHelper.getItem(randomPresetRoot._tpl)[1]; + const presetWithChildrenClone = this.jsonUtil.clone(this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssort.items, randomPresetRoot._id)); + + this.removeRandomModsOfItem(presetWithChildrenClone); + + // Need to add mods to armors so they dont show as red in the trade screen + if (this.itemHelper.itemRequiresSoftInserts(randomPresetRoot._tpl)) + { + this.randomiseArmorModDurability(presetWithChildrenClone, rootItemDb); + } + + assorts.items.push(...presetWithChildrenClone); + assorts.barter_scheme[randomPresetRoot._id] = baseFenceAssort.barter_scheme[randomPresetRoot._id]; + assorts.loyal_level_items[randomPresetRoot._id] = baseFenceAssort.loyal_level_items[randomPresetRoot._id]; + + presetsAddedCount++; + } } protected addItemAssorts( assortCount: number, - fenceAssortIds: string[], assorts: ITraderAssort, fenceAssort: ITraderAssort, itemTypeCounts: Record, @@ -425,89 +457,83 @@ export class FenceService ): void { const priceLimits = this.traderConfig.fence.itemCategoryRoublePriceLimit; + const assortRootItems = fenceAssort.items.filter(x => x.parentId === "hideout"); for (let i = 0; i < assortCount; i++) { - const itemTpl = fenceAssortIds[this.randomUtil.getInt(0, fenceAssortIds.length - 1)]; + const chosenAssortRoot = this.randomUtil.getArrayValue(assortRootItems); + if (!chosenAssortRoot) + { + this.logger.error(this.localisationService.getText("fence-unable_to_find_assort_by_id", chosenAssortRoot._id)); - const price = this.handbookHelper.getTemplatePrice(itemTpl); - const itemIsPreset = this.presetHelper.isPreset(itemTpl); + continue; + } + const desiredAssortItemAndChildrenClone = this.jsonUtil.clone(this.itemHelper.findAndReturnChildrenAsItems(fenceAssort.items, chosenAssortRoot._id)); + const itemDbDetails = this.itemHelper.getItem(chosenAssortRoot._tpl)[1]; + const itemLimitCount = itemTypeCounts[itemDbDetails._parent]; + if (itemLimitCount && itemLimitCount.current > itemLimitCount.max) + { + // Skip adding item as assort as limit reached, decrement i counter so we still get another item + i--; + continue; + } + + const itemIsPreset = this.presetHelper.isPreset(chosenAssortRoot._id); + + const price = fenceAssort.barter_scheme[chosenAssortRoot._id][0][0].count; if (price === 0 || (price === 1 && !itemIsPreset) || price === 100) { // Don't allow "special" items i--; continue; } - - // It's a normal non-preset item - if (!itemIsPreset) + + if (price > priceLimits[itemDbDetails._parent]) { - const desiredAssort = fenceAssort.items[fenceAssort.items.findIndex((i) => i._id === itemTpl)]; - if (!desiredAssort) - { - this.logger.error(this.localisationService.getText("fence-unable_to_find_assort_by_id", itemTpl)); - } - - const itemDbDetails = this.itemHelper.getItem(desiredAssort._tpl)[1]; - const itemLimitCount = itemTypeCounts[itemDbDetails._parent]; - - if (itemLimitCount && itemLimitCount.current > itemLimitCount.max) - { - // Skip adding item as assort as limit reached, decrement i counter so we still get another item - i--; - continue; - } - - if (price > priceLimits[itemDbDetails._parent]) - { - i--; - continue; - } - - // Increment count as item is being added - if (itemLimitCount) - { - itemLimitCount.current++; - } - - const itemsToPush: Item[] = []; - const rootItemToPush = this.jsonUtil.clone(desiredAssort); - this.randomiseItemUpdProperties(itemDbDetails, rootItemToPush); - itemsToPush.push(rootItemToPush); - - rootItemToPush._id = this.hashUtil.generate(); - rootItemToPush.upd.StackObjectsCount = this.getSingleItemStackCount(itemDbDetails); - rootItemToPush.upd.BuyRestrictionCurrent = 0; - rootItemToPush.upd.UnlimitedCount = false; - - // Need to add mods to armors so they dont show as red in the trade screen - if (this.itemHelper.itemRequiresSoftInserts(rootItemToPush._tpl)) - { - this.addModsToArmorModSlots(itemsToPush, itemDbDetails); - } - - assorts.items.push(...itemsToPush); - assorts.barter_scheme[rootItemToPush._id] = fenceAssort.barter_scheme[itemTpl]; - assorts.loyal_level_items[rootItemToPush._id] = loyaltyLevel; + i--; + continue; } + + // Increment count as item is being added + if (itemLimitCount) + { + itemLimitCount.current++; + } + + const rootItemBeingAdded = desiredAssortItemAndChildrenClone[0]; + this.randomiseItemUpdProperties(itemDbDetails, rootItemBeingAdded); + + rootItemBeingAdded.upd.StackObjectsCount = this.getSingleItemStackCount(itemDbDetails); + rootItemBeingAdded.upd.BuyRestrictionCurrent = 0; + rootItemBeingAdded.upd.UnlimitedCount = false; + + // Need to add mods to armors so they dont show as red in the trade screen + if (this.itemHelper.itemRequiresSoftInserts(rootItemBeingAdded._tpl)) + { + this.randomiseArmorModDurability(desiredAssortItemAndChildrenClone, itemDbDetails); + } + + assorts.items.push(...desiredAssortItemAndChildrenClone); + assorts.barter_scheme[rootItemBeingAdded._id] = fenceAssort.barter_scheme[chosenAssortRoot._id]; + assorts.loyal_level_items[rootItemBeingAdded._id] = loyaltyLevel; } } /** - * Add soft inserts + armor plates to an armor + * Adjust plate / soft insert durability values * @param armor Armor item array to add mods into * @param itemDbDetails Armor items db template */ - protected addModsToArmorModSlots(armor: Item[], itemDbDetails: ITemplateItem): void + protected randomiseArmorModDurability(armor: Item[], itemDbDetails: ITemplateItem): void { - // Armor has no mods, make no additions + // Armor has no mods, make no changes const hasMods = itemDbDetails._props.Slots.length > 0; if (!hasMods) { return; } - // Check for and add required soft inserts to armors + // Check for and adjust soft insert durability values const requiredSlots = itemDbDetails._props.Slots.filter(slot => slot._required); const hasRequiredSlots = requiredSlots.length > 0; if (hasRequiredSlots) @@ -515,7 +541,9 @@ export class FenceService for (const requiredSlot of requiredSlots) { const modItemDbDetails = this.itemHelper.getItem(requiredSlot._props.filters[0].Plate)[1]; - const durabilityValues = this.getRandomisedArmorDurabilityValues(modItemDbDetails, this.traderConfig.fence.armorMaxDurabilityPercentMinMax); + const durabilityValues = this.getRandomisedArmorDurabilityValues( + modItemDbDetails, + this.traderConfig.fence.armorMaxDurabilityPercentMinMax); const plateTpl = requiredSlot._props.filters[0].Plate; // `Plate` property appears to be the 'default' item for slot if (plateTpl === "") { @@ -523,34 +551,36 @@ export class FenceService continue; } - const mod: Item = { - _id: this.hashUtil.generate(), - _tpl: plateTpl, - parentId: armor[0]._id, - slotId: requiredSlot._name, - upd: { - Repairable: { - Durability: durabilityValues.Durability, - MaxDurability: durabilityValues.MaxDurability - } - } - }; + // Find items mod to apply dura changes to + const modItemToAdjust = armor.find(mod => mod.slotId === requiredSlot._name); + if (!modItemToAdjust.upd) + { + modItemToAdjust.upd = {} + } + + if (!modItemToAdjust.upd.Repairable) + { + modItemToAdjust.upd.Repairable = { + Durability: modItemDbDetails._props.MaxDurability, + MaxDurability: modItemDbDetails._props.MaxDurability + }; + } + modItemToAdjust.upd.Repairable.Durability = durabilityValues.Durability; + modItemToAdjust.upd.Repairable.MaxDurability = durabilityValues.MaxDurability; // 25% chance to add shots to visor when its below max durability if (this.randomUtil.getChance100(25) - && mod.parentId === BaseClasses.ARMORED_EQUIPMENT && mod.slotId === "mod_equipment_000" - && mod.upd.Repairable.Durability < modItemDbDetails._props.MaxDurability) + && modItemToAdjust.parentId === BaseClasses.ARMORED_EQUIPMENT && modItemToAdjust.slotId === "mod_equipment_000" + && modItemToAdjust.upd.Repairable.Durability < modItemDbDetails._props.MaxDurability) // Is damaged { - mod.upd.FaceShield = { + modItemToAdjust.upd.FaceShield = { Hits: this.randomUtil.getInt(1,3) } } - - armor.push(mod); } } - // Check for and add plate items + // Check for and adjust plate durability values const plateSlots = itemDbDetails._props.Slots.filter(slot => this.itemHelper.isRemovablePlateSlot(slot._name)); if (plateSlots.length > 0) { @@ -569,19 +599,27 @@ export class FenceService continue; } const modItemDbDetails = this.itemHelper.getItem(plateTpl)[1]; - const durabilityValues = this.getRandomisedArmorDurabilityValues(modItemDbDetails, this.traderConfig.fence.armorMaxDurabilityPercentMinMax); - armor.push({ - _id: this.hashUtil.generate(), - _tpl: plateSlot._props.filters[0].Plate, // `Plate` property appears to be the 'default' item for slot - parentId: armor[0]._id, - slotId: plateSlot._name, - upd: { - Repairable: { - Durability: durabilityValues.Durability, - MaxDurability: durabilityValues.MaxDurability - } - } - }); + const durabilityValues = this.getRandomisedArmorDurabilityValues( + modItemDbDetails, + this.traderConfig.fence.armorMaxDurabilityPercentMinMax); + + // Find items mod to apply dura changes to + const modItemToAdjust = armor.find(mod => mod.slotId === plateSlot._name); + if (!modItemToAdjust.upd) + { + modItemToAdjust.upd = {} + } + + if (!modItemToAdjust.upd.Repairable) + { + modItemToAdjust.upd.Repairable = { + Durability: modItemDbDetails._props.MaxDurability, + MaxDurability: modItemDbDetails._props.MaxDurability + }; + } + + modItemToAdjust.upd.Repairable.Durability = durabilityValues.Durability; + modItemToAdjust.upd.Repairable.MaxDurability = durabilityValues.MaxDurability; } } } @@ -776,10 +814,7 @@ export class FenceService // Randomise armor durability if ( - (itemDetails._parent === BaseClasses.ARMOR - || itemDetails._parent === BaseClasses.HEADWEAR - || itemDetails._parent === BaseClasses.VEST - || itemDetails._parent === BaseClasses.ARMORED_EQUIPMENT + (itemDetails._parent === BaseClasses.ARMORED_EQUIPMENT || itemDetails._parent === BaseClasses.FACECOVER || itemDetails._parent === BaseClasses.ARMOR_PLATE ) && itemDetails._props.MaxDurability > 0