2023-03-03 15:23:46 +00:00
import { inject , injectable } from "tsyringe" ;
2024-05-21 17:59:04 +00:00
import { ContainerHelper } from "@spt/helpers/ContainerHelper" ;
import { DialogueHelper } from "@spt/helpers/DialogueHelper" ;
import { ItemHelper } from "@spt/helpers/ItemHelper" ;
import { PaymentHelper } from "@spt/helpers/PaymentHelper" ;
import { PresetHelper } from "@spt/helpers/PresetHelper" ;
import { ProfileHelper } from "@spt/helpers/ProfileHelper" ;
import { TraderAssortHelper } from "@spt/helpers/TraderAssortHelper" ;
import { IPmcData } from "@spt/models/eft/common/IPmcData" ;
import { Inventory } from "@spt/models/eft/common/tables/IBotBase" ;
import { Item , Location , Upd } from "@spt/models/eft/common/tables/IItem" ;
import { IAddItemDirectRequest } from "@spt/models/eft/inventory/IAddItemDirectRequest" ;
import { IAddItemsDirectRequest } from "@spt/models/eft/inventory/IAddItemsDirectRequest" ;
import { IInventoryMergeRequestData } from "@spt/models/eft/inventory/IInventoryMergeRequestData" ;
import { IInventoryMoveRequestData } from "@spt/models/eft/inventory/IInventoryMoveRequestData" ;
import { IInventoryRemoveRequestData } from "@spt/models/eft/inventory/IInventoryRemoveRequestData" ;
import { IInventorySplitRequestData } from "@spt/models/eft/inventory/IInventorySplitRequestData" ;
import { IInventoryTransferRequestData } from "@spt/models/eft/inventory/IInventoryTransferRequestData" ;
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse" ;
import { BackendErrorCodes } from "@spt/models/enums/BackendErrorCodes" ;
import { BaseClasses } from "@spt/models/enums/BaseClasses" ;
import { BonusType } from "@spt/models/enums/BonusType" ;
import { ConfigTypes } from "@spt/models/enums/ConfigTypes" ;
import { IInventoryConfig , RewardDetails } from "@spt/models/spt/config/IInventoryConfig" ;
2024-06-12 20:25:27 +01:00
import { IOwnerInventoryItems } from "@spt/models/spt/inventory/IOwnerInventoryItems" ;
2024-05-21 17:59:04 +00:00
import { ILogger } from "@spt/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt/servers/ConfigServer" ;
import { DatabaseServer } from "@spt/servers/DatabaseServer" ;
import { FenceService } from "@spt/services/FenceService" ;
import { LocalisationService } from "@spt/services/LocalisationService" ;
import { ICloner } from "@spt/utils/cloners/ICloner" ;
import { HashUtil } from "@spt/utils/HashUtil" ;
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil" ;
2023-03-03 15:23:46 +00:00
@injectable ( )
export class InventoryHelper
{
protected inventoryConfig : IInventoryConfig ;
constructor (
2024-05-28 14:04:20 +00:00
@inject ( "PrimaryLogger" ) protected logger : ILogger ,
2023-03-03 15:23:46 +00:00
@inject ( "HashUtil" ) protected hashUtil : HashUtil ,
@inject ( "HttpResponseUtil" ) protected httpResponse : HttpResponseUtil ,
@inject ( "FenceService" ) protected fenceService : FenceService ,
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
@inject ( "PaymentHelper" ) protected paymentHelper : PaymentHelper ,
@inject ( "TraderAssortHelper" ) protected traderAssortHelper : TraderAssortHelper ,
@inject ( "DialogueHelper" ) protected dialogueHelper : DialogueHelper ,
@inject ( "ItemHelper" ) protected itemHelper : ItemHelper ,
@inject ( "ContainerHelper" ) protected containerHelper : ContainerHelper ,
@inject ( "ProfileHelper" ) protected profileHelper : ProfileHelper ,
2024-01-13 15:00:31 +00:00
@inject ( "PresetHelper" ) protected presetHelper : PresetHelper ,
2023-03-03 15:23:46 +00:00
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
2023-11-13 11:07:59 -05:00
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
2024-05-28 14:04:20 +00:00
@inject ( "PrimaryCloner" ) protected cloner : ICloner ,
2023-03-03 15:23:46 +00:00
)
{
this . inventoryConfig = this . configServer . getConfig ( ConfigTypes . INVENTORY ) ;
}
2024-02-01 09:48:46 +00:00
/ * *
2024-02-01 10:24:10 +00:00
* Add multiple items to player stash ( assuming they all fit )
2024-02-01 09:48:46 +00:00
* @param sessionId Session id
2024-02-01 10:24:10 +00:00
* @param request IAddItemsDirectRequest request
2024-02-01 09:48:46 +00:00
* @param pmcData Player profile
* @param output Client response object
* /
2024-02-02 13:54:07 -05:00
public addItemsToStash (
sessionId : string ,
request : IAddItemsDirectRequest ,
pmcData : IPmcData ,
output : IItemEventRouterResponse ,
) : void
2024-02-01 09:48:46 +00:00
{
// Check all items fit into inventory before adding
if ( ! this . canPlaceItemsInInventory ( sessionId , request . itemsWithModsToAdd ) )
{
2024-02-01 10:24:10 +00:00
// No space, exit
2024-03-08 08:59:14 +00:00
this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
BackendErrorCodes . NOTENOUGHSPACE ,
) ;
2024-02-01 09:48:46 +00:00
return ;
}
for ( const itemToAdd of request . itemsWithModsToAdd )
{
const addItemRequest : IAddItemDirectRequest = {
itemWithModsToAdd : itemToAdd ,
foundInRaid : request.foundInRaid ,
useSortingTable : request.useSortingTable ,
2024-02-02 13:54:07 -05:00
callback : request.callback ,
2024-02-01 09:48:46 +00:00
} ;
// Add to player inventory
this . addItemToStash ( sessionId , addItemRequest , pmcData , output ) ;
if ( output . warnings . length > 0 )
{
return ;
}
}
}
2023-03-03 15:23:46 +00:00
/ * *
2024-01-14 10:09:43 +00:00
* Add whatever is passed in ` request.itemWithModsToAdd ` into player inventory ( if it fits )
* @param sessionId Session id
* @param request addItemDirect request
* @param pmcData Player profile
* @param output Client response object
* /
2024-02-02 13:54:07 -05:00
public addItemToStash (
sessionId : string ,
request : IAddItemDirectRequest ,
pmcData : IPmcData ,
output : IItemEventRouterResponse ,
) : void
2024-01-14 10:09:43 +00:00
{
2024-05-13 17:58:17 +00:00
const itemWithModsToAddClone = this . cloner . clone ( request . itemWithModsToAdd ) ;
2024-01-14 10:09:43 +00:00
2024-01-14 22:30:05 +00:00
// Get stash layouts ready for use
2024-01-14 10:09:43 +00:00
const stashFS2D = this . getStashSlotMap ( pmcData , sessionId ) ;
const sortingTableFS2D = this . getSortingTableSlotMap ( pmcData ) ;
2024-01-14 21:12:56 +00:00
// Find empty slot in stash for item being added - adds 'location' + parentid + slotId properties to root item
2024-01-16 19:00:21 +00:00
this . placeItemInInventory (
2024-01-14 10:09:43 +00:00
stashFS2D ,
sortingTableFS2D ,
itemWithModsToAddClone ,
pmcData . Inventory ,
request . useSortingTable ,
output ,
) ;
2024-01-16 19:00:21 +00:00
if ( output . warnings . length > 0 )
2024-01-14 10:09:43 +00:00
{
// Failed to place, error out
2024-01-16 19:00:21 +00:00
return ;
2024-01-14 10:09:43 +00:00
}
// Apply/remove FiR to item + mods
2024-01-14 21:12:56 +00:00
this . setFindInRaidStatusForItem ( itemWithModsToAddClone , request . foundInRaid ) ;
2024-01-14 10:09:43 +00:00
// Remove trader properties from root item
this . removeTraderRagfairRelatedUpdProperties ( itemWithModsToAddClone [ 0 ] . upd ) ;
// Run callback
try
{
if ( typeof request . callback === "function" )
{
2024-01-15 14:25:17 +00:00
request . callback ( itemWithModsToAddClone [ 0 ] . upd . StackObjectsCount ) ;
2024-01-14 10:09:43 +00:00
}
}
catch ( err )
{
// Callback failed
2024-05-17 15:32:41 -04:00
const message
= typeof err ? . message === "string" ? err.message : this.localisationService.getText ( "http-unknown_error" ) ;
2024-01-14 10:09:43 +00:00
2024-01-16 19:00:21 +00:00
this . httpResponse . appendErrorToOutput ( output , message ) ;
return ;
2024-01-14 10:09:43 +00:00
}
2024-01-14 22:30:05 +00:00
// Add item + mods to output and profile inventory
2024-01-14 10:09:43 +00:00
output . profileChanges [ sessionId ] . items . new . push ( . . . itemWithModsToAddClone ) ;
pmcData . Inventory . items . push ( . . . itemWithModsToAddClone ) ;
2024-02-02 13:54:07 -05:00
this . logger . debug (
` Added ${ itemWithModsToAddClone [ 0 ] . upd ? . StackObjectsCount ? ? 1 } item: ${
itemWithModsToAddClone [ 0 ] . _tpl
} with : $ { itemWithModsToAddClone . length - 1 } mods to inventory ` ,
) ;
2024-01-14 10:09:43 +00:00
}
/ * *
2024-01-14 21:12:56 +00:00
* Set FiR status for an item + its children
* @param itemWithChildren An item
* @param foundInRaid Item was found in raid
* /
2024-02-27 12:12:46 +00:00
protected setFindInRaidStatusForItem ( itemWithChildren : Item [ ] , foundInRaid : boolean ) : void
2024-01-14 21:12:56 +00:00
{
for ( const item of itemWithChildren )
{
// Ensure item has upd object
2024-03-07 09:18:39 +00:00
this . itemHelper . addUpdObjectToItem ( item ) ;
2024-01-14 21:12:56 +00:00
if ( foundInRaid )
{
item . upd . SpawnedInSession = foundInRaid ;
}
else
{
2024-06-12 20:25:27 +01:00
delete item . upd . SpawnedInSession ;
2024-01-14 21:12:56 +00:00
}
}
}
2024-01-13 15:01:29 +00:00
/ * *
2024-02-27 12:12:46 +00:00
* Remove properties from a Upd object used by a trader / ragfair that are unnecessary to a player
2024-01-13 15:01:29 +00:00
* @param upd Object to update
* /
2024-01-14 10:09:43 +00:00
protected removeTraderRagfairRelatedUpdProperties ( upd : Upd ) : void
2024-01-13 15:01:29 +00:00
{
if ( upd . UnlimitedCount !== undefined )
{
delete upd . UnlimitedCount ;
}
if ( upd . BuyRestrictionCurrent !== undefined )
{
delete upd . BuyRestrictionCurrent ;
}
if ( upd . BuyRestrictionMax !== undefined )
{
delete upd . BuyRestrictionMax ;
}
}
2024-02-22 13:52:59 +00:00
/ * *
* Can all probided items be added into player inventory
* @param sessionId Player id
* @param itemsWithChildren array of items with children to try and fit
* @returns True all items fit
* /
2024-02-02 13:54:07 -05:00
public canPlaceItemsInInventory ( sessionId : string , itemsWithChildren : Item [ ] [ ] ) : boolean
2024-01-31 23:39:01 +00:00
{
const pmcData = this . profileHelper . getPmcProfile ( sessionId ) ;
2024-05-13 17:58:17 +00:00
const stashFS2D = this . cloner . clone ( this . getStashSlotMap ( pmcData , sessionId ) ) ;
2024-01-31 23:39:01 +00:00
for ( const itemWithChildren of itemsWithChildren )
{
2024-07-02 22:05:32 +00:00
if ( ! this . canPlaceItemInContainer ( stashFS2D , itemWithChildren ) )
2024-02-22 13:52:59 +00:00
{
return false ;
}
}
return true ;
}
/ * *
* Do the provided items all fit into the grid
* @param containerFS2D Container grid to fit items into
* @param itemsWithChildren items to try and fit into grid
* @returns True all fit
* /
public canPlaceItemsInContainer ( containerFS2D : number [ ] [ ] , itemsWithChildren : Item [ ] [ ] ) : boolean
{
for ( const itemWithChildren of itemsWithChildren )
{
2024-07-02 22:05:32 +00:00
if ( ! this . canPlaceItemInContainer ( containerFS2D , itemWithChildren ) )
2024-01-31 23:39:01 +00:00
{
return false ;
}
}
return true ;
}
2024-02-22 13:52:59 +00:00
/ * *
* Does an item fit into a container grid
* @param containerFS2D Container grid
* @param itemWithChildren item to check fits
* @returns True it fits
* /
public canPlaceItemInContainer ( containerFS2D : number [ ] [ ] , itemWithChildren : Item [ ] ) : boolean
2024-01-31 23:39:01 +00:00
{
// Get x/y size of item
const rootItem = itemWithChildren [ 0 ] ;
const itemSize = this . getItemSize ( rootItem . _tpl , rootItem . _id , itemWithChildren ) ;
// Look for a place to slot item into
2024-02-22 13:52:59 +00:00
const findSlotResult = this . containerHelper . findSlotForItem ( containerFS2D , itemSize [ 0 ] , itemSize [ 1 ] ) ;
2024-01-31 23:39:01 +00:00
if ( findSlotResult . success )
{
try
{
2024-02-09 15:13:49 +00:00
this . containerHelper . fillContainerMapWithItem (
2024-02-22 13:52:59 +00:00
containerFS2D ,
2024-01-31 23:39:01 +00:00
findSlotResult . x ,
findSlotResult . y ,
2024-02-09 17:13:19 +00:00
itemSize [ 0 ] ,
itemSize [ 1 ] ,
2024-02-09 15:13:49 +00:00
findSlotResult . rotation ,
) ;
2024-01-31 23:39:01 +00:00
}
catch ( err )
{
2024-05-07 23:57:08 -04:00
const errorText = typeof err === "string" ? ` -> ${ err } ` : err . message ;
2024-05-21 12:40:16 +01:00
this . logger . error ( this . localisationService . getText ( "inventory-unable_to_fit_item_into_inventory" , errorText ) ) ;
2024-02-09 15:18:49 +00:00
2024-01-31 23:39:01 +00:00
return false ;
}
// Success! exit
2024-07-02 22:05:32 +00:00
return true ;
2024-01-31 23:39:01 +00:00
}
2024-07-02 22:05:32 +00:00
return false ;
2024-01-31 23:39:01 +00:00
}
2024-02-22 13:52:59 +00:00
/ * *
* Find a free location inside a container to fit the item
* @param containerFS2D Container grid to add item to
* @param itemWithChildren Item to add to grid
* @param containerId Id of the container we ' re fitting item into
2024-03-10 14:13:08 +00:00
* @param desiredSlotId slot id value to use , default is "hideout"
2024-02-22 13:52:59 +00:00
* /
2024-03-10 14:13:08 +00:00
public placeItemInContainer (
containerFS2D : number [ ] [ ] ,
itemWithChildren : Item [ ] ,
containerId : string ,
desiredSlotId = "hideout" ,
) : void
2024-02-22 13:52:59 +00:00
{
// Get x/y size of item
const rootItemAdded = itemWithChildren [ 0 ] ;
const itemSize = this . getItemSize ( rootItemAdded . _tpl , rootItemAdded . _id , itemWithChildren ) ;
// Look for a place to slot item into
const findSlotResult = this . containerHelper . findSlotForItem ( containerFS2D , itemSize [ 0 ] , itemSize [ 1 ] ) ;
if ( findSlotResult . success )
{
try
{
this . containerHelper . fillContainerMapWithItem (
containerFS2D ,
findSlotResult . x ,
findSlotResult . y ,
itemSize [ 0 ] ,
itemSize [ 1 ] ,
findSlotResult . rotation ,
) ;
}
catch ( err )
{
2024-05-07 23:57:08 -04:00
const errorText = typeof err === "string" ? ` -> ${ err } ` : err . message ;
2024-02-22 13:52:59 +00:00
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
return ;
}
// Store details for object, incuding container item will be placed in
rootItemAdded . parentId = containerId ;
2024-03-10 14:13:08 +00:00
rootItemAdded . slotId = desiredSlotId ;
2024-02-22 13:52:59 +00:00
rootItemAdded . location = {
x : findSlotResult.x ,
y : findSlotResult.y ,
r : findSlotResult.rotation ? 1 : 0 ,
rotation : findSlotResult.rotation ,
} ;
// Success! exit
return ;
}
}
2024-02-09 15:48:57 +00:00
/ * *
* Find a location to place an item into inventory and place it
* @param stashFS2D 2 - dimensional representation of the container slots
* @param sortingTableFS2D 2 - dimensional representation of the sorting table slots
2024-06-30 20:38:23 +01:00
* @param itemWithChildren Item to place with children
* @param playerInventory Players inventory
2024-02-09 15:48:57 +00:00
* @param useSortingTable Should sorting table to be used if main stash has no space
* @param output output to send back to client
* /
2024-01-14 10:09:43 +00:00
protected placeItemInInventory (
stashFS2D : number [ ] [ ] ,
sortingTableFS2D : number [ ] [ ] ,
itemWithChildren : Item [ ] ,
playerInventory : Inventory ,
useSortingTable : boolean ,
2024-02-02 13:54:07 -05:00
output : IItemEventRouterResponse ,
) : void
2024-01-14 10:09:43 +00:00
{
2024-01-14 21:12:56 +00:00
// Get x/y size of item
const rootItem = itemWithChildren [ 0 ] ;
const itemSize = this . getItemSize ( rootItem . _tpl , rootItem . _id , itemWithChildren ) ;
// Look for a place to slot item into
2024-01-14 10:09:43 +00:00
const findSlotResult = this . containerHelper . findSlotForItem ( stashFS2D , itemSize [ 0 ] , itemSize [ 1 ] ) ;
if ( findSlotResult . success )
{
try
{
2024-02-09 15:13:49 +00:00
this . containerHelper . fillContainerMapWithItem (
2024-01-14 10:09:43 +00:00
stashFS2D ,
findSlotResult . x ,
findSlotResult . y ,
2024-02-09 17:13:19 +00:00
itemSize [ 0 ] ,
itemSize [ 1 ] ,
2024-02-09 15:13:49 +00:00
findSlotResult . rotation ,
) ;
2024-01-14 10:09:43 +00:00
}
catch ( err )
{
2024-06-30 20:38:23 +01:00
handleContainerPlacementError ( err , output ) ;
2024-01-16 19:00:21 +00:00
return ;
2024-01-14 10:09:43 +00:00
}
// Store details for object, incuding container item will be placed in
2024-01-14 21:12:56 +00:00
rootItem . parentId = playerInventory . stash ;
rootItem . slotId = "hideout" ;
rootItem . location = {
2024-01-14 10:09:43 +00:00
x : findSlotResult.x ,
y : findSlotResult.y ,
r : findSlotResult.rotation ? 1 : 0 ,
rotation : findSlotResult.rotation ,
} ;
// Success! exit
return ;
}
// Space not found in main stash, use sorting table
if ( useSortingTable )
{
const findSortingSlotResult = this . containerHelper . findSlotForItem (
sortingTableFS2D ,
itemSize [ 0 ] ,
itemSize [ 1 ] ,
) ;
2024-02-09 17:13:19 +00:00
2024-01-14 10:09:43 +00:00
try
{
2024-02-09 15:13:49 +00:00
this . containerHelper . fillContainerMapWithItem (
2024-01-14 10:09:43 +00:00
sortingTableFS2D ,
findSortingSlotResult . x ,
findSortingSlotResult . y ,
2024-02-09 17:13:19 +00:00
itemSize [ 0 ] ,
itemSize [ 1 ] ,
2024-02-09 15:13:49 +00:00
findSortingSlotResult . rotation ,
) ;
2024-01-14 10:09:43 +00:00
}
catch ( err )
{
2024-06-30 20:38:23 +01:00
handleContainerPlacementError ( err , output ) ;
2024-01-16 19:00:21 +00:00
return ;
2024-01-14 10:09:43 +00:00
}
// Store details for object, incuding container item will be placed in
itemWithChildren [ 0 ] . parentId = playerInventory . sortingTable ;
itemWithChildren [ 0 ] . location = {
x : findSortingSlotResult.x ,
y : findSortingSlotResult.y ,
r : findSortingSlotResult.rotation ? 1 : 0 ,
rotation : findSortingSlotResult.rotation ,
} ;
}
else
{
2024-03-08 08:59:14 +00:00
this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
BackendErrorCodes . NOTENOUGHSPACE ,
) ;
2024-01-16 19:00:21 +00:00
2024-02-02 13:54:07 -05:00
return ;
2024-01-14 10:09:43 +00:00
}
2024-06-30 20:38:23 +01:00
function handleContainerPlacementError ( err : any , output : IItemEventRouterResponse ) : void
2024-01-14 21:12:56 +00:00
{
2024-06-30 20:38:23 +01:00
const errorText = typeof err === "string" ? ` -> ${ err } ` : err . message ;
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
2023-03-03 15:23:46 +00:00
}
}
/ * *
2023-10-10 11:03:20 +00:00
* Handle Remove event
2023-07-15 11:00:35 +01:00
* Remove item from player inventory + insured items array
2023-10-10 11:03:20 +00:00
* Also deletes child items
* @param profile Profile to remove item from ( pmc or scav )
2023-03-03 15:23:46 +00:00
* @param itemId Items id to remove
* @param sessionID Session id
2024-02-14 14:59:43 +00:00
* @param output OPTIONAL - IItemEventRouterResponse
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:07:59 -05:00
public removeItem (
profile : IPmcData ,
itemId : string ,
sessionID : string ,
2024-05-28 18:32:09 +01:00
output? : IItemEventRouterResponse ,
2024-02-14 14:59:43 +00:00
) : void
2023-03-03 15:23:46 +00:00
{
if ( ! itemId )
2023-10-10 11:03:20 +00:00
{
2024-05-24 16:42:42 +01:00
this . logger . warning ( this . localisationService . getText ( "inventory-unable_to_remove_item_no_id_given" ) ) ;
2023-10-10 11:03:20 +00:00
2024-02-14 14:59:43 +00:00
return ;
2023-10-10 11:03:20 +00:00
}
2023-03-03 15:23:46 +00:00
2023-10-10 11:03:20 +00:00
// Get children of item, they get deleted too
const itemToRemoveWithChildren = this . itemHelper . findAndReturnChildrenByItems ( profile . Inventory . items , itemId ) ;
const inventoryItems = profile . Inventory . items ;
const insuredItems = profile . InsuredItems ;
2023-03-03 15:23:46 +00:00
2023-10-10 11:03:20 +00:00
// We have output object, inform client of item deletion
2023-03-03 15:23:46 +00:00
if ( output )
2023-10-10 11:03:20 +00:00
{
2023-11-13 12:38:16 -05:00
output . profileChanges [ sessionID ] . items . del . push ( { _id : itemId } ) ;
2023-10-10 11:03:20 +00:00
}
2023-03-03 15:23:46 +00:00
2023-10-10 11:03:20 +00:00
for ( const childId of itemToRemoveWithChildren )
2023-03-03 15:23:46 +00:00
{
// We expect that each inventory item and each insured item has unique "_id", respective "itemId".
// Therefore we want to use a NON-Greedy function and escape the iteration as soon as we find requested item.
2024-05-17 15:32:41 -04:00
const inventoryIndex = inventoryItems . findIndex ( ( item ) = > item . _id === childId ) ;
2024-06-30 20:38:23 +01:00
if ( inventoryIndex !== - 1 )
2023-03-03 15:23:46 +00:00
{
inventoryItems . splice ( inventoryIndex , 1 ) ;
}
2024-06-30 20:38:23 +01:00
else
2023-10-10 11:03:20 +00:00
{
2024-05-24 16:42:42 +01:00
this . logger . warning ( this . localisationService . getText ( "inventory-unable_to_remove_item_id_not_found" ,
{
childId : childId ,
profileId : profile._id ,
} ) ) ;
2023-10-10 11:03:20 +00:00
}
2024-05-17 15:32:41 -04:00
const insuredIndex = insuredItems . findIndex ( ( item ) = > item . itemId === childId ) ;
2024-06-30 20:38:23 +01:00
if ( insuredIndex !== - 1 )
2023-03-03 15:23:46 +00:00
{
insuredItems . splice ( insuredIndex , 1 ) ;
}
}
}
2024-02-27 12:12:46 +00:00
/ * *
* Delete desired item from a player profiles mail
* @param sessionId Session id
* @param removeRequest Remove request
* @param output OPTIONAL - IItemEventRouterResponse
* /
2023-11-13 11:07:59 -05:00
public removeItemAndChildrenFromMailRewards (
sessionId : string ,
removeRequest : IInventoryRemoveRequestData ,
2024-05-28 18:32:09 +01:00
output? : IItemEventRouterResponse ,
2024-01-16 12:21:42 +00:00
) : void
2023-10-10 11:03:20 +00:00
{
const fullProfile = this . profileHelper . getFullProfile ( sessionId ) ;
// Iterate over all dialogs and look for mesasage with key from request, that has item (and maybe its children) we want to remove
const dialogs = Object . values ( fullProfile . dialogues ) ;
for ( const dialog of dialogs )
{
2024-05-17 15:32:41 -04:00
const messageWithReward = dialog . messages . find ( ( x ) = > x . _id === removeRequest . fromOwner . id ) ;
2023-10-10 11:03:20 +00:00
if ( messageWithReward )
{
// Find item + any possible children and remove them from mails items array
2023-11-13 11:07:59 -05:00
const itemWithChildern = this . itemHelper . findAndReturnChildrenAsItems (
messageWithReward . items . data ,
removeRequest . item ,
) ;
2023-10-10 11:03:20 +00:00
for ( const itemToDelete of itemWithChildern )
{
// Get index of item to remove from reward array + remove it
const indexOfItemToRemove = messageWithReward . items . data . indexOf ( itemToDelete ) ;
if ( indexOfItemToRemove === - 1 )
{
2023-11-13 11:07:59 -05:00
this . logger . error (
` Unable to remove item: ${ removeRequest . item } from mail: ${ removeRequest . fromOwner . id } as item could not be found, restart client immediately to prevent data corruption ` ,
) ;
2023-10-10 11:03:20 +00:00
continue ;
}
messageWithReward . items . data . splice ( indexOfItemToRemove , 1 ) ;
}
// Flag message as having no rewards if all removed
const hasRewardItemsRemaining = messageWithReward ? . items . data ? . length > 0 ;
messageWithReward . hasRewards = hasRewardItemsRemaining ;
messageWithReward . rewardCollected = ! hasRewardItemsRemaining ;
}
}
}
2024-02-27 12:12:46 +00:00
/ * *
* Find item by id in player inventory and remove x of its count
* @param pmcData player profile
* @param itemId Item id to decrement StackObjectsCount of
* @param countToRemove Number of item to remove
* @param sessionID Session id
* @param output IItemEventRouterResponse
* @returns IItemEventRouterResponse
* /
2023-11-13 11:07:59 -05:00
public removeItemByCount (
pmcData : IPmcData ,
itemId : string ,
2024-02-27 12:12:46 +00:00
countToRemove : number ,
2023-11-13 11:07:59 -05:00
sessionID : string ,
2024-05-28 18:32:09 +01:00
output? : IItemEventRouterResponse ,
2023-11-13 11:07:59 -05:00
) : IItemEventRouterResponse
2023-03-03 15:23:46 +00:00
{
if ( ! itemId )
2023-11-13 11:07:59 -05:00
{
2023-03-03 15:23:46 +00:00
return output ;
2023-11-13 11:07:59 -05:00
}
2023-03-03 15:23:46 +00:00
2024-02-27 12:12:46 +00:00
// Goal is to keep removing items until we can remove part of an items stack
2023-03-03 15:23:46 +00:00
const itemsToReduce = this . itemHelper . findAndReturnChildrenAsItems ( pmcData . Inventory . items , itemId ) ;
2024-02-27 12:12:46 +00:00
let remainingCount = countToRemove ;
2023-03-03 15:23:46 +00:00
for ( const itemToReduce of itemsToReduce )
{
2024-02-27 12:12:46 +00:00
const itemStackSize = this . itemHelper . getItemStackSize ( itemToReduce ) ;
2023-03-03 15:23:46 +00:00
2024-02-27 12:12:46 +00:00
// Remove whole stack
if ( remainingCount >= itemStackSize )
2023-03-03 15:23:46 +00:00
{
2024-02-27 12:12:46 +00:00
remainingCount -= itemStackSize ;
2023-03-03 15:23:46 +00:00
this . removeItem ( pmcData , itemToReduce . _id , sessionID , output ) ;
}
else
{
itemToReduce . upd . StackObjectsCount -= remainingCount ;
remainingCount = 0 ;
if ( output )
2023-11-13 11:07:59 -05:00
{
2023-03-03 15:23:46 +00:00
output . profileChanges [ sessionID ] . items . change . push ( itemToReduce ) ;
2023-11-13 11:07:59 -05:00
}
2023-03-03 15:23:46 +00:00
}
if ( remainingCount === 0 )
2023-11-13 11:07:59 -05:00
{
2024-02-27 12:12:46 +00:00
// Desired count of item has been removed / we ran out of items to remove
2023-03-03 15:23:46 +00:00
break ;
2023-11-13 11:07:59 -05:00
}
2023-03-03 15:23:46 +00:00
}
return output ;
}
2024-02-27 12:12:46 +00:00
/ * *
* Get the height and width of an item - can have children that alter size
* @param itemTpl Item to get size of
* @param itemID Items id to get size of
* @param inventoryItems
* @returns [ width , height ]
2023-03-03 15:23:46 +00:00
* /
2024-01-14 10:09:43 +00:00
public getItemSize ( itemTpl : string , itemID : string , inventoryItems : Item [ ] ) : number [ ]
2023-03-03 15:23:46 +00:00
{
// -> Prepares item Width and height returns [sizeX, sizeY]
2024-01-14 10:09:43 +00:00
return this . getSizeByInventoryItemHash ( itemTpl , itemID , this . getInventoryItemHash ( inventoryItems ) ) ;
2023-03-03 15:23:46 +00:00
}
2024-06-30 20:38:23 +01:00
/ * *
* Calculates the size of an item including attachements
* takes into account if item is folded
* @param itemTpl Items template id
* @param itemID Items id
* @param inventoryItemHash Hashmap of inventory items
* @returns An array representing the [ width , height ] of the item
* /
2023-11-13 11:07:59 -05:00
protected getSizeByInventoryItemHash (
itemTpl : string ,
itemID : string ,
inventoryItemHash : InventoryHelper.InventoryItemHash ,
) : number [ ]
2023-03-03 15:23:46 +00:00
{
const toDo = [ itemID ] ;
const result = this . itemHelper . getItem ( itemTpl ) ;
const tmpItem = result [ 1 ] ;
2023-04-12 15:51:52 +01:00
// Invalid item or no object
2023-03-03 15:23:46 +00:00
if ( ! ( result [ 0 ] && result [ 1 ] ) )
{
this . logger . error ( this . localisationService . getText ( "inventory-invalid_item_missing_from_db" , itemTpl ) ) ;
}
2023-07-19 11:00:34 +01:00
// Item found but no _props property
2023-04-12 15:51:52 +01:00
if ( tmpItem && ! tmpItem . _props )
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:07:59 -05:00
this . localisationService . getText ( "inventory-item_missing_props_property" , {
itemTpl : itemTpl ,
itemName : tmpItem?._name ,
} ) ;
2023-04-12 15:51:52 +01:00
}
// No item object or getItem() returned false
if ( ! ( tmpItem && result [ 0 ] ) )
{
// return default size of 1x1
this . logger . error ( this . localisationService . getText ( "inventory-return_default_size" , itemTpl ) ) ;
2024-06-30 20:38:23 +01:00
return [ 1 , 1 ] ; // Invalid input data, return defaults
2023-03-03 15:23:46 +00:00
}
const rootItem = inventoryItemHash . byItemId [ itemID ] ;
const foldableWeapon = tmpItem . _props . Foldable ;
const foldedSlot = tmpItem . _props . FoldedSlot ;
let sizeUp = 0 ;
let sizeDown = 0 ;
let sizeLeft = 0 ;
let sizeRight = 0 ;
let forcedUp = 0 ;
let forcedDown = 0 ;
let forcedLeft = 0 ;
let forcedRight = 0 ;
let outX = tmpItem . _props . Width ;
const outY = tmpItem . _props . Height ;
2024-06-30 20:38:23 +01:00
// Item types to ignore
2023-03-03 15:23:46 +00:00
const skipThisItems : string [ ] = [
BaseClasses . BACKPACK ,
BaseClasses . SEARCHABLE_ITEM ,
2023-11-13 11:07:59 -05:00
BaseClasses . SIMPLE_CONTAINER ,
2023-03-03 15:23:46 +00:00
] ;
const rootFolded = rootItem . upd ? . Foldable && rootItem . upd . Foldable . Folded === true ;
2023-11-13 11:07:59 -05:00
// The item itself is collapsible
2023-03-03 15:23:46 +00:00
if ( foldableWeapon && ( foldedSlot === undefined || foldedSlot === "" ) && rootFolded )
{
outX -= tmpItem . _props . SizeReduceRight ;
}
2024-06-30 20:38:23 +01:00
// Calculate size contribution from child items/attachments
2023-03-03 15:23:46 +00:00
if ( ! skipThisItems . includes ( tmpItem . _parent ) )
{
while ( toDo . length > 0 )
{
if ( toDo [ 0 ] in inventoryItemHash . byParentId )
{
for ( const item of inventoryItemHash . byParentId [ toDo [ 0 ] ] )
{
2023-11-13 11:07:59 -05:00
// Filtering child items outside of mod slots, such as those inside containers, without counting their ExtraSize attribute
2023-03-03 15:23:46 +00:00
if ( item . slotId . indexOf ( "mod_" ) < 0 )
{
continue ;
}
toDo . push ( item . _id ) ;
// If the barrel is folded the space in the barrel is not counted
const itemResult = this . itemHelper . getItem ( item . _tpl ) ;
if ( ! itemResult [ 0 ] )
{
2023-11-13 11:07:59 -05:00
this . logger . error (
this . localisationService . getText (
"inventory-get_item_size_item_not_found_by_tpl" ,
item . _tpl ,
) ,
) ;
2023-03-03 15:23:46 +00:00
}
const itm = itemResult [ 1 ] ;
const childFoldable = itm . _props . Foldable ;
const childFolded = item . upd ? . Foldable && item . upd . Foldable . Folded === true ;
if ( foldableWeapon && foldedSlot === item . slotId && ( rootFolded || childFolded ) )
{
continue ;
}
2024-02-02 13:54:07 -05:00
2024-01-16 19:00:21 +00:00
if ( childFoldable && rootFolded && childFolded )
2023-03-03 15:23:46 +00:00
{
continue ;
}
// Calculating child ExtraSize
if ( itm . _props . ExtraSizeForceAdd === true )
{
forcedUp += itm . _props . ExtraSizeUp ;
forcedDown += itm . _props . ExtraSizeDown ;
forcedLeft += itm . _props . ExtraSizeLeft ;
forcedRight += itm . _props . ExtraSizeRight ;
}
else
{
sizeUp = sizeUp < itm . _props . ExtraSizeUp ? itm._props.ExtraSizeUp : sizeUp ;
sizeDown = sizeDown < itm . _props . ExtraSizeDown ? itm._props.ExtraSizeDown : sizeDown ;
sizeLeft = sizeLeft < itm . _props . ExtraSizeLeft ? itm._props.ExtraSizeLeft : sizeLeft ;
sizeRight = sizeRight < itm . _props . ExtraSizeRight ? itm._props.ExtraSizeRight : sizeRight ;
}
}
}
toDo . splice ( 0 , 1 ) ;
}
}
return [
outX + sizeLeft + sizeRight + forcedLeft + forcedRight ,
2023-11-13 11:07:59 -05:00
outY + sizeUp + sizeDown + forcedUp + forcedDown ,
2023-03-03 15:23:46 +00:00
] ;
}
2024-02-27 12:12:46 +00:00
/ * *
* Get a blank two - dimentional representation of a container
* @param containerH Horizontal size of container
* @param containerY Vertical size of container
* @returns Two - dimensional representation of container
* /
2024-02-22 13:52:59 +00:00
protected getBlankContainerMap ( containerH : number , containerY : number ) : number [ ] [ ]
2023-03-03 15:23:46 +00:00
{
2024-05-17 15:32:41 -04:00
return Array ( containerY )
. fill ( 0 )
. map ( ( ) = > Array ( containerH ) . fill ( 0 ) ) ;
2024-02-22 13:52:59 +00:00
}
2024-02-27 12:12:46 +00:00
/ * *
* @param containerH Horizontal size of container
* @param containerV Vertical size of container
* @param itemList
* @param containerId Id of the container
* @returns Two - dimensional representation of container
* /
2024-02-22 13:52:59 +00:00
public getContainerMap ( containerH : number , containerV : number , itemList : Item [ ] , containerId : string ) : number [ ] [ ]
{
const container2D : number [ ] [ ] = this . getBlankContainerMap ( containerH , containerV ) ;
2023-03-03 15:23:46 +00:00
const inventoryItemHash = this . getInventoryItemHash ( itemList ) ;
const containerItemHash = inventoryItemHash . byParentId [ containerId ] ;
if ( ! containerItemHash )
{
// No items in the container
return container2D ;
}
for ( const item of containerItemHash )
{
if ( ! ( "location" in item ) )
{
continue ;
}
const tmpSize = this . getSizeByInventoryItemHash ( item . _tpl , item . _id , inventoryItemHash ) ;
const iW = tmpSize [ 0 ] ; // x
const iH = tmpSize [ 1 ] ; // y
2024-05-07 23:57:08 -04:00
const fH
2024-05-17 15:32:41 -04:00
= ( item . location as Location ) . r === 1
|| ( item . location as Location ) . r === "Vertical"
2024-05-07 23:57:08 -04:00
|| ( item . location as Location ) . rotation === "Vertical"
2023-11-13 12:31:52 -05:00
? iW
: iH ;
2024-05-07 23:57:08 -04:00
const fW
2024-05-17 15:32:41 -04:00
= ( item . location as Location ) . r === 1
|| ( item . location as Location ) . r === "Vertical"
2024-05-07 23:57:08 -04:00
|| ( item . location as Location ) . rotation === "Vertical"
2023-11-13 12:31:52 -05:00
? iH
: iW ;
2023-03-03 15:23:46 +00:00
const fillTo = ( item . location as Location ) . x + fW ;
for ( let y = 0 ; y < fH ; y ++ )
{
try
{
container2D [ ( item . location as Location ) . y + y ] . fill ( 1 , ( item . location as Location ) . x , fillTo ) ;
}
catch ( e )
{
2023-11-13 11:07:59 -05:00
this . logger . error (
this . localisationService . getText ( "inventory-unable_to_fill_container" , {
id : item._id ,
error : e ,
} ) ,
) ;
2023-03-03 15:23:46 +00:00
}
}
}
return container2D ;
}
2024-02-27 12:12:46 +00:00
protected getInventoryItemHash ( inventoryItem : Item [ ] ) : InventoryHelper . InventoryItemHash
{
const inventoryItemHash : InventoryHelper.InventoryItemHash = { byItemId : { } , byParentId : { } } ;
for ( const item of inventoryItem )
{
inventoryItemHash . byItemId [ item . _id ] = item ;
if ( ! ( "parentId" in item ) )
{
continue ;
}
if ( ! ( item . parentId in inventoryItemHash . byParentId ) )
{
inventoryItemHash . byParentId [ item . parentId ] = [ ] ;
}
inventoryItemHash . byParentId [ item . parentId ] . push ( item ) ;
}
return inventoryItemHash ;
}
2023-03-03 15:23:46 +00:00
/ * *
2023-10-10 11:03:20 +00:00
* Return the inventory that needs to be modified ( scav / pmc etc )
* Changes made to result apply to character inventory
2023-03-03 15:23:46 +00:00
* Based on the item action , determine whose inventories we should be looking at for from and to .
2023-10-10 11:03:20 +00:00
* @param request Item interaction request
* @param sessionId Session id / playerid
* @returns OwnerInventoryItems with inventory of player / scav to adjust
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:07:59 -05:00
public getOwnerInventoryItems (
2024-02-22 13:52:59 +00:00
request :
2024-05-07 23:57:08 -04:00
| IInventoryMoveRequestData
| IInventorySplitRequestData
| IInventoryMergeRequestData
| IInventoryTransferRequestData ,
2023-11-13 11:07:59 -05:00
sessionId : string ,
2024-02-01 09:58:20 +00:00
) : IOwnerInventoryItems
2023-03-03 15:23:46 +00:00
{
let isSameInventory = false ;
2023-10-10 11:03:20 +00:00
const pmcItems = this . profileHelper . getPmcProfile ( sessionId ) . Inventory . items ;
const scavData = this . profileHelper . getScavProfile ( sessionId ) ;
2023-03-03 15:23:46 +00:00
let fromInventoryItems = pmcItems ;
let fromType = "pmc" ;
2023-10-10 11:03:20 +00:00
if ( request . fromOwner )
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
if ( request . fromOwner . id === scavData . _id )
2023-03-03 15:23:46 +00:00
{
fromInventoryItems = scavData . Inventory . items ;
fromType = "scav" ;
}
2023-10-10 11:03:20 +00:00
else if ( request . fromOwner . type . toLocaleLowerCase ( ) === "mail" )
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
// Split requests dont use 'use' but 'splitItem' property
2023-11-13 12:31:52 -05:00
const item = "splitItem" in request ? request.splitItem : request.item ;
2023-10-10 11:03:20 +00:00
fromInventoryItems = this . dialogueHelper . getMessageItemContents ( request . fromOwner . id , sessionId , item ) ;
2023-03-03 15:23:46 +00:00
fromType = "mail" ;
}
}
// Don't need to worry about mail for destination because client doesn't allow
// users to move items back into the mail stash.
let toInventoryItems = pmcItems ;
let toType = "pmc" ;
2023-10-10 11:03:20 +00:00
// Destination is scav inventory, update values
if ( request . toOwner ? . id === scavData . _id )
2023-03-03 15:23:46 +00:00
{
toInventoryItems = scavData . Inventory . items ;
toType = "scav" ;
}
2023-10-10 11:03:20 +00:00
// From and To types match, same inventory
2023-03-03 15:23:46 +00:00
if ( fromType === toType )
{
isSameInventory = true ;
}
return {
from : fromInventoryItems ,
to : toInventoryItems ,
sameInventory : isSameInventory ,
2023-11-13 11:07:59 -05:00
isMail : fromType === "mail" ,
2023-03-03 15:23:46 +00:00
} ;
}
/ * *
2024-02-27 12:12:46 +00:00
* Get a two dimensional array to represent stash slots
* 0 value = free , 1 = taken
* @param pmcData Player profile
* @param sessionID session id
* @returns 2 - dimensional array
2023-03-03 15:23:46 +00:00
* /
protected getStashSlotMap ( pmcData : IPmcData , sessionID : string ) : number [ ] [ ]
{
const playerStashSize = this . getPlayerStashSize ( sessionID ) ;
2023-11-13 11:07:59 -05:00
return this . getContainerMap (
playerStashSize [ 0 ] ,
playerStashSize [ 1 ] ,
pmcData . Inventory . items ,
pmcData . Inventory . stash ,
) ;
2023-03-03 15:23:46 +00:00
}
2024-02-27 12:12:46 +00:00
/ * *
* Get a blank two - dimensional array representation of a container
* @param containerTpl Container to get data for
* @returns blank two - dimensional array
* /
2024-02-22 13:52:59 +00:00
public getContainerSlotMap ( containerTpl : string ) : number [ ] [ ]
{
const containerTemplate = this . itemHelper . getItem ( containerTpl ) [ 1 ] ;
const containerH = containerTemplate . _props . Grids [ 0 ] . _props . cellsH ;
const containerV = containerTemplate . _props . Grids [ 0 ] . _props . cellsV ;
return this . getBlankContainerMap ( containerH , containerV ) ;
}
2024-02-27 12:12:46 +00:00
/ * *
* Get a two - dimensional array representation of the players sorting table
* @param pmcData Player profile
* @returns two - dimensional array
* /
2023-07-23 11:51:04 +01:00
protected getSortingTableSlotMap ( pmcData : IPmcData ) : number [ ] [ ]
2023-03-03 15:23:46 +00:00
{
2023-07-23 12:29:00 +01:00
return this . getContainerMap ( 10 , 45 , pmcData . Inventory . items , pmcData . Inventory . sortingTable ) ;
2023-03-03 15:23:46 +00:00
}
2023-10-10 11:03:20 +00:00
/ * *
2024-02-27 12:02:03 +00:00
* Get Players Stash Size
* @param sessionID Players id
* @returns Array of 2 values , horizontal and vertical stash size
2023-10-10 11:03:20 +00:00
* /
2023-03-03 15:23:46 +00:00
protected getPlayerStashSize ( sessionID : string ) : Record < number , number >
{
2024-02-27 12:02:03 +00:00
const profile = this . profileHelper . getPmcProfile ( sessionID ) ;
2024-05-17 15:32:41 -04:00
const stashRowBonus = profile . Bonuses . find ( ( bonus ) = > bonus . type === BonusType . STASH_ROWS ) ;
2024-02-27 12:02:03 +00:00
2023-11-13 11:07:59 -05:00
// this sets automatically a stash size from items.json (its not added anywhere yet cause we still use base stash)
2023-03-03 15:23:46 +00:00
const stashTPL = this . getStashType ( sessionID ) ;
2023-10-10 11:03:20 +00:00
if ( ! stashTPL )
{
2023-10-24 16:40:34 +01:00
this . logger . error ( this . localisationService . getText ( "inventory-missing_stash_size" ) ) ;
2023-10-10 11:03:20 +00:00
}
2024-02-27 12:02:03 +00:00
const stashItemResult = this . itemHelper . getItem ( stashTPL ) ;
if ( ! stashItemResult [ 0 ] )
2023-10-10 11:03:20 +00:00
{
2023-10-24 16:40:34 +01:00
this . logger . error ( this . localisationService . getText ( "inventory-stash_not_found" , stashTPL ) ) ;
2024-02-27 12:02:03 +00:00
return ;
}
const stashItemDetails = stashItemResult [ 1 ] ;
const firstStashItemGrid = stashItemDetails . _props . Grids [ 0 ] ;
const stashH = firstStashItemGrid . _props . cellsH !== 0 ? firstStashItemGrid._props.cellsH : 10 ;
let stashV = firstStashItemGrid . _props . cellsV !== 0 ? firstStashItemGrid._props.cellsV : 66 ;
// Player has a bonus, apply to vertical size
if ( stashRowBonus )
{
stashV += stashRowBonus . value ;
2023-10-10 11:03:20 +00:00
}
2024-02-27 12:02:03 +00:00
return [ stashH , stashV ] ;
2023-03-03 15:23:46 +00:00
}
2023-10-10 11:03:20 +00:00
/ * *
* Get the players stash items tpl
* @param sessionID Player id
* @returns Stash tpl
* /
2023-07-23 11:51:04 +01:00
protected getStashType ( sessionID : string ) : string
{
const pmcData = this . profileHelper . getPmcProfile ( sessionID ) ;
2024-05-17 15:32:41 -04:00
const stashObj = pmcData . Inventory . items . find ( ( item ) = > item . _id === pmcData . Inventory . stash ) ;
2023-07-23 11:51:04 +01:00
if ( ! stashObj )
{
this . logger . error ( this . localisationService . getText ( "inventory-unable_to_find_stash" ) ) ;
}
2023-10-10 11:03:20 +00:00
return stashObj ? . _tpl ;
2023-07-23 11:51:04 +01:00
}
2023-03-03 15:23:46 +00:00
/ * *
2024-06-12 20:25:27 +01:00
* Internal helper function to transfer an item + children from one profile to another .
* @param sourceItems Inventory of the source ( can be non - player )
2023-10-10 11:03:20 +00:00
* @param toItems Inventory of the destination
2024-06-12 20:25:27 +01:00
* @param request Move request
2023-10-10 11:03:20 +00:00
* /
2024-06-12 20:25:27 +01:00
public moveItemToProfile ( sourceItems : Item [ ] , toItems : Item [ ] , request : IInventoryMoveRequestData ) : void
2023-03-03 15:23:46 +00:00
{
2024-06-12 20:25:27 +01:00
this . handleCartridges ( sourceItems , request ) ;
2023-10-10 11:03:20 +00:00
// Get all children item has, they need to move with item
2024-06-12 20:25:27 +01:00
const idsToMove = this . itemHelper . findAndReturnChildrenByItems ( sourceItems , request . item ) ;
2023-03-03 15:23:46 +00:00
for ( const itemId of idsToMove )
{
2024-06-12 20:25:27 +01:00
const itemToMove = sourceItems . find ( ( item ) = > item . _id === itemId ) ;
2023-10-10 11:03:20 +00:00
if ( ! itemToMove )
{
2024-05-21 12:40:16 +01:00
this . logger . error ( this . localisationService . getText ( "inventory-unable_to_find_item_to_move" , itemId ) ) ;
2024-07-09 09:52:58 +01:00
continue ;
2023-10-10 11:03:20 +00:00
}
// Only adjust the values for parent item, not children (their values are already correctly tied to parent)
2024-06-12 20:25:27 +01:00
if ( itemId === request . item )
2023-03-03 15:23:46 +00:00
{
2024-06-12 20:25:27 +01:00
itemToMove . parentId = request . to . id ;
itemToMove . slotId = request . to . container ;
2023-10-10 11:03:20 +00:00
2024-06-12 20:25:27 +01:00
if ( request . to . location )
2023-10-10 11:03:20 +00:00
{
// Update location object
2024-06-12 20:25:27 +01:00
itemToMove . location = request . to . location ;
2023-10-10 11:03:20 +00:00
}
else
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
// No location in request, delete it
if ( itemToMove . location )
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
delete itemToMove . location ;
2023-03-03 15:23:46 +00:00
}
}
}
2023-10-10 11:03:20 +00:00
toItems . push ( itemToMove ) ;
2024-06-12 20:25:27 +01:00
sourceItems . splice ( sourceItems . indexOf ( itemToMove ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
}
/ * *
2023-10-10 11:03:20 +00:00
* Internal helper function to move item within the same profile_f .
* @param pmcData profile to edit
2023-11-13 11:07:59 -05:00
* @param inventoryItems
2024-06-12 20:25:27 +01:00
* @param moveRequest client move request
2023-10-10 11:03:20 +00:00
* @returns True if move was successful
* /
2023-11-13 11:07:59 -05:00
public moveItemInternal (
pmcData : IPmcData ,
inventoryItems : Item [ ] ,
moveRequest : IInventoryMoveRequestData ,
2024-05-07 23:57:08 -04:00
) : { success : boolean , errorMessage? : string }
2023-03-03 15:23:46 +00:00
{
2023-03-03 17:53:28 +00:00
this . handleCartridges ( inventoryItems , moveRequest ) ;
2023-03-03 15:23:46 +00:00
2023-06-30 19:30:49 +01:00
// Find item we want to 'move'
2024-06-12 20:25:27 +01:00
const matchingInventoryItem = inventoryItems . find ( ( item ) = > item . _id === moveRequest . item ) ;
2023-10-10 11:03:20 +00:00
if ( ! matchingInventoryItem )
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
const errorMesage = ` Unable to move item: ${ moveRequest . item } , cannot find in inventory ` ;
this . logger . error ( errorMesage ) ;
2023-03-03 15:23:46 +00:00
2023-11-13 12:38:16 -05:00
return { success : false , errorMessage : errorMesage } ;
2023-10-10 11:03:20 +00:00
}
2023-11-13 11:07:59 -05:00
this . logger . debug (
` ${ moveRequest . Action } item: ${ moveRequest . item } from slotid: ${ matchingInventoryItem . slotId } to container: ${ moveRequest . to . container } ` ,
) ;
2023-03-03 15:23:46 +00:00
2024-06-12 20:25:27 +01:00
// Don't move shells from camora to cartridges (happens when loading shells into mts-255 revolver shotgun)
if ( matchingInventoryItem . slotId ? . includes ( "camora_" ) && moveRequest . to . container === "cartridges" )
2023-10-10 11:03:20 +00:00
{
2023-11-13 11:07:59 -05:00
this . logger . warning (
this . localisationService . getText ( "inventory-invalid_move_to_container" , {
slotId : matchingInventoryItem.slotId ,
container : moveRequest.to.container ,
} ) ,
) ;
2023-03-03 15:23:46 +00:00
2023-11-13 12:38:16 -05:00
return { success : true } ;
2023-10-10 11:03:20 +00:00
}
2023-03-03 17:53:28 +00:00
2023-10-10 11:03:20 +00:00
// Edit items details to match its new location
matchingInventoryItem . parentId = moveRequest . to . id ;
matchingInventoryItem . slotId = moveRequest . to . container ;
2024-06-12 20:25:27 +01:00
// Ensure fastpanel dict updates when item was moved out of fast-panel-accessible slot
2023-10-10 11:03:20 +00:00
this . updateFastPanelBinding ( pmcData , matchingInventoryItem ) ;
2024-06-12 20:25:27 +01:00
// Item has location propery, ensure its value is handled
2023-10-10 11:03:20 +00:00
if ( "location" in moveRequest . to )
{
matchingInventoryItem . location = moveRequest . to . location ;
}
else
{
2024-06-12 20:25:27 +01:00
// Moved from slot with location to one without, clean up
2023-10-10 11:03:20 +00:00
if ( matchingInventoryItem . location )
2023-06-30 19:30:49 +01:00
{
2023-10-10 11:03:20 +00:00
delete matchingInventoryItem . location ;
2023-03-03 15:23:46 +00:00
}
}
2023-10-10 11:03:20 +00:00
2023-11-13 12:38:16 -05:00
return { success : true } ;
2023-03-03 15:23:46 +00:00
}
2023-03-03 17:53:28 +00:00
/ * *
* Update fast panel bindings when an item is moved into a container that doesnt allow quick slot access
* @param pmcData Player profile
* @param itemBeingMoved item being moved
* /
protected updateFastPanelBinding ( pmcData : IPmcData , itemBeingMoved : Item ) : void
{
2024-06-12 20:25:27 +01:00
// Find matching _id in fast panel
const fastPanelSlot = Object . entries ( pmcData . Inventory . fastPanel )
. find ( ( [ itemId ] ) = > itemId === itemBeingMoved . _id ) ;
if ( ! fastPanelSlot )
2023-03-03 17:53:28 +00:00
{
2024-06-12 20:25:27 +01:00
return ;
}
2023-11-13 11:07:59 -05:00
2024-06-12 20:25:27 +01:00
// Get moved items parent (should be container item was put into)
const itemParent = pmcData . Inventory . items . find ( ( item ) = > item . _id === itemBeingMoved . parentId ) ;
if ( ! itemParent )
{
return ;
}
2023-03-03 17:53:28 +00:00
2024-06-12 20:25:27 +01:00
// Reset fast panel value if item was moved to a container other than pocket/rig (cant be used from fastpanel)
const wasMovedToFastPanelAccessibleContainer = [ "pockets" , "tacticalvest" ] . includes ( itemParent ? . slotId ? . toLowerCase ( ) ? ? "" ) ;
if ( ! wasMovedToFastPanelAccessibleContainer )
{
pmcData . Inventory . fastPanel [ fastPanelSlot [ 0 ] ] = "" ;
2023-03-03 17:53:28 +00:00
}
}
2023-03-03 15:23:46 +00:00
/ * *
2023-11-13 11:07:59 -05:00
* Internal helper function to handle cartridges in inventory if any of them exist .
* /
2024-06-12 20:25:27 +01:00
protected handleCartridges ( items : Item [ ] , request : IInventoryMoveRequestData ) : void
2023-03-03 15:23:46 +00:00
{
2024-06-12 20:25:27 +01:00
// Not moving item into a cartridge slot, skip
if ( request . to . container !== "cartridges" )
2023-03-03 15:23:46 +00:00
{
2024-06-12 20:25:27 +01:00
return ;
2023-03-03 15:23:46 +00:00
}
2024-06-12 20:25:27 +01:00
// Get a count of cartridges in existing magazine
const cartridgeCount = items . filter ( ( item ) = > item . parentId === request . to . id ) . length ;
request . to . location = cartridgeCount ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Get details for how a random loot container should be handled , max rewards , possible reward tpls
* @param itemTpl Container being opened
* @returns Reward details
* /
public getRandomLootContainerRewardDetails ( itemTpl : string ) : RewardDetails
{
return this . inventoryConfig . randomLootContainers [ itemTpl ] ;
}
2023-06-20 16:07:05 +01:00
public getInventoryConfig ( ) : IInventoryConfig
{
return this . inventoryConfig ;
}
2024-01-20 20:15:03 +00:00
/ * *
* Recursively checks if the given item is
* inside the stash , that is it has the stash as
* ancestor with slotId = hideout
* @param pmcData Player profile
* @param itemToCheck Item to look for
* @returns True if item exists inside stash
* /
public isItemInStash ( pmcData : IPmcData , itemToCheck : Item ) : boolean
{
2024-06-14 19:18:55 +01:00
// Create recursive helper function
const isParentInStash = ( itemId : string ) : boolean = >
2024-01-20 20:15:03 +00:00
{
2024-06-14 19:18:55 +01:00
// Item not found / has no parent
const item = pmcData . Inventory . items . find ( ( item ) = > item . _id === itemId ) ;
if ( ! item || ! item . parentId )
2024-01-20 20:15:03 +00:00
{
2024-06-14 19:18:55 +01:00
return false ;
2024-01-20 20:15:03 +00:00
}
2024-06-14 19:18:55 +01:00
// Root level. Items parent is the stash with slotId "hideout"
if ( item . parentId === pmcData . Inventory . stash && item . slotId === "hideout" )
2024-01-20 20:15:03 +00:00
{
2024-06-14 19:18:55 +01:00
return true ;
2024-01-20 20:15:03 +00:00
}
2024-06-13 13:41:29 +01:00
2024-06-14 19:18:55 +01:00
// Recursive case: Check the items parent
return isParentInStash ( item . parentId ) ;
} ;
// Start recursive check
return isParentInStash ( itemToCheck . _id ) ;
2024-01-20 20:15:03 +00:00
}
2023-03-03 15:23:46 +00:00
}
2024-05-13 17:58:17 +00:00
// eslint-disable-next-line @typescript-eslint/no-namespace
2023-03-03 15:23:46 +00:00
namespace InventoryHelper
{
export interface InventoryItemHash
{
2024-05-07 23:57:08 -04:00
byItemId : Record < string , Item >
byParentId : Record < string , Item [ ] >
2023-03-03 15:23:46 +00:00
}
2023-11-13 11:07:59 -05:00
}