2023-03-03 16:23:46 +01:00
import { inject , injectable } from "tsyringe" ;
2024-05-21 19:59:04 +02:00
import { HandbookHelper } from "@spt/helpers/HandbookHelper" ;
import { IStaticAmmoDetails } from "@spt/models/eft/common/ILocation" ;
import { IPmcData } from "@spt/models/eft/common/IPmcData" ;
import { InsuredItem } from "@spt/models/eft/common/tables/IBotBase" ;
import { Item , Location , Repairable , Upd } from "@spt/models/eft/common/tables/IItem" ;
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem" ;
import { BaseClasses } from "@spt/models/enums/BaseClasses" ;
import { EquipmentSlots } from "@spt/models/enums/EquipmentSlots" ;
import { ILogger } from "@spt/models/spt/utils/ILogger" ;
import { DatabaseServer } from "@spt/servers/DatabaseServer" ;
import { ItemBaseClassService } from "@spt/services/ItemBaseClassService" ;
import { ItemFilterService } from "@spt/services/ItemFilterService" ;
import { LocaleService } from "@spt/services/LocaleService" ;
import { LocalisationService } from "@spt/services/LocalisationService" ;
import { ICloner } from "@spt/utils/cloners/ICloner" ;
import { CompareUtil } from "@spt/utils/CompareUtil" ;
import { HashUtil } from "@spt/utils/HashUtil" ;
import { JsonUtil } from "@spt/utils/JsonUtil" ;
import { MathUtil } from "@spt/utils/MathUtil" ;
import { ObjectId } from "@spt/utils/ObjectId" ;
import { ProbabilityObject , ProbabilityObjectArray , RandomUtil } from "@spt/utils/RandomUtil" ;
2023-03-03 16:23:46 +01:00
@injectable ( )
2023-12-15 15:43:05 +01:00
export class ItemHelper
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
protected readonly defaultInvalidBaseTypes : string [ ] = [
BaseClasses . LOOT_CONTAINER ,
BaseClasses . MOB_CONTAINER ,
BaseClasses . STASH ,
BaseClasses . SORTING_TABLE ,
BaseClasses . INVENTORY ,
BaseClasses . STATIONARY_CONTAINER ,
2023-11-13 17:07:59 +01:00
BaseClasses . POCKETS ,
2023-10-10 13:03:20 +02:00
] ;
2023-03-03 16:23:46 +01:00
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "HashUtil" ) protected hashUtil : HashUtil ,
@inject ( "JsonUtil" ) protected jsonUtil : JsonUtil ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "ObjectId" ) protected objectId : ObjectId ,
@inject ( "MathUtil" ) protected mathUtil : MathUtil ,
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
@inject ( "HandbookHelper" ) protected handbookHelper : HandbookHelper ,
@inject ( "ItemBaseClassService" ) protected itemBaseClassService : ItemBaseClassService ,
2023-11-04 11:49:52 +01:00
@inject ( "ItemFilterService" ) protected itemFilterService : ItemFilterService ,
2023-03-03 16:23:46 +01:00
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
2023-11-13 17:07:59 +01:00
@inject ( "LocaleService" ) protected localeService : LocaleService ,
2024-05-01 22:17:09 +02:00
@inject ( "CompareUtil" ) protected compareUtil : CompareUtil ,
2024-05-13 19:58:17 +02:00
@inject ( "RecursiveCloner" ) protected cloner : ICloner ,
2023-03-03 16:23:46 +01:00
)
{ }
2024-05-01 22:17:09 +02:00
/ * *
* 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 )
{
2024-05-17 21:32:41 +02:00
const itemOf2 = item2 . find ( ( i2 ) = > i2 . _tpl === itemOf1 . _tpl ) ;
2024-05-01 22:17:09 +02:00
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 )
{
2024-05-17 21:32:41 +02:00
return Array . from ( compareUpdProperties . values ( ) ) . every ( ( p ) = >
2024-05-08 05:57:08 +02:00
this . compareUtil . recursiveCompare ( item1 . upd ? . [ p ] , item2 . upd ? . [ p ] ) ,
2024-05-01 22:17:09 +02:00
) ;
}
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 ;
}
2023-03-03 16:23:46 +01:00
/ * *
* Checks if an id is a valid item . Valid meaning that it ' s an item that be stored in stash
2024-02-06 04:02:44 +01:00
* @param { string } tpl the template id / tpl
* @returns boolean ; true for items that may be in player possession and not quest items
2023-03-03 16:23:46 +01:00
* /
public isValidItem ( tpl : string , invalidBaseTypes : string [ ] = null ) : boolean
{
2024-02-06 04:02:44 +01:00
const baseTypes = invalidBaseTypes || this . defaultInvalidBaseTypes ;
2023-03-03 16:23:46 +01:00
const itemDetails = this . getItem ( tpl ) ;
if ( ! itemDetails [ 0 ] )
{
return false ;
}
2024-05-17 21:32:41 +02:00
return (
! itemDetails [ 1 ] . _props . QuestItem
&& itemDetails [ 1 ] . _type === "Item"
&& baseTypes . every ( ( x ) = > ! this . isOfBaseclass ( tpl , x ) )
&& this . getItemPrice ( tpl ) > 0
&& ! this . itemFilterService . isItemBlacklisted ( tpl )
) ;
2023-03-03 16:23:46 +01:00
}
/ * *
* Check if the tpl / template Id provided is a descendent of the baseclass
*
* @param { string } tpl the item template id to check
* @param { string } baseClassTpl the baseclass to check for
* @return { boolean } is the tpl a descendent ?
* /
public isOfBaseclass ( tpl : string , baseClassTpl : string ) : boolean
{
return this . itemBaseClassService . itemHasBaseClass ( tpl , [ baseClassTpl ] ) ;
}
/ * *
* Check if item has any of the supplied base classes
* @param tpl Item to check base classes of
* @param baseClassTpls base classes to check for
* @returns true if any supplied base classes match
* /
public isOfBaseclasses ( tpl : string , baseClassTpls : string [ ] ) : boolean
{
return this . itemBaseClassService . itemHasBaseClass ( tpl , baseClassTpls ) ;
}
2024-01-09 17:49:59 +01:00
/ * *
* Does the provided item have the chance to require soft armor inserts
* Only applies to helmets / vest / armors .
* Not all head gear needs them
* @param itemTpl item to check
* @returns Does item have the possibility ot need soft inserts
* /
2024-01-11 18:42:58 +01:00
public armorItemCanHoldMods ( itemTpl : string ) : boolean
2024-01-09 17:49:59 +01:00
{
2024-02-02 19:54:07 +01:00
return this . isOfBaseclasses ( itemTpl , [ BaseClasses . HEADWEAR , BaseClasses . VEST , BaseClasses . ARMOR ] ) ;
2024-01-09 17:49:59 +01:00
}
2024-03-23 12:23:29 +01:00
/ * *
* Does the provided item tpl need soft / removable inserts to function
* @param itemTpl Armor item
* @returns True if item needs some kind of insert
* /
public armorItemHasRemovableOrSoftInsertSlots ( itemTpl : string ) : boolean
{
if ( ! this . armorItemCanHoldMods ( itemTpl ) )
{
return false ;
}
2024-05-08 05:57:08 +02:00
return this . armorItemHasRemovablePlateSlots ( itemTpl ) || this . itemRequiresSoftInserts ( itemTpl ) ;
2024-03-23 12:23:29 +01:00
}
2024-02-14 12:12:20 +01:00
/ * *
* Does the pased in tpl have ability to hold removable plate items
* @param itemTpl item tpl to check for plate support
* @returns True when armor can hold plates
* /
public armorItemHasRemovablePlateSlots ( itemTpl : string ) : boolean
{
const itemTemplate = this . getItem ( itemTpl ) ;
const plateSlotIds = this . getRemovablePlateSlotIds ( ) ;
2024-05-17 21:32:41 +02:00
return itemTemplate [ 1 ] . _props . Slots . some ( ( slot ) = > plateSlotIds . includes ( slot . _name . toLowerCase ( ) ) ) ;
2024-02-14 12:12:20 +01:00
}
2024-01-11 18:42:58 +01:00
/ * *
* Does the provided item tpl require soft inserts to become a valid armor item
* @param itemTpl Item tpl to check
* @returns True if it needs armor inserts
* /
public itemRequiresSoftInserts ( itemTpl : string ) : boolean
{
// not a slot that takes soft-inserts
if ( ! this . armorItemCanHoldMods ( itemTpl ) )
{
return false ;
}
// Check is an item
const itemDbDetails = this . getItem ( itemTpl ) ;
if ( ! itemDbDetails [ 0 ] )
{
return false ;
}
// Has no slots
if ( ! ( itemDbDetails [ 1 ] . _props . Slots ? ? [ ] ) . length )
{
return false ;
}
// Check if item has slots that match soft insert name ids
2024-02-05 10:02:58 +01:00
const softInsertIds = this . getSoftInsertSlotIds ( ) ;
2024-05-17 21:32:41 +02:00
if ( itemDbDetails [ 1 ] . _props . Slots . find ( ( slot ) = > softInsertIds . includes ( slot . _name . toLowerCase ( ) ) ) )
2024-01-15 13:41:54 +01:00
{
return true ;
}
2024-01-11 18:42:58 +01:00
return false ;
}
2024-02-29 13:24:32 +01:00
/ * *
* Get all soft insert slot ids
* @returns An array of soft insert ids ( e . g . soft_armor_back , helmet_top )
* /
2024-02-04 23:41:11 +01:00
public getSoftInsertSlotIds ( ) : string [ ]
{
return [
"groin" ,
"groin_back" ,
"soft_armor_back" ,
"soft_armor_front" ,
"soft_armor_left" ,
"soft_armor_right" ,
"shoulder_l" ,
"shoulder_r" ,
"collar" ,
2024-02-05 10:02:58 +01:00
"helmet_top" ,
"helmet_back" ,
2024-02-29 13:24:32 +01:00
"helmet_eyes" ,
"helmet_jaw" ,
2024-02-05 10:02:58 +01:00
"helmet_ears" ,
2024-02-04 23:41:11 +01:00
] ;
}
2024-02-07 21:15:44 +01:00
/ * *
* Returns the items total price based on the handbook or as a fallback from the prices . json if the item is not
* found in the handbook . If the price can ' t be found at all return 0
* @param tpls item tpls to look up the price of
* @returns Total price in roubles
* /
public getItemAndChildrenPrice ( tpls : string [ ] ) : number
{
// Run getItemPrice for each tpl in tpls array, return sum
return tpls . reduce ( ( total , tpl ) = > total + this . getItemPrice ( tpl ) , 0 ) ;
}
2023-03-03 16:23:46 +01:00
/ * *
* Returns the item price based on the handbook or as a fallback from the prices . json if the item is not
* found in the handbook . If the price can ' t be found at all return 0
2023-07-20 17:04:26 +02:00
* @param tpl Item to look price up of
* @returns Price in roubles
2023-03-03 16:23:46 +01:00
* /
public getItemPrice ( tpl : string ) : number
2023-07-20 17:04:26 +02:00
{
const handbookPrice = this . getStaticItemPrice ( tpl ) ;
if ( handbookPrice >= 1 )
{
return handbookPrice ;
}
2023-11-04 11:28:33 +01:00
const dynamicPrice = this . getDynamicItemPrice ( tpl ) ;
2023-07-20 17:04:26 +02:00
if ( dynamicPrice )
{
return dynamicPrice ;
}
2023-11-07 22:13:41 +01:00
return 0 ;
2023-07-20 17:04:26 +02:00
}
2023-10-10 13:03:20 +02:00
/ * *
* Returns the item price based on the handbook or as a fallback from the prices . json if the item is not
* found in the handbook . If the price can ' t be found at all return 0
* @param tpl Item to look price up of
* @returns Price in roubles
* /
public getItemMaxPrice ( tpl : string ) : number
{
const staticPrice = this . getStaticItemPrice ( tpl ) ;
const dynamicPrice = this . getDynamicItemPrice ( tpl ) ;
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
2023-10-10 13:03:20 +02:00
return Math . max ( staticPrice , dynamicPrice ) ;
}
2023-07-20 17:04:26 +02:00
/ * *
* Get the static ( handbook ) price in roubles for an item by tpl
* @param tpl Items tpl id to look up price
* @returns Price in roubles ( 0 if not found )
* /
public getStaticItemPrice ( tpl : string ) : number
2023-03-03 16:23:46 +01:00
{
const handbookPrice = this . handbookHelper . getTemplatePrice ( tpl ) ;
2023-07-20 17:04:26 +02:00
if ( handbookPrice >= 1 )
2023-03-03 16:23:46 +01:00
{
return handbookPrice ;
}
2023-07-20 17:04:26 +02:00
return 0 ;
}
/ * *
* Get the dynamic ( flea ) price in roubles for an item by tpl
* @param tpl Items tpl id to look up price
* @returns Price in roubles ( undefined if not found )
* /
public getDynamicItemPrice ( tpl : string ) : number
{
2023-03-03 16:23:46 +01:00
const dynamicPrice = this . databaseServer . getTables ( ) . templates . prices [ tpl ] ;
if ( dynamicPrice )
{
return dynamicPrice ;
}
return 0 ;
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
}
2023-03-03 16:23:46 +01:00
2023-10-10 13:03:20 +02:00
/ * *
* Update items upd . StackObjectsCount to be 1 if its upd is missing or StackObjectsCount is undefined
* @param item Item to update
* @returns Fixed item
* /
2023-03-03 16:23:46 +01:00
public fixItemStackCount ( item : Item ) : Item
{
if ( item . upd === undefined )
{
2023-11-13 18:38:16 +01:00
item . upd = { StackObjectsCount : 1 } ;
2023-03-03 16:23:46 +01:00
}
if ( item . upd . StackObjectsCount === undefined )
{
item . upd . StackObjectsCount = 1 ;
}
return item ;
}
/ * *
* Get cloned copy of all item data from items . json
* @returns array of ITemplateItem objects
* /
public getItems ( ) : ITemplateItem [ ]
{
2024-05-13 19:58:17 +02:00
return this . cloner . clone ( Object . values ( this . databaseServer . getTables ( ) . templates . items ) ) ;
2023-03-03 16:23:46 +01:00
}
/ * *
* Gets item data from items . json
* @param tpl items template id to look up
* @returns bool - is valid + template item object as array
* /
public getItem ( tpl : string ) : [ boolean , ITemplateItem ]
{
// -> Gets item from <input: _tpl>
if ( tpl in this . databaseServer . getTables ( ) . templates . items )
{
return [ true , this . databaseServer . getTables ( ) . templates . items [ tpl ] ] ;
}
return [ false , undefined ] ;
}
2024-02-03 12:00:30 +01:00
public itemHasSlots ( itemTpl : string ) : boolean
{
return this . getItem ( itemTpl ) [ 1 ] . _props . Slots ? . length > 0 ;
}
2023-05-19 18:40:06 +02:00
public isItemInDb ( tpl : string ) : boolean
{
const itemDetails = this . getItem ( tpl ) ;
return itemDetails [ 0 ] ;
}
2024-02-06 21:44:40 +01:00
/ * *
* Calcualte the average quality of an item and its children
* @param items An offers item to process
* @returns % quality modifer between 0 and 1
* /
public getItemQualityModifierForOfferItems ( items : Item [ ] ) : number
{
2024-02-11 15:59:25 +01:00
if ( this . isOfBaseclass ( items [ 0 ] . _tpl , BaseClasses . WEAPON ) )
{
return this . getItemQualityModifier ( items [ 0 ] ) ;
}
let qualityModifier = 0 ;
2024-02-06 21:44:40 +01:00
for ( const item of items )
{
qualityModifier += this . getItemQualityModifier ( item ) ;
}
return Math . min ( qualityModifier / items . length , 1 ) ;
}
2023-03-03 16:23:46 +01:00
/ * *
* get normalized value ( 0 - 1 ) based on item condition
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
* @param item
2023-03-03 16:23:46 +01:00
* @returns number between 0 and 1
* /
public getItemQualityModifier ( item : Item ) : number
{
// Default to 100%
let result = 1 ;
if ( item . upd )
{
2024-05-08 05:57:08 +02:00
const medkit = item . upd . MedKit ? item.upd.MedKit : null ;
const repairable = item . upd . Repairable ? item.upd.Repairable : null ;
const foodDrink = item . upd . FoodDrink ? item.upd.FoodDrink : null ;
const key = item . upd . Key ? item.upd.Key : null ;
const resource = item . upd . Resource ? item.upd.Resource : null ;
const repairKit = item . upd . RepairKit ? item.upd.RepairKit : null ;
2023-03-03 16:23:46 +01:00
const itemDetails = this . getItem ( item . _tpl ) [ 1 ] ;
if ( medkit )
{
// Meds
result = medkit . HpResource / itemDetails . _props . MaxHpResource ;
}
else if ( repairable )
{
result = this . getRepairableItemQualityValue ( itemDetails , repairable , item ) ;
}
else if ( foodDrink )
{
// food & drink
result = foodDrink . HpPercent / itemDetails . _props . MaxResource ;
}
2024-02-16 22:18:06 +01:00
else if ( key && key . NumberOfUsages > 0 && itemDetails . _props . MaximumNumberOfUsage > 0 )
2023-03-03 16:23:46 +01:00
{
// keys - keys count upwards, not down like everything else
const maxNumOfUsages = itemDetails . _props . MaximumNumberOfUsage ;
result = ( maxNumOfUsages - key . NumberOfUsages ) / maxNumOfUsages ;
}
else if ( resource && resource . UnitsConsumed > 0 )
{
// Things like fuel tank
2023-10-10 13:03:20 +02:00
result = resource . Value / itemDetails . _props . MaxResource ;
2023-03-03 16:23:46 +01:00
}
else if ( repairKit )
{
// Repair kits
result = repairKit . Resource / itemDetails . _props . MaxRepairResource ;
}
if ( result === 0 )
{
// make item non-zero but still very low
result = 0.01 ;
}
2024-02-11 15:59:25 +01:00
return result ;
2023-03-03 16:23:46 +01:00
}
2024-02-11 15:59:25 +01:00
return 1 ;
2023-03-03 16:23:46 +01:00
}
/ * *
* Get a quality value based on a repairable items ( weapon / armor ) current state between current and max durability
2023-10-10 13:03:20 +02:00
* @param itemDetails Db details for item we want quality value for
* @param repairable Repairable properties
* @param item Item quality value is for
* @returns A number between 0 and 1
2023-03-03 16:23:46 +01:00
* /
protected getRepairableItemQualityValue ( itemDetails : ITemplateItem , repairable : Repairable , item : Item ) : number
{
2023-11-05 14:16:59 +01:00
// Edge case, max durability is below durability
2023-11-06 10:24:13 +01:00
if ( repairable . Durability > repairable . MaxDurability )
2023-11-05 14:16:59 +01:00
{
2023-11-13 17:07:59 +01:00
this . logger . warning (
2024-04-12 03:04:06 +02:00
` Max durability: ${ repairable . MaxDurability } for item id: ${ item . _id } was below durability: ${ repairable . Durability } , adjusting values to match ` ,
2023-11-13 17:07:59 +01:00
) ;
2023-11-05 14:16:59 +01:00
repairable . MaxDurability = repairable . Durability ;
}
2024-04-12 03:04:06 +02:00
// Attempt to get the max durability from _props. If not available, use Repairable max durability value instead.
2024-05-08 05:57:08 +02:00
const maxDurability = itemDetails . _props . MaxDurability
2023-11-13 18:29:16 +01:00
? itemDetails . _props . MaxDurability
: repairable . MaxDurability ;
2023-11-05 14:19:10 +01:00
const durability = repairable . Durability / maxDurability ;
2023-03-03 16:23:46 +01:00
2023-11-05 14:19:10 +01:00
if ( ! durability )
{
this . logger . error ( this . localisationService . getText ( "item-durability_value_invalid_use_default" , item . _tpl ) ) ;
2023-03-03 16:23:46 +01:00
2023-11-05 14:19:10 +01:00
return 1 ;
2023-03-03 16:23:46 +01:00
}
2023-11-05 14:19:10 +01:00
return Math . sqrt ( durability ) ;
2023-03-03 16:23:46 +01:00
}
/ * *
2023-04-24 13:47:19 +02:00
* Recursive function that looks at every item from parameter and gets their childrens Ids + includes parent item in results
2023-10-10 13:03:20 +02:00
* @param items Array of items ( item + possible children )
2024-01-09 00:27:18 +01:00
* @param baseItemId Parent items id
2023-03-03 16:23:46 +01:00
* @returns an array of strings
* /
2024-01-09 00:27:18 +01:00
public findAndReturnChildrenByItems ( items : Item [ ] , baseItemId : string ) : string [ ]
2023-03-03 16:23:46 +01:00
{
const list : string [ ] = [ ] ;
for ( const childitem of items )
{
2024-01-09 00:27:18 +01:00
if ( childitem . parentId === baseItemId )
2023-03-03 16:23:46 +01:00
{
list . push ( . . . this . findAndReturnChildrenByItems ( items , childitem . _id ) ) ;
}
}
2024-01-09 00:27:18 +01:00
list . push ( baseItemId ) ; // Required, push original item id onto array
2023-10-10 13:03:20 +02:00
2023-03-03 16:23:46 +01:00
return list ;
}
/ * *
* A variant of findAndReturnChildren where the output is list of item objects instead of their ids .
2024-01-09 00:27:18 +01:00
* @param items Array of items ( item + possible children )
* @param baseItemId Parent items id
2024-01-23 21:00:36 +01:00
* @param modsOnly Include only mod items , exclude items stored inside root item
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
* @returns An array of Item objects
2023-03-03 16:23:46 +01:00
* /
2024-01-23 21:00:36 +01:00
public findAndReturnChildrenAsItems ( items : Item [ ] , baseItemId : string , modsOnly = false ) : Item [ ]
2023-03-03 16:23:46 +01:00
{
const list : Item [ ] = [ ] ;
for ( const childItem of items )
{
2023-10-10 13:03:20 +02:00
// Include itself
2023-03-03 16:23:46 +01:00
if ( childItem . _id === baseItemId )
{
list . unshift ( childItem ) ;
continue ;
}
2024-01-23 21:00:36 +01:00
// Is stored in parent and disallowed
if ( modsOnly && childItem . location )
{
continue ;
}
// Items parentid matches root item AND returned items doesnt contain current child
2024-05-17 21:32:41 +02:00
if ( childItem . parentId === baseItemId && ! list . find ( ( item ) = > childItem . _id === item . _id ) )
2023-03-03 16:23:46 +01:00
{
list . push ( . . . this . findAndReturnChildrenAsItems ( items , childItem . _id ) ) ;
}
}
2023-10-10 13:03:20 +02:00
2023-03-03 16:23:46 +01:00
return list ;
}
/ * *
* Find children of the item in a given assort ( weapons parts for example , need recursive loop function )
* @param itemIdToFind Template id of item to check for
* @param assort Array of items to check in
* @returns Array of children of requested item
* /
public findAndReturnChildrenByAssort ( itemIdToFind : string , assort : Item [ ] ) : Item [ ]
{
let list : Item [ ] = [ ] ;
for ( const itemFromAssort of assort )
{
2024-05-17 21:32:41 +02:00
if ( itemFromAssort . parentId === itemIdToFind && ! list . find ( ( item ) = > itemFromAssort . _id === item . _id ) )
2023-03-03 16:23:46 +01:00
{
list . push ( itemFromAssort ) ;
list = list . concat ( this . findAndReturnChildrenByAssort ( itemFromAssort . _id , assort ) ) ;
}
}
return list ;
}
/ * *
* Check if the passed in item has buy count restrictions
* @param itemToCheck Item to check
* @returns true if it has buy restrictions
* /
public hasBuyRestrictions ( itemToCheck : Item ) : boolean
{
2023-11-13 18:31:52 +01:00
if ( itemToCheck . upd ? . BuyRestrictionCurrent !== undefined && itemToCheck . upd ? . BuyRestrictionMax !== undefined )
2023-03-03 16:23:46 +01:00
{
return true ;
}
return false ;
}
/ * *
* is the passed in template id a dog tag
* @param tpl Template id to check
* @returns true if it is a dogtag
* /
public isDogtag ( tpl : string ) : boolean
{
return tpl === BaseClasses . DOG_TAG_BEAR || tpl === BaseClasses . DOG_TAG_USEC ;
}
/ * *
* Gets the identifier for a child using slotId , locationX and locationY .
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
* @param item
2023-03-03 16:23:46 +01:00
* @returns "slotId OR slotid,locationX,locationY"
* /
public getChildId ( item : Item ) : string
{
if ( ! ( "location" in item ) )
{
return item . slotId ;
}
return ` ${ item . slotId } , ${ ( item . location as Location ) . x } , ${ ( item . location as Location ) . y } ` ;
}
/ * *
* Can the passed in item be stacked
* @param tpl item to check
* @returns true if it can be stacked
* /
public isItemTplStackable ( tpl : string ) : boolean
{
2023-11-05 13:37:17 +01:00
const item = this . databaseServer . getTables ( ) . templates . items [ tpl ] ;
if ( ! item )
{
return undefined ;
}
return item . _props . StackMaxSize > 1 ;
2023-03-03 16:23:46 +01:00
}
/ * *
2024-02-02 16:35:02 +01:00
* Split item stack if it exceeds its items StackMaxSize property into child items of passed in parent
2023-10-10 13:03:20 +02:00
* @param itemToSplit Item to split into smaller stacks
2024-02-01 12:23:32 +01:00
* @returns Array of root item + children
2023-03-03 16:23:46 +01:00
* /
2023-03-21 15:19:49 +01:00
public splitStack ( itemToSplit : Item ) : Item [ ]
2023-03-03 16:23:46 +01:00
{
2023-03-21 15:19:49 +01:00
if ( ! ( itemToSplit ? . upd ? . StackObjectsCount != null ) )
2023-03-03 16:23:46 +01:00
{
2023-03-21 15:19:49 +01:00
return [ itemToSplit ] ;
2023-03-03 16:23:46 +01:00
}
2024-02-01 12:23:32 +01:00
const maxStackSize = this . getItem ( itemToSplit . _tpl ) [ 1 ] . _props . StackMaxSize ;
2023-03-21 15:19:49 +01:00
let remainingCount = itemToSplit . upd . StackObjectsCount ;
2024-02-01 12:23:32 +01:00
const rootAndChildren : Item [ ] = [ ] ;
2023-03-03 16:23:46 +01:00
// If the current count is already equal or less than the max
2024-02-01 12:23:32 +01:00
// return the item as is.
2023-03-21 15:19:49 +01:00
if ( remainingCount <= maxStackSize )
2023-03-03 16:23:46 +01:00
{
2024-05-13 19:58:17 +02:00
rootAndChildren . push ( this . cloner . clone ( itemToSplit ) ) ;
2023-03-21 15:19:49 +01:00
2024-02-01 12:23:32 +01:00
return rootAndChildren ;
2023-03-03 16:23:46 +01:00
}
2023-03-21 15:19:49 +01:00
while ( remainingCount )
2023-03-03 16:23:46 +01:00
{
2023-03-21 15:19:49 +01:00
const amount = Math . min ( remainingCount , maxStackSize ) ;
2024-05-13 19:58:17 +02:00
const newStackClone = this . cloner . clone ( itemToSplit ) ;
2023-03-03 16:23:46 +01:00
2024-02-05 15:43:46 +01:00
newStackClone . _id = this . hashUtil . generate ( ) ;
newStackClone . upd . StackObjectsCount = amount ;
2023-03-21 15:19:49 +01:00
remainingCount -= amount ;
2024-02-05 15:43:46 +01:00
rootAndChildren . push ( newStackClone ) ;
2023-03-03 16:23:46 +01:00
}
2024-02-01 12:23:32 +01:00
return rootAndChildren ;
2023-03-03 16:23:46 +01:00
}
2024-02-02 16:35:02 +01:00
/ * *
* Turn items like money into separate stacks that adhere to max stack size
* @param itemToSplit Item to split into smaller stacks
2024-02-02 19:54:07 +01:00
* @returns
2024-02-02 16:35:02 +01:00
* /
public splitStackIntoSeparateItems ( itemToSplit : Item ) : Item [ ] [ ]
{
const itemTemplate = this . getItem ( itemToSplit . _tpl ) [ 1 ] ;
const itemMaxStackSize = itemTemplate . _props . StackMaxSize ? ? 1 ;
// item already within bounds of stack size, return it
if ( itemToSplit . upd ? . StackObjectsCount <= itemMaxStackSize )
{
return [ [ itemToSplit ] ] ;
}
// Split items stack into chunks
const result : Item [ ] [ ] = [ ] ;
let remainingCount = itemToSplit . upd . StackObjectsCount ;
while ( remainingCount )
{
const amount = Math . min ( remainingCount , itemMaxStackSize ) ;
2024-05-13 19:58:17 +02:00
const newItemClone = this . cloner . clone ( itemToSplit ) ;
2024-02-02 16:35:02 +01:00
2024-02-05 15:43:46 +01:00
newItemClone . _id = this . hashUtil . generate ( ) ;
newItemClone . upd . StackObjectsCount = amount ;
2024-02-02 16:35:02 +01:00
remainingCount -= amount ;
2024-02-05 15:43:46 +01:00
result . push ( [ newItemClone ] ) ;
2024-02-02 16:35:02 +01:00
}
return result ;
}
2023-03-03 16:23:46 +01:00
/ * *
2023-10-10 13:03:20 +02:00
* Find Barter items from array of items
2023-03-21 15:19:49 +01:00
* @param { string } by tpl or id
2024-01-16 19:25:03 +01:00
* @param { Item [ ] } itemsToSearch Array of items to iterate over
* @param { string } desiredBarterItemIds
2023-03-03 16:23:46 +01:00
* @returns Array of Item objects
* /
2024-01-16 19:25:03 +01:00
public findBarterItems ( by : "tpl" | "id" , itemsToSearch : Item [ ] , desiredBarterItemIds : string | string [ ] ) : Item [ ]
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
{
2024-01-16 19:25:03 +01:00
// Find required items to take after buying (handles multiple items)
2024-05-17 21:32:41 +02:00
const desiredBarterIds
= typeof desiredBarterItemIds === "string" ? [ desiredBarterItemIds ] : desiredBarterItemIds ;
2023-03-03 16:23:46 +01:00
2024-01-16 19:25:03 +01:00
const matchingItems : Item [ ] = [ ] ;
for ( const barterId of desiredBarterIds )
2023-03-03 16:23:46 +01:00
{
2024-01-16 19:25:03 +01:00
const filterResult = itemsToSearch . filter ( ( item ) = >
2023-03-03 16:23:46 +01:00
{
2024-05-08 05:57:08 +02:00
return by === "tpl" ? item . _tpl === barterId : item._id === barterId ;
2023-03-03 16:23:46 +01:00
} ) ;
2024-01-19 23:49:31 +01:00
matchingItems . push ( . . . filterResult ) ;
2023-03-21 15:19:49 +01:00
}
2024-01-16 19:25:03 +01:00
if ( matchingItems . length === 0 )
2023-03-21 15:19:49 +01:00
{
2024-01-16 19:25:03 +01:00
this . logger . warning ( ` No items found for barter Id: ${ desiredBarterIds } ` ) ;
2023-03-03 16:23:46 +01:00
}
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
2024-01-16 19:25:03 +01:00
return matchingItems ;
2023-03-03 16:23:46 +01:00
}
/ * *
2024-02-06 04:22:03 +01:00
* Regenerate all GUIDs with new IDs , for the exception of special item types ( e . g . quest , sorting table , etc . ) This
* function will not mutate the original items array , but will return a new array with new GUIDs .
*
* @param originalItems Items to adjust the IDs of
2023-07-13 15:20:31 +02:00
* @param pmcData Player profile
2024-02-06 04:22:03 +01:00
* @param insuredItems Insured items that should not have their IDs replaced
* @param fastPanel Quick slot panel
2023-07-13 18:32:50 +02:00
* @returns Item [ ]
2023-03-03 16:23:46 +01:00
* /
2024-02-06 04:22:03 +01:00
public replaceIDs (
originalItems : Item [ ] ,
pmcData : IPmcData | null = null ,
insuredItems : InsuredItem [ ] | null = null ,
fastPanel = null ,
) : Item [ ]
2023-03-03 16:23:46 +01:00
{
2024-05-13 19:58:17 +02:00
let items = this . cloner . clone ( originalItems ) ; // Deep-clone the items to avoid mutation.
2023-03-03 16:23:46 +01:00
let serialisedInventory = this . jsonUtil . serialize ( items ) ;
for ( const item of items )
{
if ( pmcData !== null )
{
2024-02-06 04:22:03 +01:00
// Insured items should not be renamed. Only works for PMCs.
2024-05-17 21:32:41 +02:00
if ( insuredItems ? . find ( ( insuredItem ) = > insuredItem . itemId === item . _id ) )
2023-03-03 16:23:46 +01:00
{
continue ;
}
2024-02-06 04:22:03 +01:00
// Do not replace the IDs of specific types of items.
2023-11-13 17:07:59 +01:00
if (
2023-11-13 18:29:16 +01:00
item . _id === pmcData . Inventory . equipment
|| item . _id === pmcData . Inventory . questRaidItems
|| item . _id === pmcData . Inventory . questStashItems
|| item . _id === pmcData . Inventory . sortingTable
|| item . _id === pmcData . Inventory . stash
2023-11-13 17:07:59 +01:00
)
2023-03-03 16:23:46 +01:00
{
continue ;
}
}
2024-02-06 04:22:03 +01:00
// Replace the ID of the item in the serialised inventory using a regular expression.
2023-03-03 16:23:46 +01:00
const oldId = item . _id ;
const newId = this . hashUtil . generate ( ) ;
serialisedInventory = serialisedInventory . replace ( new RegExp ( oldId , "g" ) , newId ) ;
// Also replace in quick slot if the old ID exists.
if ( fastPanel !== null )
{
for ( const itemSlot in fastPanel )
{
if ( fastPanel [ itemSlot ] === oldId )
{
fastPanel [ itemSlot ] = fastPanel [ itemSlot ] . replace ( new RegExp ( oldId , "g" ) , newId ) ;
}
}
}
}
items = this . jsonUtil . deserialize ( serialisedInventory ) ;
// fix duplicate id's
const dupes : Record < string , number > = { } ;
const newParents : Record < string , Item [ ] > = { } ;
const childrenMapping = { } ;
const oldToNewIds : Record < string , string [ ] > = { } ;
// Finding duplicate IDs involves scanning the item three times.
// First scan - Check which ids are duplicated.
// Second scan - Map parents to items.
// Third scan - Resolve IDs.
for ( const item of items )
{
dupes [ item . _id ] = ( dupes [ item . _id ] || 0 ) + 1 ;
}
for ( const item of items )
{
// register the parents
if ( dupes [ item . _id ] > 1 )
{
const newId = this . hashUtil . generate ( ) ;
newParents [ item . parentId ] = newParents [ item . parentId ] || [ ] ;
newParents [ item . parentId ] . push ( item ) ;
oldToNewIds [ item . _id ] = oldToNewIds [ item . _id ] || [ ] ;
oldToNewIds [ item . _id ] . push ( newId ) ;
}
}
for ( const item of items )
{
if ( dupes [ item . _id ] > 1 )
{
const oldId = item . _id ;
const newId = oldToNewIds [ oldId ] . splice ( 0 , 1 ) [ 0 ] ;
item . _id = newId ;
// Extract one of the children that's also duplicated.
if ( oldId in newParents && newParents [ oldId ] . length > 0 )
{
childrenMapping [ newId ] = { } ;
for ( const childIndex in newParents [ oldId ] )
{
// Make sure we haven't already assigned another duplicate child of
// same slot and location to this parent.
const childId = this . getChildId ( newParents [ oldId ] [ childIndex ] ) ;
if ( ! ( childId in childrenMapping [ newId ] ) )
{
childrenMapping [ newId ] [ childId ] = 1 ;
newParents [ oldId ] [ childIndex ] . parentId = newId ;
// Some very fucking sketchy stuff on this childIndex
// No clue wth was that childIndex supposed to be, but its not
newParents [ oldId ] . splice ( Number . parseInt ( childIndex ) , 1 ) ;
}
}
}
}
}
return items ;
}
2024-03-07 09:43:15 +01:00
/ * *
* Mark the passed in array of items as found in raid .
* Modifies passed in items
* @param items The list of items to mark as FiR
* /
public setFoundInRaid ( items : Item [ ] ) : void
{
for ( const item of items )
{
if ( ! item . upd )
{
item . upd = { } ;
}
item . upd . SpawnedInSession = true ;
}
}
2023-03-03 16:23:46 +01:00
/ * *
* WARNING , SLOW . Recursively loop down through an items hierarchy to see if any of the ids match the supplied list , return true if any do
2023-03-03 18:53:28 +01:00
* @param { string } tpl Items tpl to check parents of
* @param { Array } tplsToCheck Tpl values to check if parents of item match
* @returns boolean Match found
2023-03-03 16:23:46 +01:00
* /
public doesItemOrParentsIdMatch ( tpl : string , tplsToCheck : string [ ] ) : boolean
{
const itemDetails = this . getItem ( tpl ) ;
const itemExists = itemDetails [ 0 ] ;
const item = itemDetails [ 1 ] ;
// not an item, drop out
if ( ! itemExists )
{
return false ;
}
// no parent to check
if ( ! item . _parent )
{
return false ;
}
// Does templateId match any values in tplsToCheck array
if ( tplsToCheck . includes ( item . _id ) )
{
return true ;
}
// Does the items parent type exist in tplsToCheck array
if ( tplsToCheck . includes ( item . _parent ) )
{
return true ;
}
// check items parent with same method
return this . doesItemOrParentsIdMatch ( item . _parent , tplsToCheck ) ;
}
/ * *
2023-03-03 18:53:28 +01:00
* Check if item is quest item
* @param tpl Items tpl to check quest status of
* @returns true if item is flagged as quest item
2023-03-03 16:23:46 +01:00
* /
public isQuestItem ( tpl : string ) : boolean
{
const itemDetails = this . getItem ( tpl ) ;
if ( itemDetails [ 0 ] && itemDetails [ 1 ] . _props . QuestItem )
{
return true ;
}
return false ;
}
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
/ * *
* Checks to see if the item is * actually * moddable in - raid . Checks include the items existence in the database , the
* parent items existence in the database , the existence ( and value ) of the items RaidModdable property , and that
* the parents slot - required property exists , matches that of the item , and it ' s value .
*
* Note : this function does not preform any checks to see if the item and parent are * actually * related .
*
* @param item The item to be checked
* @param parent The parent of the item to be checked
* @returns True if the item is actually moddable , false if it is not , and null if the check cannot be performed .
* /
public isRaidModdable ( item : Item , parent : Item ) : boolean | null
{
// This check requires the item to have the slotId property populated.
if ( ! item . slotId )
{
return null ;
}
const itemTemplate = this . getItem ( item . _tpl ) ;
const parentTemplate = this . getItem ( parent . _tpl ) ;
// Check for RaidModdable property on the item template.
let isNotRaidModdable = false ;
if ( itemTemplate [ 0 ] )
{
isNotRaidModdable = itemTemplate [ 1 ] ? . _props ? . RaidModdable === false ;
}
// Check to see if the slot that the item is attached to is marked as required in the parent item's template.
let isRequiredSlot = false ;
if ( parentTemplate [ 0 ] && parentTemplate [ 1 ] ? . _props ? . Slots )
{
2024-05-17 21:32:41 +02:00
isRequiredSlot = parentTemplate [ 1 ] . _props . Slots . some (
( slot ) = > slot . _name === item . slotId && slot . _required ,
2023-11-13 17:07:59 +01:00
) ;
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
}
return itemTemplate [ 0 ] && parentTemplate [ 0 ] && ! ( isNotRaidModdable || isRequiredSlot ) ;
}
/ * *
* Retrieves the main parent item for a given attachment item .
*
* This method traverses up the hierarchy of items starting from a given ` itemId ` , until it finds the main parent
* item that is not an attached attachment itself . In other words , if you pass it an item id of a suppressor , it
* will traverse up the muzzle brake , barrel , upper receiver , and return the gun that the suppressor is ultimately
* attached to , even if that gun is located within multiple containers .
*
* It ' s important to note that traversal is expensive , so this method requires that you pass it a Map of the items
* to traverse , where the keys are the item IDs and the values are the corresponding Item objects . This alleviates
* some of the performance concerns , as it allows for quick lookups of items by ID .
*
* @param itemId - The unique identifier of the item for which to find the main parent .
* @param itemsMap - A Map containing item IDs mapped to their corresponding Item objects for quick lookup .
* @returns The Item object representing the top - most parent of the given item , or ` null ` if no such parent exists .
* /
public getAttachmentMainParent ( itemId : string , itemsMap : Map < string , Item > ) : Item | null
{
let currentItem = itemsMap . get ( itemId ) ;
while ( currentItem && this . isAttachmentAttached ( currentItem ) )
{
currentItem = itemsMap . get ( currentItem . parentId ) ;
2023-11-02 06:47:28 +01:00
if ( ! currentItem )
{
return null ;
}
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
}
return currentItem ;
}
/ * *
* Determines if an item is an attachment that is currently attached to it ' s parent item .
*
* @param item The item to check .
* @returns true if the item is attached attachment , otherwise false .
* /
public isAttachmentAttached ( item : Item ) : boolean
{
2024-05-17 21:32:41 +02:00
const equipmentSlots = Object . values ( EquipmentSlots ) . map ( ( value ) = > value as string ) ;
2024-02-08 21:56:45 +01:00
2024-05-17 21:32:41 +02:00
return ! (
[ "hideout" , "main" ] . includes ( item . slotId )
|| equipmentSlots . includes ( item . slotId )
|| ! Number . isNaN ( Number ( item . slotId ) )
) ;
2024-02-08 21:56:45 +01:00
}
/ * *
* Retrieves the equipment parent item for a given item .
*
* This method traverses up the hierarchy of items starting from a given ` itemId ` , until it finds the equipment
* parent item . In other words , if you pass it an item id of a suppressor , it will traverse up the muzzle brake ,
* barrel , upper receiver , gun , nested backpack , and finally return the backpack Item that is equipped .
*
* It ' s important to note that traversal is expensive , so this method requires that you pass it a Map of the items
* to traverse , where the keys are the item IDs and the values are the corresponding Item objects . This alleviates
* some of the performance concerns , as it allows for quick lookups of items by ID .
*
* @param itemId - The unique identifier of the item for which to find the equipment parent .
* @param itemsMap - A Map containing item IDs mapped to their corresponding Item objects for quick lookup .
* @returns The Item object representing the equipment parent of the given item , or ` null ` if no such parent exists .
* /
public getEquipmentParent ( itemId : string , itemsMap : Map < string , Item > ) : Item | null
{
let currentItem = itemsMap . get ( itemId ) ;
2024-05-17 21:32:41 +02:00
const equipmentSlots = Object . values ( EquipmentSlots ) . map ( ( value ) = > value as string ) ;
2024-02-08 21:56:45 +01:00
while ( currentItem && ! equipmentSlots . includes ( currentItem . slotId ) )
{
currentItem = itemsMap . get ( currentItem . parentId ) ;
if ( ! currentItem )
{
return null ;
}
}
return currentItem ;
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
}
2023-03-03 16:23:46 +01:00
/ * *
* Get the inventory size of an item
2023-03-03 18:53:28 +01:00
* @param items Item with children
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
* @param rootItemId
2023-03-03 16:23:46 +01:00
* @returns ItemSize object ( width and height )
* /
public getItemSize ( items : Item [ ] , rootItemId : string ) : ItemHelper . ItemSize
{
2024-05-17 21:32:41 +02:00
const rootTemplate = this . getItem ( items . filter ( ( x ) = > x . _id === rootItemId ) [ 0 ] . _tpl ) [ 1 ] ;
2023-03-03 16:23:46 +01:00
const width = rootTemplate . _props . Width ;
const height = rootTemplate . _props . Height ;
let sizeUp = 0 ;
let sizeDown = 0 ;
let sizeLeft = 0 ;
let sizeRight = 0 ;
let forcedUp = 0 ;
let forcedDown = 0 ;
let forcedLeft = 0 ;
let forcedRight = 0 ;
const children = this . findAndReturnChildrenAsItems ( items , rootItemId ) ;
for ( const ci of children )
{
const itemTemplate = this . getItem ( ci . _tpl ) [ 1 ] ;
// Calculating child ExtraSize
if ( itemTemplate . _props . ExtraSizeForceAdd === true )
{
forcedUp += itemTemplate . _props . ExtraSizeUp ;
forcedDown += itemTemplate . _props . ExtraSizeDown ;
forcedLeft += itemTemplate . _props . ExtraSizeLeft ;
forcedRight += itemTemplate . _props . ExtraSizeRight ;
}
else
{
sizeUp = sizeUp < itemTemplate . _props . ExtraSizeUp ? itemTemplate._props.ExtraSizeUp : sizeUp ;
sizeDown = sizeDown < itemTemplate . _props . ExtraSizeDown ? itemTemplate._props.ExtraSizeDown : sizeDown ;
sizeLeft = sizeLeft < itemTemplate . _props . ExtraSizeLeft ? itemTemplate._props.ExtraSizeLeft : sizeLeft ;
2024-05-17 21:32:41 +02:00
sizeRight
= sizeRight < itemTemplate . _props . ExtraSizeRight ? itemTemplate._props.ExtraSizeRight : sizeRight ;
2023-03-03 16:23:46 +01:00
}
}
return {
width : width + sizeLeft + sizeRight + forcedLeft + forcedRight ,
2023-11-13 17:07:59 +01:00
height : height + sizeUp + sizeDown + forcedUp + forcedDown ,
2023-03-03 16:23:46 +01:00
} ;
}
/ * *
* Get a random cartridge from an items Filter property
2023-03-03 18:53:28 +01:00
* @param item Db item template to look up Cartridge filter values from
* @returns Caliber of cartridge
2023-03-03 16:23:46 +01:00
* /
2023-11-08 04:59:04 +01:00
public getRandomCompatibleCaliberTemplateId ( item : ITemplateItem ) : string | null
2023-03-03 16:23:46 +01:00
{
2023-11-08 04:59:04 +01:00
const cartridges = item ? . _props ? . Cartridges [ 0 ] ? . _props ? . filters [ 0 ] ? . Filter ;
2023-03-03 16:23:46 +01:00
if ( ! cartridges )
{
2023-11-08 04:59:04 +01:00
this . logger . warning ( ` Failed to find cartridge for item: ${ item ? . _id } ${ item ? . _name } ` ) ;
2023-03-03 16:23:46 +01:00
return null ;
}
2023-11-08 04:59:04 +01:00
return this . randomUtil . getArrayValue ( cartridges ) ;
2023-03-03 16:23:46 +01:00
}
/ * *
* Add cartridges to the ammo box with correct max stack sizes
* @param ammoBox Box to add cartridges to
* @param ammoBoxDetails Item template from items db
* /
public addCartridgesToAmmoBox ( ammoBox : Item [ ] , ammoBoxDetails : ITemplateItem ) : void
{
const ammoBoxMaxCartridgeCount = ammoBoxDetails . _props . StackSlots [ 0 ] . _max_count ;
const cartridgeTpl = ammoBoxDetails . _props . StackSlots [ 0 ] . _props . filters [ 0 ] . Filter [ 0 ] ;
const cartridgeDetails = this . getItem ( cartridgeTpl ) ;
const cartridgeMaxStackSize = cartridgeDetails [ 1 ] . _props . StackMaxSize ;
2024-04-29 00:04:37 +02:00
// Exit if ammo already exists in box
2024-05-17 21:32:41 +02:00
if ( ammoBox . find ( ( item ) = > item . _tpl === cartridgeTpl ) )
2024-04-29 00:04:37 +02:00
{
return ;
}
2023-03-03 16:23:46 +01:00
// Add new stack-size-correct items to ammo box
let currentStoredCartridgeCount = 0 ;
2023-10-10 13:03:20 +02:00
const maxPerStack = Math . min ( ammoBoxMaxCartridgeCount , cartridgeMaxStackSize ) ;
2024-02-11 18:31:52 +01:00
// Find location based on Max ammo box size
let location = Math . ceil ( ammoBoxMaxCartridgeCount / maxPerStack ) - 1 ;
2024-02-11 16:15:21 +01:00
2023-03-03 16:23:46 +01:00
while ( currentStoredCartridgeCount < ammoBoxMaxCartridgeCount )
{
2023-10-10 13:03:20 +02:00
const remainingSpace = ammoBoxMaxCartridgeCount - currentStoredCartridgeCount ;
2024-05-08 05:57:08 +02:00
const cartridgeCountToAdd = remainingSpace < maxPerStack ? remainingSpace : maxPerStack ;
2023-03-03 16:23:46 +01:00
2023-03-21 15:19:49 +01:00
// Add cartridge item into items array
2024-02-11 13:12:27 +01:00
const cartridgeItemToAdd = this . createCartridges (
ammoBox [ 0 ] . _id ,
cartridgeTpl ,
cartridgeCountToAdd ,
location ,
ammoBox [ 0 ] . upd ? . SpawnedInSession ,
2024-02-02 19:54:07 +01:00
) ;
2023-03-03 16:23:46 +01:00
2024-02-11 13:12:27 +01:00
// In live no ammo box has the first cartridge item with a location
if ( location === 0 )
{
delete cartridgeItemToAdd . location ;
}
2024-02-11 18:31:52 +01:00
ammoBox . push ( cartridgeItemToAdd ) ;
2024-02-11 13:12:27 +01:00
2023-03-03 16:23:46 +01:00
currentStoredCartridgeCount += cartridgeCountToAdd ;
2024-02-11 18:31:52 +01:00
location -- ;
2023-03-03 16:23:46 +01:00
}
}
2024-02-08 17:01:38 +01:00
/ * *
* Add a single stack of cartridges to the ammo box
* @param ammoBox Box to add cartridges to
* @param ammoBoxDetails Item template from items db
* /
public addSingleStackCartridgesToAmmoBox ( ammoBox : Item [ ] , ammoBoxDetails : ITemplateItem ) : void
{
const ammoBoxMaxCartridgeCount = ammoBoxDetails . _props . StackSlots [ 0 ] . _max_count ;
const cartridgeTpl = ammoBoxDetails . _props . StackSlots [ 0 ] . _props . filters [ 0 ] . Filter [ 0 ] ;
ammoBox . push (
this . createCartridges (
ammoBox [ 0 ] . _id ,
cartridgeTpl ,
ammoBoxMaxCartridgeCount ,
0 ,
ammoBox [ 0 ] . upd ? . SpawnedInSession ,
) ,
) ;
}
2023-10-10 13:03:20 +02:00
/ * *
* Check if item is stored inside of a container
* @param item Item to check is inside of container
* @param desiredContainerSlotId Name of slot to check item is in e . g . SecuredContainer / Backpack
* @param items Inventory with child parent items to check
* @returns True when item is in container
* /
public itemIsInsideContainer ( item : Item , desiredContainerSlotId : string , items : Item [ ] ) : boolean
{
// Get items parent
2024-05-17 21:32:41 +02:00
const parent = items . find ( ( x ) = > x . _id === item . parentId ) ;
2023-10-10 13:03:20 +02:00
if ( ! parent )
{
// No parent, end of line, not inside container
return false ;
}
if ( parent . slotId === desiredContainerSlotId )
{
return true ;
}
2024-01-09 00:27:18 +01:00
return this . itemIsInsideContainer ( parent , desiredContainerSlotId , items ) ;
2023-10-10 13:03:20 +02:00
}
2023-03-03 18:53:28 +01:00
/ * *
* Add child items ( cartridges ) to a magazine
* @param magazine Magazine to add child items to
* @param magTemplate Db template of magazine
* @param staticAmmoDist Cartridge distribution
* @param caliber Caliber of cartridge to add to magazine
* @param minSizePercent % the magazine must be filled to
2024-05-01 00:23:00 +02:00
* @param defaultCartridgeTpl Cartridge to use when none found
2024-02-07 15:45:43 +01:00
* @param weapon Weapon the magazine will be used for ( if passed in uses Chamber as whitelist )
2023-03-03 18:53:28 +01:00
* /
public fillMagazineWithRandomCartridge (
magazine : Item [ ] ,
2023-03-03 16:23:46 +01:00
magTemplate : ITemplateItem ,
2023-03-03 18:53:28 +01:00
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
caliber : string = undefined ,
2023-11-13 17:07:59 +01:00
minSizePercent = 0.25 ,
2024-05-01 00:23:00 +02:00
defaultCartridgeTpl? : string ,
weapon? : ITemplateItem ,
2023-03-03 18:53:28 +01:00
) : void
2023-03-03 16:23:46 +01:00
{
2024-02-06 04:02:44 +01:00
let chosenCaliber = caliber || this . getRandomValidCaliber ( magTemplate ) ;
2023-03-03 18:53:28 +01:00
2023-03-06 11:08:36 +01:00
// Edge case for the Klin pp-9, it has a typo in its ammo caliber
2024-02-06 04:02:44 +01:00
if ( chosenCaliber === "Caliber9x18PMM" )
2023-03-06 11:08:36 +01:00
{
2024-02-06 04:02:44 +01:00
chosenCaliber = "Caliber9x18PM" ;
2023-03-06 11:08:36 +01:00
}
2023-03-03 18:53:28 +01:00
// Chose a randomly weighted cartridge that fits
2024-02-07 15:45:43 +01:00
const cartridgeTpl = this . drawAmmoTpl (
chosenCaliber ,
staticAmmoDist ,
2024-05-01 00:23:00 +02:00
defaultCartridgeTpl ,
2024-02-07 15:45:43 +01:00
weapon ? . _props ? . Chambers [ 0 ] ? . _props ? . filters [ 0 ] ? . Filter ,
) ;
2024-05-01 00:23:00 +02:00
if ( ! cartridgeTpl )
{
return ;
}
2023-03-03 18:53:28 +01:00
this . fillMagazineWithCartridge ( magazine , magTemplate , cartridgeTpl , minSizePercent ) ;
}
/ * *
* Add child items to a magazine of a specific cartridge
2024-01-27 16:03:15 +01:00
* @param magazineWithChildCartridges Magazine to add child items to
2023-03-03 18:53:28 +01:00
* @param magTemplate Db template of magazine
* @param cartridgeTpl Cartridge to add to magazine
* @param minSizePercent % the magazine must be filled to
* /
public fillMagazineWithCartridge (
2024-01-27 16:03:15 +01:00
magazineWithChildCartridges : Item [ ] ,
2023-03-03 18:53:28 +01:00
magTemplate : ITemplateItem ,
cartridgeTpl : string ,
2023-11-13 17:07:59 +01:00
minSizePercent = 0.25 ,
2023-03-03 18:53:28 +01:00
) : void
{
2023-11-29 12:36:20 +01:00
// Get cartridge properties and max allowed stack size
2023-03-03 18:53:28 +01:00
const cartridgeDetails = this . getItem ( cartridgeTpl ) ;
2024-05-26 13:17:47 +02:00
if ( ! cartridgeDetails [ 0 ] )
{
this . logger . error ( this . localisationService . getText ( "item-invalid_tpl_item" , cartridgeTpl ) ) ;
}
const cartridgeMaxStackSize = cartridgeDetails [ 1 ] . _props ? . StackMaxSize ;
if ( ! cartridgeMaxStackSize )
{
this . logger . error ( ` Item with tpl: ${ cartridgeTpl } lacks a _props or StackMaxSize property ` ) ;
}
2023-03-03 18:53:28 +01:00
// Get max number of cartridges in magazine, choose random value between min/max
2024-05-08 05:57:08 +02:00
const magazineCartridgeMaxCount = this . isOfBaseclass ( magTemplate . _id , BaseClasses . SPRING_DRIVEN_CYLINDER )
2023-12-30 19:43:17 +01:00
? magTemplate . _props . Slots . length // Edge case for rotating grenade launcher magazine
: magTemplate . _props . Cartridges [ 0 ] ? . _max_count ;
2023-12-16 23:23:50 +01:00
if ( ! magazineCartridgeMaxCount )
{
2024-02-02 19:54:07 +01:00
this . logger . warning (
` Magazine: ${ magTemplate . _id } ${ magTemplate . _name } lacks a Cartridges array, unable to fill magazine with ammo ` ,
) ;
2023-12-16 23:23:50 +01:00
return ;
}
2023-11-13 17:07:59 +01:00
const desiredStackCount = this . randomUtil . getInt (
Math . round ( minSizePercent * magazineCartridgeMaxCount ) ,
magazineCartridgeMaxCount ,
) ;
2023-03-03 18:53:28 +01:00
2024-01-27 16:03:15 +01:00
if ( magazineWithChildCartridges . length > 1 )
2023-12-08 17:27:34 +01:00
{
this . logger . warning ( ` Magazine ${ magTemplate . _name } already has cartridges defined, this may cause issues ` ) ;
}
2023-03-03 18:53:28 +01:00
// Loop over cartridge count and add stacks to magazine
let currentStoredCartridgeCount = 0 ;
let location = 0 ;
2023-03-06 19:17:43 +01:00
while ( currentStoredCartridgeCount < desiredStackCount )
2023-03-03 18:53:28 +01:00
{
// Get stack size of cartridges
2024-05-17 21:32:41 +02:00
let cartridgeCountToAdd
= desiredStackCount <= cartridgeMaxStackSize ? desiredStackCount : cartridgeMaxStackSize ;
2023-03-03 18:53:28 +01:00
2023-03-06 19:17:43 +01:00
// Ensure we don't go over the max stackcount size
const remainingSpace = desiredStackCount - currentStoredCartridgeCount ;
if ( cartridgeCountToAdd > remainingSpace )
{
cartridgeCountToAdd = remainingSpace ;
}
2023-03-03 18:53:28 +01:00
// Add cartridge item object into items array
2024-01-27 16:03:15 +01:00
magazineWithChildCartridges . push (
this . createCartridges (
magazineWithChildCartridges [ 0 ] . _id ,
2024-02-02 19:54:07 +01:00
cartridgeTpl ,
cartridgeCountToAdd ,
2024-01-27 16:03:15 +01:00
location ,
2024-02-02 19:54:07 +01:00
magazineWithChildCartridges [ 0 ] . upd ? . SpawnedInSession ,
) ,
2024-01-27 16:03:15 +01:00
) ;
2023-03-03 18:53:28 +01:00
currentStoredCartridgeCount += cartridgeCountToAdd ;
2023-11-13 17:07:59 +01:00
location ++ ;
2023-03-03 18:53:28 +01:00
}
2024-01-27 16:03:15 +01:00
// Only one cartridge stack added, remove location property as its only used for 2 or more stacks
if ( location === 1 )
{
delete magazineWithChildCartridges [ 1 ] . location ;
}
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
/ * *
* Choose a random bullet type from the list of possible a magazine has
* @param magTemplate Magazine template from Db
* @returns Tpl of cartridge
* /
2023-03-03 16:23:46 +01:00
protected getRandomValidCaliber ( magTemplate : ITemplateItem ) : string
{
const ammoTpls = magTemplate . _props . Cartridges [ 0 ] . _props . filters [ 0 ] . Filter ;
const calibers = [
. . . new Set (
2024-05-17 21:32:41 +02:00
ammoTpls
. filter ( ( x : string ) = > this . getItem ( x ) [ 0 ] )
. map ( ( x : string ) = > this . getItem ( x ) [ 1 ] . _props . Caliber ) ,
2023-11-13 17:07:59 +01:00
) ,
2023-03-03 16:23:46 +01:00
] ;
return this . randomUtil . drawRandomFromList ( calibers ) [ 0 ] ;
}
2023-10-10 13:03:20 +02:00
/ * *
* Chose a randomly weighted cartridge that fits
* @param caliber Desired caliber
* @param staticAmmoDist Cartridges and thier weights
2024-04-23 10:34:01 +02:00
* @param fallbackCartridgeTpl If a cartridge cannot be found in the above staticAmmoDist param , use this instead
2024-02-07 15:45:43 +01:00
* @param cartridgeWhitelist OPTIONAL whitelist for cartridges
2023-11-29 12:36:20 +01:00
* @returns Tpl of cartridge
2023-10-10 13:03:20 +02:00
* /
2024-02-07 15:45:43 +01:00
protected drawAmmoTpl (
caliber : string ,
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
2024-04-23 10:34:01 +02:00
fallbackCartridgeTpl : string ,
2024-02-07 15:45:43 +01:00
cartridgeWhitelist : string [ ] = null ,
) : string
2023-03-03 16:23:46 +01:00
{
2024-01-02 16:01:27 +01:00
const ammos = staticAmmoDist [ caliber ] ;
2024-05-01 00:23:00 +02:00
if ( ! ammos && fallbackCartridgeTpl )
2024-01-02 16:01:27 +01:00
{
2024-04-23 10:34:01 +02:00
this . logger . error (
` Unable to pick a cartridge for caliber: ${ caliber } as staticAmmoDist has no data. using fallback value of ${ fallbackCartridgeTpl } ` ,
) ;
return fallbackCartridgeTpl ;
2024-02-20 10:07:48 +01:00
}
2024-05-01 00:23:00 +02:00
if ( ! Array . isArray ( ammos ) && fallbackCartridgeTpl )
2024-02-20 10:07:48 +01:00
{
this . logger . error (
2024-04-23 10:34:01 +02:00
` Unable to pick a cartridge for caliber: ${ caliber } , the chosen staticAmmoDist data is not an array. Using fallback value of ${ fallbackCartridgeTpl } ` ,
2024-02-20 10:07:48 +01:00
) ;
2024-04-23 10:34:01 +02:00
return fallbackCartridgeTpl ;
2024-01-02 16:01:27 +01:00
}
2024-05-01 00:23:00 +02:00
if ( ! ammos && ! fallbackCartridgeTpl )
{
this . logger . error (
` Unable to pick a cartridge for caliber: ${ caliber } as staticAmmoDist has no data. No fallback value provided ` ,
) ;
return ;
}
2024-05-13 19:58:17 +02:00
const ammoArray = new ProbabilityObjectArray < string > ( this . mathUtil , this . cloner ) ;
2024-01-02 16:01:27 +01:00
for ( const icd of ammos )
2023-03-03 16:23:46 +01:00
{
2024-02-07 15:45:43 +01:00
// Whitelist exists and tpl not inside it, skip
// Fixes 9x18mm kedr issues
if ( cartridgeWhitelist && ! cartridgeWhitelist . includes ( icd . tpl ) )
{
continue ;
}
2023-11-13 18:31:52 +01:00
ammoArray . push ( new ProbabilityObject ( icd . tpl , icd . relativeProbability ) ) ;
2023-03-03 16:23:46 +01:00
}
return ammoArray . draw ( 1 ) [ 0 ] ;
}
/ * *
2023-10-10 13:03:20 +02:00
* Create a basic cartrige object
2023-03-03 16:23:46 +01:00
* @param parentId container cartridges will be placed in
* @param ammoTpl Cartridge to insert
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
* @param stackCount Count of cartridges inside parent
2023-03-03 16:23:46 +01:00
* @param location Location inside parent ( e . g . 0 , 1 )
2024-01-23 16:24:02 +01:00
* @param foundInRaid OPTIONAL - Are cartridges found in raid ( SpawnedInSession )
2023-03-03 16:23:46 +01:00
* @returns Item
* /
2024-02-02 19:54:07 +01:00
public createCartridges (
parentId : string ,
ammoTpl : string ,
stackCount : number ,
location : number ,
foundInRaid = false ,
) : Item
2023-03-03 16:23:46 +01:00
{
return {
_id : this.objectId.generate ( ) ,
_tpl : ammoTpl ,
parentId : parentId ,
slotId : "cartridges" ,
location : location ,
2024-02-02 19:54:07 +01:00
upd : { StackObjectsCount : stackCount , SpawnedInSession : foundInRaid } ,
2023-03-03 16:23:46 +01:00
} ;
}
/ * *
* Get the size of a stack , return 1 if no stack object count property found
Refactor of InsuranceController & New ItemHelper Methods (!151)
This commit is my second go-around at refactoring the `InsuranceController`, attempting to improving the code's modularity, maintainability, and efficiency while squashing a few bugs along the way.
1. **InsuranceController.ts**
- Removed `ITemplateItem` import, as it is no longer used.
- Introduced the `adoptOrphanedItems` method to manage orphaned items in the insurance list.
- Since "normal" items are individually rolled for deletion, and can be nested within one another, there are situations where a parent item is deleted, leaving its children orphaned. This method moves those orphaned children from their missing parent into the root of the insurance container.
- Overhauled `findItemsToDelete` method to improve its efficiency and readability:
- Divided the original monolithic method into smaller, specialized methods like `populateItemsMap`, `populateParentAttachmentsMap`, `processRegularItems`, and `processAttachments`.
- Changed the return type to `Set<string>` for better performance.
- Introduced `EnrichedItem` interface (a simple extension of the `Item` interface) to add additional item data, like `name` and `maxPrice` to `Item` objects as they're processed throughout the class. This is done in place of repeatedly querying for this data, or complicating return types.
- Enhanced logging capabilities to debug the item deletion process. Due to the *current* lack of testing available I've stepped up the amount of debug logging that is done. This will hopefully help us find issues in the future.
- Modified the `rollForItemDelete` method, now renamed to `rollForDelete`, to include more detailed logging, return a boolean directly, and changed the `insuredItem` parameter to be optional.
- Added new methods for dealing with some of the particulars that arise from item adoption and creating item maps.
- Improved inline comments and documentation for better code maintainability.
2. **ItemHelper.ts**
- Added the `isRaidModdable` method to check if an item is *actually* modifiable in-raid, which takes into account not just the item, but the item that it's attached to.
- Added the `getAttachmentMainParent` method to fetch the main parent item of a given attachment, useful for item hierarchy traversal. For example, if you pass it an item ID of a suppressor, it will traverse up the muzzle brake, barrel, upper receiver, and return the gun that the suppressor is ultimately attached to, even if that gun is located within other multiple containers.
- Added the `isAttachmentAttached` method to check if an item is an attachment that is currently attached to its parent.
**Fixes:**
- Resolved an issue that caused item attachments from being property grouped together for deletion rolls. This issue prevented valuable attachments from being taken first.
- Resolved an issue that caused child items being orphaned when their parent was removed due to an insurance roll. Probable cause of the bug that made the client spaz out and send repeated insurance packages to the profile---Though I'm still unable to reproduce.
- Probably more...
Co-authored-by: Refringe <brownelltyler@gmail.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/151
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-committed-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
2023-10-14 11:05:49 +02:00
* @param item Item to get stack size of
2023-03-03 16:23:46 +01:00
* @returns size of stack
* /
public getItemStackSize ( item : Item ) : number
{
if ( item . upd ? . StackObjectsCount )
{
return item . upd . StackObjectsCount ;
}
return 1 ;
}
/ * *
* Get the name of an item from the locale file using the item tpl
* @param itemTpl Tpl of item to get name of
* @returns Name of item
* /
public getItemName ( itemTpl : string ) : string
{
return this . localeService . getLocaleDb ( ) [ ` ${ itemTpl } Name ` ] ;
}
2023-10-11 18:04:18 +02:00
public getItemTplsOfBaseType ( desiredBaseType : string ) : string [ ]
{
2024-05-17 21:32:41 +02:00
return Object . values ( this . databaseServer . getTables ( ) . templates . items )
. filter ( ( x ) = > x . _parent === desiredBaseType )
. map ( ( x ) = > x . _id ) ;
2023-10-11 18:04:18 +02:00
}
2024-01-09 00:27:18 +01:00
/ * *
* Add child slot items to an item , chooses random child item if multiple choices exist
* @param itemToAdd array with single object ( root item )
* @param itemToAddTemplate Db tempalte for root item
* @param modSpawnChanceDict Optional dictionary of mod name + % chance mod will be included in item ( e . g . front_plate : 100 )
2024-01-09 11:30:00 +01:00
* @param requiredOnly Only add required mods
* @returns Item with children
2024-01-09 00:27:18 +01:00
* /
2024-02-02 19:54:07 +01:00
public addChildSlotItems (
itemToAdd : Item [ ] ,
itemToAddTemplate : ITemplateItem ,
modSpawnChanceDict : Record < string , number > = null ,
requiredOnly = false ,
) : Item [ ]
2024-01-09 00:27:18 +01:00
{
const result = itemToAdd ;
2024-01-09 11:30:00 +01:00
const incompatibleModTpls : Set < string > = new Set ( ) ;
2024-01-09 00:27:18 +01:00
for ( const slot of itemToAddTemplate . _props . Slots )
{
2024-01-09 11:30:00 +01:00
// If only required mods is requested, skip non-essential
if ( requiredOnly && ! slot . _required )
{
continue ;
}
2024-01-09 00:27:18 +01:00
// Roll chance for non-required slot mods
if ( modSpawnChanceDict && ! slot . _required )
{
// only roll chance to not include mod if dict exists and has value for this mod type (e.g. front_plate)
const modSpawnChance = modSpawnChanceDict [ slot . _name . toLowerCase ( ) ] ;
if ( modSpawnChance )
{
2024-01-23 22:33:24 +01:00
if ( ! this . randomUtil . getChance100 ( modSpawnChance ) )
2024-01-09 00:27:18 +01:00
{
continue ;
}
}
}
2024-02-02 19:54:07 +01:00
const itemPool = slot . _props . filters [ 0 ] . Filter ? ? [ ] ;
2024-01-09 11:30:00 +01:00
const chosenTpl = this . getCompatibleTplFromArray ( itemPool , incompatibleModTpls ) ;
if ( ! chosenTpl )
2024-01-09 00:27:18 +01:00
{
2024-02-02 19:54:07 +01:00
this . logger . debug (
` Unable to add mod to item: ${ itemToAddTemplate . _id } ${ itemToAddTemplate . _name } slot: ${ slot . _name } as no compatible tpl could be found in pool of ${ itemPool . length } , skipping ` ,
) ;
2024-01-09 00:27:18 +01:00
continue ;
}
2024-01-21 17:40:14 +01:00
const modItemToAdd = {
2024-01-09 11:30:00 +01:00
_id : this.hashUtil.generate ( ) ,
_tpl : chosenTpl ,
parentId : result [ 0 ] . _id ,
2024-02-02 19:54:07 +01:00
slotId : slot._name ,
2024-01-09 11:30:00 +01:00
} ;
2024-01-21 17:40:14 +01:00
result . push ( modItemToAdd ) ;
2024-01-09 11:30:00 +01:00
2024-01-21 17:40:14 +01:00
const modItemDbDetails = this . getItem ( modItemToAdd . _tpl ) [ 1 ] ;
2024-01-09 11:30:00 +01:00
// Include conflicting items of newly added mod in pool to be used for next mod choice
modItemDbDetails . _props . ConflictingItems . forEach ( incompatibleModTpls . add , incompatibleModTpls ) ;
}
return result ;
}
/ * *
* Get a compatible tpl from the array provided where it is not found in the provided incompatible mod tpls parameter
2024-04-23 05:43:35 +02:00
* @param possibleTpls Tpls to randomly choose from
2024-01-09 11:30:00 +01:00
* @param incompatibleModTpls Incompatible tpls to not allow
* @returns Chosen tpl or null
* /
public getCompatibleTplFromArray ( possibleTpls : string [ ] , incompatibleModTpls : Set < string > ) : string
{
if ( possibleTpls . length === 0 )
{
return null ;
}
let chosenTpl = null ;
let count = 0 ;
while ( ! chosenTpl )
{
// Loop over choosing a random tpl until one is found or count varaible reaches the same size as the possible tpls array
const tpl = this . randomUtil . getArrayValue ( possibleTpls ) ;
if ( incompatibleModTpls . has ( tpl ) )
{
// Incompatible tpl was chosen, try again
2024-02-02 19:54:07 +01:00
count ++ ;
2024-01-09 11:30:00 +01:00
if ( count >= possibleTpls . length )
2024-01-09 00:27:18 +01:00
{
2024-01-09 11:30:00 +01:00
return null ;
2024-01-09 00:27:18 +01:00
}
2024-01-09 11:30:00 +01:00
continue ;
}
chosenTpl = tpl ;
2024-01-09 00:27:18 +01:00
}
2024-01-09 11:30:00 +01:00
return chosenTpl ;
2024-01-09 00:27:18 +01:00
}
2024-01-10 15:47:09 +01:00
/ * *
* Is the provided item . _props . Slots . _name property a plate slot
* @param slotName Name of slot ( _name ) of Items Slot array
* @returns True if its a slot that holds a removable palte
* /
public isRemovablePlateSlot ( slotName : string ) : boolean
{
2024-01-23 12:42:47 +01:00
return this . getRemovablePlateSlotIds ( ) . includes ( slotName . toLowerCase ( ) ) ;
2024-01-10 15:47:09 +01:00
}
/ * *
* Get a list of slot names that hold removable plates
* @returns Array of slot ids ( e . g . front_plate )
* /
2024-01-23 12:42:47 +01:00
public getRemovablePlateSlotIds ( ) : string [ ]
2024-01-10 15:47:09 +01:00
{
2024-02-08 17:53:14 +01:00
return [ "front_plate" , "back_plate" , "left_side_plate" , "right_side_plate" ] ;
2024-01-10 15:47:09 +01:00
}
2024-01-14 22:12:56 +01:00
/ * *
* Generate new unique ids for child items while preserving hierarchy
* @param rootItem Base / primary item
* @param itemWithChildren Primary item + children of primary item
* @returns Item array with updated IDs
* /
public reparentItemAndChildren ( rootItem : Item , itemWithChildren : Item [ ] ) : Item [ ]
{
const oldRootId = itemWithChildren [ 0 ] . _id ;
const idMappings = { } ;
idMappings [ oldRootId ] = rootItem . _id ;
for ( const mod of itemWithChildren )
{
if ( idMappings [ mod . _id ] === undefined )
{
idMappings [ mod . _id ] = this . hashUtil . generate ( ) ;
}
// Has parentId + no remapping exists for its parent
if ( mod . parentId !== undefined && idMappings [ mod . parentId ] === undefined )
{
// Make remapping for items parentId
idMappings [ mod . parentId ] = this . hashUtil . generate ( ) ;
}
mod . _id = idMappings [ mod . _id ] ;
if ( mod . parentId !== undefined )
{
mod . parentId = idMappings [ mod . parentId ] ;
}
}
// Force item's details into first location of presetItems
if ( itemWithChildren [ 0 ] . _tpl !== rootItem . _tpl )
{
this . logger . warning ( ` Reassigning root item from ${ itemWithChildren [ 0 ] . _tpl } to ${ rootItem . _tpl } ` ) ;
}
itemWithChildren [ 0 ] = rootItem ;
return itemWithChildren ;
}
/ * *
* Update a root items _id property value to be unique
* @param itemWithChildren Item to update root items _id property
* @param newId Optional : new id to use
2024-02-03 13:15:20 +01:00
* @returns New root id
2024-01-14 22:12:56 +01:00
* /
2024-02-03 13:15:20 +01:00
public remapRootItemId ( itemWithChildren : Item [ ] , newId = this . hashUtil . generate ( ) ) : string
2024-01-14 22:12:56 +01:00
{
const rootItemExistingId = itemWithChildren [ 0 ] . _id ;
for ( const item of itemWithChildren )
{
// Root, update id
if ( item . _id === rootItemExistingId )
{
item . _id = newId ;
continue ;
}
// Child with parent of root, update
if ( item . parentId === rootItemExistingId )
{
item . parentId = newId ;
}
}
2024-02-03 13:15:20 +01:00
return newId ;
2024-01-14 22:12:56 +01:00
}
2024-02-08 21:56:45 +01:00
/ * *
* Adopts orphaned items by resetting them as root "hideout" items . Helpful in situations where a parent has been
* deleted from a group of items and there are children still referencing the missing parent . This method will
* remove the reference from the children to the parent and set item properties to root values .
*
* @param rootId The ID of the "root" of the container .
* @param items Array of Items that should be adjusted .
* @returns Array of Items that have been adopted .
* /
public adoptOrphanedItems ( rootId : string , items : Item [ ] ) : Item [ ]
{
for ( const item of items )
{
// Check if the item's parent exists.
2024-05-17 21:32:41 +02:00
const parentExists = items . some ( ( parentItem ) = > parentItem . _id === item . parentId ) ;
2024-02-08 21:56:45 +01:00
// If the parent does not exist and the item is not already a 'hideout' item, adopt the orphaned item by
// setting the parent ID to the PMCs inventory equipment ID, the slot ID to 'hideout', and remove the location.
if ( ! parentExists && item . parentId !== rootId && item . slotId !== "hideout" )
{
item . parentId = rootId ;
item . slotId = "hideout" ;
delete item . location ;
}
}
return items ;
}
/ * *
* Populate a Map object of items for quick lookup using their ID .
*
* @param items An array of Items that should be added to a Map .
* @returns A Map where the keys are the item IDs and the values are the corresponding Item objects .
* /
public generateItemsMap ( items : Item [ ] ) : Map < string , Item >
{
const itemsMap = new Map < string , Item > ( ) ;
for ( const item of items )
{
itemsMap . set ( item . _id , item ) ;
}
return itemsMap ;
}
2024-03-07 10:18:39 +01:00
/ * *
* Add a blank upd object to passed in item if it does not exist already
* @param item item to add upd to
* @param warningMessageWhenMissing text to write to log when upd object was not found
* @returns True when upd object was added
* /
public addUpdObjectToItem ( item : Item , warningMessageWhenMissing : string = null ) : boolean
{
if ( ! item . upd )
{
item . upd = { } ;
2024-03-07 14:44:43 +01:00
if ( warningMessageWhenMissing )
{
2024-04-28 23:50:39 +02:00
this . logger . debug ( warningMessageWhenMissing ) ;
2024-03-07 14:44:43 +01:00
}
2024-03-07 10:18:39 +01:00
return true ;
}
return false ;
}
2023-03-03 16:23:46 +01:00
}
2024-05-13 19:58:17 +02:00
// eslint-disable-next-line @typescript-eslint/no-namespace
2023-03-03 16:23:46 +01:00
namespace ItemHelper
{
export interface ItemSize
{
2024-05-08 05:57:08 +02:00
width : number
height : number
2023-03-03 16:23:46 +01:00
}
}