2023-03-03 16:23:46 +01:00
import { inject , injectable } from "tsyringe" ;
2023-10-19 19:21:17 +02:00
import { ContainerHelper } from "@spt-aki/helpers/ContainerHelper" ;
import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper" ;
import { ItemHelper } from "@spt-aki/helpers/ItemHelper" ;
import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper" ;
2024-01-13 16:00:31 +01:00
import { PresetHelper } from "@spt-aki/helpers/PresetHelper" ;
2023-10-19 19:21:17 +02:00
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper" ;
import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper" ;
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData" ;
import { Inventory } from "@spt-aki/models/eft/common/tables/IBotBase" ;
import { Item , Location , Upd } from "@spt-aki/models/eft/common/tables/IItem" ;
2024-01-14 11:09:43 +01:00
import { IAddItemDirectRequest } from "@spt-aki/models/eft/inventory/IAddItemDirectRequest" ;
2024-02-17 21:53:47 +01:00
import { AddItem } from "@spt-aki/models/eft/inventory/IAddItemRequestData" ;
2023-10-19 19:21:17 +02:00
import { IAddItemTempObject } from "@spt-aki/models/eft/inventory/IAddItemTempObject" ;
2024-02-01 10:48:46 +01:00
import { IAddItemsDirectRequest } from "@spt-aki/models/eft/inventory/IAddItemsDirectRequest" ;
2023-10-19 19:21:17 +02:00
import { IInventoryMergeRequestData } from "@spt-aki/models/eft/inventory/IInventoryMergeRequestData" ;
import { IInventoryMoveRequestData } from "@spt-aki/models/eft/inventory/IInventoryMoveRequestData" ;
import { IInventoryRemoveRequestData } from "@spt-aki/models/eft/inventory/IInventoryRemoveRequestData" ;
import { IInventorySplitRequestData } from "@spt-aki/models/eft/inventory/IInventorySplitRequestData" ;
2024-02-17 21:53:47 +01:00
import { IInventoryTransferRequestData } from "@spt-aki/models/eft/inventory/IInventoryTransferRequestData" ;
2023-10-19 19:21:17 +02:00
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse" ;
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses" ;
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes" ;
import { Traders } from "@spt-aki/models/enums/Traders" ;
import { IInventoryConfig , RewardDetails } from "@spt-aki/models/spt/config/IInventoryConfig" ;
import { ILogger } from "@spt-aki/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt-aki/servers/ConfigServer" ;
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer" ;
import { FenceService } from "@spt-aki/services/FenceService" ;
import { LocalisationService } from "@spt-aki/services/LocalisationService" ;
import { HashUtil } from "@spt-aki/utils/HashUtil" ;
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil" ;
import { JsonUtil } from "@spt-aki/utils/JsonUtil" ;
2023-03-03 16:23:46 +01:00
2024-02-01 10:58:20 +01:00
export interface IOwnerInventoryItems
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
/** Inventory items from source */
2023-11-13 17:07:59 +01:00
from : Item [ ] ;
2023-10-10 13:03:20 +02:00
/** Inventory items at destination */
2023-11-13 17:07:59 +01:00
to : Item [ ] ;
sameInventory : boolean ;
isMail : boolean ;
2023-03-03 16:23:46 +01:00
}
@injectable ( )
export class InventoryHelper
{
protected inventoryConfig : IInventoryConfig ;
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "JsonUtil" ) protected jsonUtil : JsonUtil ,
@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 16:00:31 +01:00
@inject ( "PresetHelper" ) protected presetHelper : PresetHelper ,
2023-03-03 16:23:46 +01:00
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
2023-11-13 17:07:59 +01:00
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
2023-03-03 16:23:46 +01:00
)
{
this . inventoryConfig = this . configServer . getConfig ( ConfigTypes . INVENTORY ) ;
}
2024-02-01 10:48:46 +01:00
/ * *
2024-02-01 11:24:10 +01:00
* Add multiple items to player stash ( assuming they all fit )
2024-02-01 10:48:46 +01:00
* @param sessionId Session id
2024-02-01 11:24:10 +01:00
* @param request IAddItemsDirectRequest request
2024-02-01 10:48:46 +01:00
* @param pmcData Player profile
* @param output Client response object
* /
2024-02-02 19:54:07 +01:00
public addItemsToStash (
sessionId : string ,
request : IAddItemsDirectRequest ,
pmcData : IPmcData ,
output : IItemEventRouterResponse ,
) : void
2024-02-01 10:48:46 +01:00
{
// Check all items fit into inventory before adding
if ( ! this . canPlaceItemsInInventory ( sessionId , request . itemsWithModsToAdd ) )
{
2024-02-01 11:24:10 +01:00
// No space, exit
2024-02-02 19:54:07 +01:00
this . httpResponse . appendErrorToOutput ( output , this . localisationService . getText ( "inventory-no_stash_space" ) ) ;
2024-02-01 10:48:46 +01:00
return ;
}
for ( const itemToAdd of request . itemsWithModsToAdd )
{
const addItemRequest : IAddItemDirectRequest = {
itemWithModsToAdd : itemToAdd ,
foundInRaid : request.foundInRaid ,
useSortingTable : request.useSortingTable ,
2024-02-02 19:54:07 +01:00
callback : request.callback ,
2024-02-01 10:48:46 +01:00
} ;
// Add to player inventory
this . addItemToStash ( sessionId , addItemRequest , pmcData , output ) ;
if ( output . warnings . length > 0 )
{
return ;
}
}
}
2023-03-03 16:23:46 +01:00
/ * *
2024-01-14 11:09:43 +01: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 19:54:07 +01:00
public addItemToStash (
sessionId : string ,
request : IAddItemDirectRequest ,
pmcData : IPmcData ,
output : IItemEventRouterResponse ,
) : void
2024-01-14 11:09:43 +01:00
{
const itemWithModsToAddClone = this . jsonUtil . clone ( request . itemWithModsToAdd ) ;
2024-01-14 23:30:05 +01:00
// Get stash layouts ready for use
2024-01-14 11:09:43 +01:00
const stashFS2D = this . getStashSlotMap ( pmcData , sessionId ) ;
const sortingTableFS2D = this . getSortingTableSlotMap ( pmcData ) ;
2024-01-14 22:12:56 +01:00
// Find empty slot in stash for item being added - adds 'location' + parentid + slotId properties to root item
2024-01-16 20:00:21 +01:00
this . placeItemInInventory (
2024-01-14 11:09:43 +01:00
stashFS2D ,
sortingTableFS2D ,
itemWithModsToAddClone ,
pmcData . Inventory ,
request . useSortingTable ,
output ,
) ;
2024-01-16 20:00:21 +01:00
if ( output . warnings . length > 0 )
2024-01-14 11:09:43 +01:00
{
// Failed to place, error out
2024-01-16 20:00:21 +01:00
return ;
2024-01-14 11:09:43 +01:00
}
// Apply/remove FiR to item + mods
2024-01-14 22:12:56 +01:00
this . setFindInRaidStatusForItem ( itemWithModsToAddClone , request . foundInRaid ) ;
2024-01-14 11:09:43 +01:00
// Remove trader properties from root item
this . removeTraderRagfairRelatedUpdProperties ( itemWithModsToAddClone [ 0 ] . upd ) ;
// Run callback
try
{
if ( typeof request . callback === "function" )
{
2024-01-15 15:25:17 +01:00
request . callback ( itemWithModsToAddClone [ 0 ] . upd . StackObjectsCount ) ;
2024-01-14 11:09:43 +01:00
}
}
catch ( err )
{
// Callback failed
2024-01-15 15:25:17 +01:00
const message = typeof err ? . message === "string"
? err . message
2024-01-14 11:09:43 +01:00
: this . localisationService . getText ( "http-unknown_error" ) ;
2024-01-16 20:00:21 +01:00
this . httpResponse . appendErrorToOutput ( output , message ) ;
return ;
2024-01-14 11:09:43 +01:00
}
2024-01-14 23:30:05 +01:00
// Add item + mods to output and profile inventory
2024-01-14 11:09:43 +01:00
output . profileChanges [ sessionId ] . items . new . push ( . . . itemWithModsToAddClone ) ;
pmcData . Inventory . items . push ( . . . itemWithModsToAddClone ) ;
2024-02-02 19:54:07 +01:00
this . logger . debug (
` Added ${ itemWithModsToAddClone [ 0 ] . upd ? . StackObjectsCount ? ? 1 } item: ${
itemWithModsToAddClone [ 0 ] . _tpl
} with : $ { itemWithModsToAddClone . length - 1 } mods to inventory ` ,
) ;
2024-01-14 11:09:43 +01:00
}
/ * *
2024-01-14 22:12:56 +01:00
* Set FiR status for an item + its children
* @param itemWithChildren An item
* @param foundInRaid Item was found in raid
* /
private setFindInRaidStatusForItem ( itemWithChildren : Item [ ] , foundInRaid : boolean )
{
for ( const item of itemWithChildren )
{
// Ensure item has upd object
if ( ! item . upd )
{
item . upd = { } ;
}
if ( foundInRaid )
{
item . upd . SpawnedInSession = foundInRaid ;
}
else
{
if ( delete item . upd . SpawnedInSession )
{
delete item . upd . SpawnedInSession ;
}
}
}
}
2024-01-13 16:01:29 +01:00
/ * *
2024-01-14 11:09:43 +01:00
* Remove properties from a Upd object used by a trader / ragfair
2024-01-13 16:01:29 +01:00
* @param upd Object to update
* /
2024-01-14 11:09:43 +01:00
protected removeTraderRagfairRelatedUpdProperties ( upd : Upd ) : void
2024-01-13 16:01:29 +01: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 14:52:59 +01: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 19:54:07 +01:00
public canPlaceItemsInInventory ( sessionId : string , itemsWithChildren : Item [ ] [ ] ) : boolean
2024-02-01 00:39:01 +01:00
{
const pmcData = this . profileHelper . getPmcProfile ( sessionId ) ;
2024-02-09 18:13:19 +01:00
const stashFS2D = this . jsonUtil . clone ( this . getStashSlotMap ( pmcData , sessionId ) ) ;
2024-02-01 00:39:01 +01:00
for ( const itemWithChildren of itemsWithChildren )
{
2024-02-22 14:52:59 +01:00
if ( this . canPlaceItemInContainer ( stashFS2D , itemWithChildren ) )
{
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 )
{
if ( this . canPlaceItemInContainer ( containerFS2D , itemWithChildren ) )
2024-02-01 00:39:01 +01:00
{
return false ;
}
}
return true ;
}
2024-02-22 14:52:59 +01: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-02-01 00:39:01 +01: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 14:52:59 +01:00
const findSlotResult = this . containerHelper . findSlotForItem ( containerFS2D , itemSize [ 0 ] , itemSize [ 1 ] ) ;
2024-02-01 00:39:01 +01:00
if ( findSlotResult . success )
{
try
{
2024-02-09 16:13:49 +01:00
this . containerHelper . fillContainerMapWithItem (
2024-02-22 14:52:59 +01:00
containerFS2D ,
2024-02-01 00:39:01 +01:00
findSlotResult . x ,
findSlotResult . y ,
2024-02-09 18:13:19 +01:00
itemSize [ 0 ] ,
itemSize [ 1 ] ,
2024-02-09 16:13:49 +01:00
findSlotResult . rotation ,
) ;
2024-02-01 00:39:01 +01:00
}
catch ( err )
{
2024-02-09 18:13:19 +01:00
const errorText = ( typeof err === "string" ) ? ` -> ${ err } ` : err . message ;
2024-02-09 16:18:49 +01:00
this . logger . error ( ` Unable to fit item into inventory: ${ errorText } ` ) ;
2024-02-01 00:39:01 +01:00
return false ;
}
// Success! exit
return ;
}
return true ;
}
2024-02-22 14:52:59 +01: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
* /
public placeItemInContainer ( containerFS2D : number [ ] [ ] , itemWithChildren : Item [ ] , containerId : string ) : void
{
// 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 )
{
const errorText = ( typeof err === "string" ) ? ` -> ${ err } ` : err . message ;
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 ;
rootItemAdded . slotId = "hideout" ;
rootItemAdded . location = {
x : findSlotResult.x ,
y : findSlotResult.y ,
r : findSlotResult.rotation ? 1 : 0 ,
rotation : findSlotResult.rotation ,
} ;
// Success! exit
return ;
}
}
2024-02-09 16:48:57 +01: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
* @param itemWithChildren Item to place
* @param playerInventory
* @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 11:09:43 +01:00
protected placeItemInInventory (
stashFS2D : number [ ] [ ] ,
sortingTableFS2D : number [ ] [ ] ,
itemWithChildren : Item [ ] ,
playerInventory : Inventory ,
useSortingTable : boolean ,
2024-02-02 19:54:07 +01:00
output : IItemEventRouterResponse ,
) : void
2024-01-14 11:09:43 +01:00
{
2024-01-14 22:12:56 +01: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 11:09:43 +01:00
const findSlotResult = this . containerHelper . findSlotForItem ( stashFS2D , itemSize [ 0 ] , itemSize [ 1 ] ) ;
if ( findSlotResult . success )
{
try
{
2024-02-09 16:13:49 +01:00
this . containerHelper . fillContainerMapWithItem (
2024-01-14 11:09:43 +01:00
stashFS2D ,
findSlotResult . x ,
findSlotResult . y ,
2024-02-09 18:13:19 +01:00
itemSize [ 0 ] ,
itemSize [ 1 ] ,
2024-02-09 16:13:49 +01:00
findSlotResult . rotation ,
) ;
2024-01-14 11:09:43 +01:00
}
catch ( err )
{
2024-02-09 18:13:19 +01:00
const errorText = ( typeof err === "string" ) ? ` -> ${ err } ` : err . message ;
2024-01-14 11:09:43 +01:00
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
2024-01-16 20:00:21 +01:00
this . httpResponse . appendErrorToOutput (
2024-01-14 11:09:43 +01:00
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
2024-01-16 20:00:21 +01:00
return ;
2024-01-14 11:09:43 +01:00
}
// Store details for object, incuding container item will be placed in
2024-01-14 22:12:56 +01:00
rootItem . parentId = playerInventory . stash ;
rootItem . slotId = "hideout" ;
rootItem . location = {
2024-01-14 11:09:43 +01: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 18:13:19 +01:00
2024-01-14 11:09:43 +01:00
try
{
2024-02-09 16:13:49 +01:00
this . containerHelper . fillContainerMapWithItem (
2024-01-14 11:09:43 +01:00
sortingTableFS2D ,
findSortingSlotResult . x ,
findSortingSlotResult . y ,
2024-02-09 18:13:19 +01:00
itemSize [ 0 ] ,
itemSize [ 1 ] ,
2024-02-09 16:13:49 +01:00
findSortingSlotResult . rotation ,
) ;
2024-01-14 11:09:43 +01:00
}
catch ( err )
{
const errorText = typeof err === "string" ? ` -> ${ err } ` : "" ;
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
2024-01-16 20:00:21 +01:00
this . httpResponse . appendErrorToOutput (
2024-01-14 11:09:43 +01:00
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
2024-01-16 20:00:21 +01:00
return ;
2024-01-14 11:09:43 +01: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-02-02 19:54:07 +01:00
this . httpResponse . appendErrorToOutput ( output , this . localisationService . getText ( "inventory-no_stash_space" ) ) ;
2024-01-16 20:00:21 +01:00
2024-02-02 19:54:07 +01:00
return ;
2024-01-14 11:09:43 +01:00
}
}
2023-03-03 16:23:46 +01:00
/ * *
* @param assortItems Items to add to inventory
* @param requestItem Details of purchased item to add to inventory
* @param result Array split stacks are added to
* /
2024-02-09 16:57:54 +01:00
protected splitStackIntoSmallerChildStacks (
assortItems : Item [ ] ,
requestItem : AddItem ,
result : IAddItemTempObject [ ] ,
) : void
2023-03-03 16:23:46 +01:00
{
for ( const item of assortItems )
2024-01-14 22:12:56 +01:00
{
// Iterated item matches root item
2023-03-03 16:23:46 +01:00
if ( item . _id === requestItem . item_id )
{
// Get item details from db
const itemDetails = this . itemHelper . getItem ( item . _tpl ) [ 1 ] ;
const itemToAdd : IAddItemTempObject = {
itemRef : item ,
count : requestItem.count ,
2024-01-13 16:00:31 +01:00
isPreset : ! ! requestItem . sptIsPreset ,
2023-11-13 17:07:59 +01:00
} ;
2023-03-03 16:23:46 +01:00
// Split stacks if the size is higher than allowed by items StackMaxSize property
let maxStackCount = 1 ;
if ( requestItem . count > itemDetails . _props . StackMaxSize )
{
let remainingCountOfItemToAdd = requestItem . count ;
2023-11-13 18:29:16 +01:00
const calc = requestItem . count
- ( Math . floor ( requestItem . count / itemDetails . _props . StackMaxSize )
* itemDetails . _props . StackMaxSize ) ;
2023-03-03 16:23:46 +01:00
2023-11-13 18:29:16 +01:00
maxStackCount = ( calc > 0 )
? maxStackCount + Math . floor ( remainingCountOfItemToAdd / itemDetails . _props . StackMaxSize )
: Math . floor ( remainingCountOfItemToAdd / itemDetails . _props . StackMaxSize ) ;
2023-03-03 16:23:46 +01:00
// Iterate until totalCountOfPurchasedItem is 0
for ( let i = 0 ; i < maxStackCount ; i ++ )
{
// Keep splitting items into stacks until none left
if ( remainingCountOfItemToAdd > 0 )
{
2024-01-08 13:41:52 +01:00
const newChildItemToAdd = this . jsonUtil . clone ( itemToAdd ) ;
2023-03-03 16:23:46 +01:00
if ( remainingCountOfItemToAdd > itemDetails . _props . StackMaxSize )
{
// Reduce total count of item purchased by stack size we're going to add to inventory
remainingCountOfItemToAdd -= itemDetails . _props . StackMaxSize ;
2024-01-08 13:41:52 +01:00
newChildItemToAdd . count = itemDetails . _props . StackMaxSize ;
2023-03-03 16:23:46 +01:00
}
else
{
2024-01-08 13:41:52 +01:00
newChildItemToAdd . count = remainingCountOfItemToAdd ;
2023-03-03 16:23:46 +01:00
}
2024-01-08 13:41:52 +01:00
result . push ( newChildItemToAdd ) ;
2023-03-03 16:23:46 +01:00
}
}
}
else
{
// Item count is within allowed stack size, just add it
result . push ( itemToAdd ) ;
}
}
}
}
/ * *
2023-10-10 13:03:20 +02:00
* Handle Remove event
2023-07-15 12:00:35 +02:00
* Remove item from player inventory + insured items array
2023-10-10 13:03:20 +02:00
* Also deletes child items
* @param profile Profile to remove item from ( pmc or scav )
2023-03-03 16:23:46 +01:00
* @param itemId Items id to remove
* @param sessionID Session id
2024-02-14 15:59:43 +01:00
* @param output OPTIONAL - IItemEventRouterResponse
2023-03-03 16:23:46 +01:00
* /
2023-11-13 17:07:59 +01:00
public removeItem (
profile : IPmcData ,
itemId : string ,
sessionID : string ,
output : IItemEventRouterResponse = undefined ,
2024-02-14 15:59:43 +01:00
) : void
2023-03-03 16:23:46 +01:00
{
if ( ! itemId )
2023-10-10 13:03:20 +02:00
{
this . logger . warning ( "No itemId supplied, unable to remove item from inventory" ) ;
2024-02-14 15:59:43 +01:00
return ;
2023-10-10 13:03:20 +02:00
}
2023-03-03 16:23:46 +01:00
2023-10-10 13:03:20 +02: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 16:23:46 +01:00
2023-10-10 13:03:20 +02:00
// We have output object, inform client of item deletion
2023-03-03 16:23:46 +01:00
if ( output )
2023-10-10 13:03:20 +02:00
{
2023-11-13 18:38:16 +01:00
output . profileChanges [ sessionID ] . items . del . push ( { _id : itemId } ) ;
2023-10-10 13:03:20 +02:00
}
2023-03-03 16:23:46 +01:00
2023-10-10 13:03:20 +02:00
for ( const childId of itemToRemoveWithChildren )
2023-03-03 16:23:46 +01: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.
2023-11-13 17:07:59 +01:00
const inventoryIndex = inventoryItems . findIndex ( ( item ) = > item . _id === childId ) ;
2023-03-03 16:23:46 +01:00
if ( inventoryIndex > - 1 )
{
inventoryItems . splice ( inventoryIndex , 1 ) ;
}
2023-10-10 13:03:20 +02:00
if ( inventoryIndex === - 1 )
{
2023-11-13 17:07:59 +01:00
this . logger . warning (
` Unable to remove item with Id: ${ childId } as it was not found in inventory ${ profile . _id } ` ,
) ;
2023-10-10 13:03:20 +02:00
}
2023-11-13 17:07:59 +01:00
const insuredIndex = insuredItems . findIndex ( ( item ) = > item . itemId === childId ) ;
2023-03-03 16:23:46 +01:00
if ( insuredIndex > - 1 )
{
insuredItems . splice ( insuredIndex , 1 ) ;
}
}
}
2023-11-13 17:07:59 +01:00
public removeItemAndChildrenFromMailRewards (
sessionId : string ,
removeRequest : IInventoryRemoveRequestData ,
output : IItemEventRouterResponse ,
2024-01-16 13:21:42 +01:00
) : void
2023-10-10 13:03:20 +02: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 )
{
2023-11-13 17:07:59 +01:00
const messageWithReward = dialog . messages . find ( ( x ) = > x . _id === removeRequest . fromOwner . id ) ;
2023-10-10 13:03:20 +02:00
if ( messageWithReward )
{
// Find item + any possible children and remove them from mails items array
2023-11-13 17:07:59 +01:00
const itemWithChildern = this . itemHelper . findAndReturnChildrenAsItems (
messageWithReward . items . data ,
removeRequest . item ,
) ;
2023-10-10 13:03:20 +02: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 17:07:59 +01: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 13:03:20 +02: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 ;
}
}
}
2023-11-13 17:07:59 +01:00
public removeItemByCount (
pmcData : IPmcData ,
itemId : string ,
count : number ,
sessionID : string ,
output : IItemEventRouterResponse = undefined ,
) : IItemEventRouterResponse
2023-03-03 16:23:46 +01:00
{
if ( ! itemId )
2023-11-13 17:07:59 +01:00
{
2023-03-03 16:23:46 +01:00
return output ;
2023-11-13 17:07:59 +01:00
}
2023-03-03 16:23:46 +01:00
const itemsToReduce = this . itemHelper . findAndReturnChildrenAsItems ( pmcData . Inventory . items , itemId ) ;
let remainingCount = count ;
for ( const itemToReduce of itemsToReduce )
{
const itemCount = this . itemHelper . getItemStackSize ( itemToReduce ) ;
// remove whole stack
if ( remainingCount >= itemCount )
{
remainingCount -= itemCount ;
this . removeItem ( pmcData , itemToReduce . _id , sessionID , output ) ;
}
else
{
itemToReduce . upd . StackObjectsCount -= remainingCount ;
remainingCount = 0 ;
if ( output )
2023-11-13 17:07:59 +01:00
{
2023-03-03 16:23:46 +01:00
output . profileChanges [ sessionID ] . items . change . push ( itemToReduce ) ;
2023-11-13 17:07:59 +01:00
}
2023-03-03 16:23:46 +01:00
}
if ( remainingCount === 0 )
2023-11-13 17:07:59 +01:00
{
2023-03-03 16:23:46 +01:00
break ;
2023-11-13 17:07:59 +01:00
}
2023-03-03 16:23:46 +01:00
}
return output ;
}
/ * C a l c u l a t e S i z e o f i t e m i n p u t
* inputs Item template ID , Item Id , InventoryItem ( item from inventory having _id and _tpl )
* outputs [ width , height ]
* /
2024-01-14 11:09:43 +01:00
public getItemSize ( itemTpl : string , itemID : string , inventoryItems : Item [ ] ) : number [ ]
2023-03-03 16:23:46 +01:00
{
// -> Prepares item Width and height returns [sizeX, sizeY]
2024-01-14 11:09:43 +01:00
return this . getSizeByInventoryItemHash ( itemTpl , itemID , this . getInventoryItemHash ( inventoryItems ) ) ;
2023-03-03 16:23:46 +01:00
}
// note from 2027: there IS a thing i didn't explore and that is Merges With Children
// -> Prepares item Width and height returns [sizeX, sizeY]
2023-11-13 17:07:59 +01:00
protected getSizeByInventoryItemHash (
itemTpl : string ,
itemID : string ,
inventoryItemHash : InventoryHelper.InventoryItemHash ,
) : number [ ]
2023-03-03 16:23:46 +01:00
{
const toDo = [ itemID ] ;
const result = this . itemHelper . getItem ( itemTpl ) ;
const tmpItem = result [ 1 ] ;
2023-04-12 16:51:52 +02:00
// Invalid item or no object
2023-03-03 16:23:46 +01:00
if ( ! ( result [ 0 ] && result [ 1 ] ) )
{
this . logger . error ( this . localisationService . getText ( "inventory-invalid_item_missing_from_db" , itemTpl ) ) ;
}
2023-07-19 12:00:34 +02:00
// Item found but no _props property
2023-04-12 16:51:52 +02:00
if ( tmpItem && ! tmpItem . _props )
2023-03-03 16:23:46 +01:00
{
2023-11-13 17:07:59 +01:00
this . localisationService . getText ( "inventory-item_missing_props_property" , {
itemTpl : itemTpl ,
itemName : tmpItem?._name ,
} ) ;
2023-04-12 16:51:52 +02: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 ) ) ;
return [ 1 , 1 ] ;
2023-03-03 16:23:46 +01: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 ;
const skipThisItems : string [ ] = [
BaseClasses . BACKPACK ,
BaseClasses . SEARCHABLE_ITEM ,
2023-11-13 17:07:59 +01:00
BaseClasses . SIMPLE_CONTAINER ,
2023-03-03 16:23:46 +01:00
] ;
const rootFolded = rootItem . upd ? . Foldable && rootItem . upd . Foldable . Folded === true ;
2023-11-13 17:07:59 +01:00
// The item itself is collapsible
2023-03-03 16:23:46 +01:00
if ( foldableWeapon && ( foldedSlot === undefined || foldedSlot === "" ) && rootFolded )
{
outX -= tmpItem . _props . SizeReduceRight ;
}
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 17:07:59 +01:00
// Filtering child items outside of mod slots, such as those inside containers, without counting their ExtraSize attribute
2023-03-03 16:23:46 +01: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 17:07:59 +01:00
this . logger . error (
this . localisationService . getText (
"inventory-get_item_size_item_not_found_by_tpl" ,
item . _tpl ,
) ,
) ;
2023-03-03 16:23:46 +01: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 19:54:07 +01:00
2024-01-16 20:00:21 +01:00
if ( childFoldable && rootFolded && childFolded )
2023-03-03 16:23:46 +01: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 17:07:59 +01:00
outY + sizeUp + sizeDown + forcedUp + forcedDown ,
2023-03-03 16:23:46 +01:00
] ;
}
protected getInventoryItemHash ( inventoryItem : Item [ ] ) : InventoryHelper . InventoryItemHash
{
2023-11-13 18:38:16 +01:00
const inventoryItemHash : InventoryHelper.InventoryItemHash = { byItemId : { } , byParentId : { } } ;
2023-03-03 16:23:46 +01:00
2023-10-31 23:52:09 +01:00
for ( const item of inventoryItem )
2023-03-03 16:23:46 +01:00
{
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 ;
}
2024-02-22 14:52:59 +01:00
protected getBlankContainerMap ( containerH : number , containerY : number ) : number [ ] [ ]
2023-03-03 16:23:46 +01:00
{
2024-02-22 14:52:59 +01:00
return Array ( containerY ) . fill ( 0 ) . map ( ( ) = > Array ( containerH ) . fill ( 0 ) ) ;
}
public getContainerMap ( containerH : number , containerV : number , itemList : Item [ ] , containerId : string ) : number [ ] [ ]
{
const container2D : number [ ] [ ] = this . getBlankContainerMap ( containerH , containerV ) ;
2023-03-03 16:23:46 +01: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
2023-11-13 18:31:52 +01:00
const fH =
( ( item . location as Location ) . r === 1 || ( item . location as Location ) . r === "Vertical"
|| ( item . location as Location ) . rotation === "Vertical" )
? iW
: iH ;
const fW =
( ( item . location as Location ) . r === 1 || ( item . location as Location ) . r === "Vertical"
|| ( item . location as Location ) . rotation === "Vertical" )
? iH
: iW ;
2023-03-03 16:23:46 +01: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 17:07:59 +01:00
this . logger . error (
this . localisationService . getText ( "inventory-unable_to_fill_container" , {
id : item._id ,
error : e ,
} ) ,
) ;
2023-03-03 16:23:46 +01:00
}
}
}
return container2D ;
}
/ * *
2023-10-10 13:03:20 +02:00
* Return the inventory that needs to be modified ( scav / pmc etc )
* Changes made to result apply to character inventory
2023-03-03 16:23:46 +01:00
* Based on the item action , determine whose inventories we should be looking at for from and to .
2023-10-10 13:03:20 +02:00
* @param request Item interaction request
* @param sessionId Session id / playerid
* @returns OwnerInventoryItems with inventory of player / scav to adjust
2023-03-03 16:23:46 +01:00
* /
2023-11-13 17:07:59 +01:00
public getOwnerInventoryItems (
2024-02-22 14:52:59 +01:00
request :
| IInventoryMoveRequestData
| IInventorySplitRequestData
| IInventoryMergeRequestData
| IInventoryTransferRequestData ,
2023-11-13 17:07:59 +01:00
sessionId : string ,
2024-02-01 10:58:20 +01:00
) : IOwnerInventoryItems
2023-03-03 16:23:46 +01:00
{
let isSameInventory = false ;
2023-10-10 13:03:20 +02:00
const pmcItems = this . profileHelper . getPmcProfile ( sessionId ) . Inventory . items ;
const scavData = this . profileHelper . getScavProfile ( sessionId ) ;
2023-03-03 16:23:46 +01:00
let fromInventoryItems = pmcItems ;
let fromType = "pmc" ;
2023-10-10 13:03:20 +02:00
if ( request . fromOwner )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
if ( request . fromOwner . id === scavData . _id )
2023-03-03 16:23:46 +01:00
{
fromInventoryItems = scavData . Inventory . items ;
fromType = "scav" ;
}
2023-10-10 13:03:20 +02:00
else if ( request . fromOwner . type . toLocaleLowerCase ( ) === "mail" )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
// Split requests dont use 'use' but 'splitItem' property
2023-11-13 18:31:52 +01:00
const item = "splitItem" in request ? request.splitItem : request.item ;
2023-10-10 13:03:20 +02:00
fromInventoryItems = this . dialogueHelper . getMessageItemContents ( request . fromOwner . id , sessionId , item ) ;
2023-03-03 16:23:46 +01: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 13:03:20 +02:00
// Destination is scav inventory, update values
if ( request . toOwner ? . id === scavData . _id )
2023-03-03 16:23:46 +01:00
{
toInventoryItems = scavData . Inventory . items ;
toType = "scav" ;
}
2023-10-10 13:03:20 +02:00
// From and To types match, same inventory
2023-03-03 16:23:46 +01:00
if ( fromType === toType )
{
isSameInventory = true ;
}
return {
from : fromInventoryItems ,
to : toInventoryItems ,
sameInventory : isSameInventory ,
2023-11-13 17:07:59 +01:00
isMail : fromType === "mail" ,
2023-03-03 16:23:46 +01:00
} ;
}
/ * *
* Made a 2 d array table with 0 - free slot and 1 - used slot
* @param { Object } pmcData
* @param { string } sessionID
* @returns Array
* /
protected getStashSlotMap ( pmcData : IPmcData , sessionID : string ) : number [ ] [ ]
{
const playerStashSize = this . getPlayerStashSize ( sessionID ) ;
2023-11-13 17:07:59 +01:00
return this . getContainerMap (
playerStashSize [ 0 ] ,
playerStashSize [ 1 ] ,
pmcData . Inventory . items ,
pmcData . Inventory . stash ,
) ;
2023-03-03 16:23:46 +01:00
}
2024-02-22 14:52:59 +01: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 ) ;
}
2023-07-23 12:51:04 +02:00
protected getSortingTableSlotMap ( pmcData : IPmcData ) : number [ ] [ ]
2023-03-03 16:23:46 +01:00
{
2023-07-23 13:29:00 +02:00
return this . getContainerMap ( 10 , 45 , pmcData . Inventory . items , pmcData . Inventory . sortingTable ) ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
/ * *
* Get Player Stash Proper Size
* @param sessionID Playerid
* @returns Array of 2 values , x and y stash size
* /
2023-03-03 16:23:46 +01:00
protected getPlayerStashSize ( sessionID : string ) : Record < number , number >
{
2023-11-13 17:07:59 +01:00
// this sets automatically a stash size from items.json (its not added anywhere yet cause we still use base stash)
2023-03-03 16:23:46 +01:00
const stashTPL = this . getStashType ( sessionID ) ;
2023-10-10 13:03:20 +02:00
if ( ! stashTPL )
{
2023-10-24 17:40:34 +02:00
this . logger . error ( this . localisationService . getText ( "inventory-missing_stash_size" ) ) ;
2023-10-10 13:03:20 +02:00
}
const stashItemDetails = this . itemHelper . getItem ( stashTPL ) ;
if ( ! stashItemDetails [ 0 ] )
{
2023-10-24 17:40:34 +02:00
this . logger . error ( this . localisationService . getText ( "inventory-stash_not_found" , stashTPL ) ) ;
2023-10-10 13:03:20 +02:00
}
2024-02-22 14:52:59 +01:00
const stashH = stashItemDetails [ 1 ] . _props . Grids [ 0 ] . _props . cellsH !== 0
2023-11-13 18:29:16 +01:00
? stashItemDetails [ 1 ] . _props . Grids [ 0 ] . _props . cellsH
: 10 ;
const stashY = stashItemDetails [ 1 ] . _props . Grids [ 0 ] . _props . cellsV !== 0
? stashItemDetails [ 1 ] . _props . Grids [ 0 ] . _props . cellsV
: 66 ;
2024-02-22 14:52:59 +01:00
return [ stashH , stashY ] ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
/ * *
* Get the players stash items tpl
* @param sessionID Player id
* @returns Stash tpl
* /
2023-07-23 12:51:04 +02:00
protected getStashType ( sessionID : string ) : string
{
const pmcData = this . profileHelper . getPmcProfile ( sessionID ) ;
2023-11-13 17:07:59 +01:00
const stashObj = pmcData . Inventory . items . find ( ( item ) = > item . _id === pmcData . Inventory . stash ) ;
2023-07-23 12:51:04 +02:00
if ( ! stashObj )
{
this . logger . error ( this . localisationService . getText ( "inventory-unable_to_find_stash" ) ) ;
}
2023-10-10 13:03:20 +02:00
return stashObj ? . _tpl ;
2023-07-23 12:51:04 +02:00
}
2023-03-03 16:23:46 +01:00
/ * *
2023-10-10 13:03:20 +02:00
* Internal helper function to transfer an item from one profile to another .
* @param fromItems Inventory of the source ( can be non - player )
* @param toItems Inventory of the destination
* @param body Move request
* /
2023-03-03 16:23:46 +01:00
public moveItemToProfile ( fromItems : Item [ ] , toItems : Item [ ] , body : IInventoryMoveRequestData ) : void
{
this . handleCartridges ( fromItems , body ) ;
2023-10-10 13:03:20 +02:00
// Get all children item has, they need to move with item
2023-03-03 16:23:46 +01:00
const idsToMove = this . itemHelper . findAndReturnChildrenByItems ( fromItems , body . item ) ;
for ( const itemId of idsToMove )
{
2023-11-13 17:07:59 +01:00
const itemToMove = fromItems . find ( ( x ) = > x . _id === itemId ) ;
2023-10-10 13:03:20 +02:00
if ( ! itemToMove )
{
this . logger . error ( ` Unable to find item to move: ${ itemId } ` ) ;
}
// Only adjust the values for parent item, not children (their values are already correctly tied to parent)
if ( itemId === body . item )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
itemToMove . parentId = body . to . id ;
itemToMove . slotId = body . to . container ;
if ( body . to . location )
{
// Update location object
itemToMove . location = body . to . location ;
}
else
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
// No location in request, delete it
if ( itemToMove . location )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
delete itemToMove . location ;
2023-03-03 16:23:46 +01:00
}
}
}
2023-10-10 13:03:20 +02:00
toItems . push ( itemToMove ) ;
fromItems . splice ( fromItems . indexOf ( itemToMove ) , 1 ) ;
2023-03-03 16:23:46 +01:00
}
}
/ * *
2023-10-10 13:03:20 +02:00
* Internal helper function to move item within the same profile_f .
* @param pmcData profile to edit
2023-11-13 17:07:59 +01:00
* @param inventoryItems
* @param moveRequest
2023-10-10 13:03:20 +02:00
* @returns True if move was successful
* /
2023-11-13 17:07:59 +01:00
public moveItemInternal (
pmcData : IPmcData ,
inventoryItems : Item [ ] ,
moveRequest : IInventoryMoveRequestData ,
2023-11-13 18:38:16 +01:00
) : { success : boolean ; errorMessage? : string ; }
2023-03-03 16:23:46 +01:00
{
2023-03-03 18:53:28 +01:00
this . handleCartridges ( inventoryItems , moveRequest ) ;
2023-03-03 16:23:46 +01:00
2023-06-30 20:30:49 +02:00
// Find item we want to 'move'
2023-11-13 17:07:59 +01:00
const matchingInventoryItem = inventoryItems . find ( ( x ) = > x . _id === moveRequest . item ) ;
2023-10-10 13:03:20 +02:00
if ( ! matchingInventoryItem )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
const errorMesage = ` Unable to move item: ${ moveRequest . item } , cannot find in inventory ` ;
this . logger . error ( errorMesage ) ;
2023-03-03 16:23:46 +01:00
2023-11-13 18:38:16 +01:00
return { success : false , errorMessage : errorMesage } ;
2023-10-10 13:03:20 +02:00
}
2023-11-13 17:07:59 +01:00
this . logger . debug (
` ${ moveRequest . Action } item: ${ moveRequest . item } from slotid: ${ matchingInventoryItem . slotId } to container: ${ moveRequest . to . container } ` ,
) ;
2023-03-03 16:23:46 +01:00
2023-10-10 13:03:20 +02: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-11-13 17:07:59 +01:00
this . logger . warning (
this . localisationService . getText ( "inventory-invalid_move_to_container" , {
slotId : matchingInventoryItem.slotId ,
container : moveRequest.to.container ,
} ) ,
) ;
2023-03-03 16:23:46 +01:00
2023-11-13 18:38:16 +01:00
return { success : true } ;
2023-10-10 13:03:20 +02:00
}
2023-03-03 18:53:28 +01:00
2023-10-10 13:03:20 +02:00
// Edit items details to match its new location
matchingInventoryItem . parentId = moveRequest . to . id ;
matchingInventoryItem . slotId = moveRequest . to . container ;
this . updateFastPanelBinding ( pmcData , matchingInventoryItem ) ;
if ( "location" in moveRequest . to )
{
matchingInventoryItem . location = moveRequest . to . location ;
}
else
{
if ( matchingInventoryItem . location )
2023-06-30 20:30:49 +02:00
{
2023-10-10 13:03:20 +02:00
delete matchingInventoryItem . location ;
2023-03-03 16:23:46 +01:00
}
}
2023-10-10 13:03:20 +02:00
2023-11-13 18:38:16 +01:00
return { success : true } ;
2023-03-03 16:23:46 +01:00
}
2023-03-03 18:53:28 +01: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
{
// Find matching itemid in fast panel
for ( const itemKey in pmcData . Inventory . fastPanel )
{
if ( pmcData . Inventory . fastPanel [ itemKey ] === itemBeingMoved . _id )
{
// Get moved items parent
2023-11-13 17:07:59 +01:00
const itemParent = pmcData . Inventory . items . find ( ( x ) = > x . _id === itemBeingMoved . parentId ) ;
2023-03-03 18:53:28 +01:00
// Empty out id if item is moved to a container other than pocket/rig
if ( itemParent && ! ( itemParent . slotId ? . startsWith ( "Pockets" ) || itemParent . slotId === "TacticalVest" ) )
{
pmcData . Inventory . fastPanel [ itemKey ] = "" ;
}
break ;
}
}
}
2023-03-03 16:23:46 +01:00
/ * *
2023-11-13 17:07:59 +01:00
* Internal helper function to handle cartridges in inventory if any of them exist .
* /
2023-03-03 16:23:46 +01:00
protected handleCartridges ( items : Item [ ] , body : IInventoryMoveRequestData ) : void
{
// -> Move item to different place - counts with equipping filling magazine etc
if ( body . to . container === "cartridges" )
{
let tmpCounter = 0 ;
for ( const itemAmmo in items )
{
if ( body . to . id === items [ itemAmmo ] . parentId )
{
tmpCounter ++ ;
}
}
// wrong location for first cartridge
body . to . location = tmpCounter ;
}
}
/ * *
* 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 17:07:05 +02:00
public getInventoryConfig ( ) : IInventoryConfig
{
return this . inventoryConfig ;
}
2024-01-20 21:15:03 +01: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
{
let container = itemToCheck ;
while ( "parentId" in container )
{
if ( container . parentId === pmcData . Inventory . stash && container . slotId === "hideout" )
{
return true ;
}
container = pmcData . Inventory . items . find ( ( item ) = > item . _id === container . parentId ) ;
if ( ! container )
{
break ;
}
}
return false ;
}
2023-03-03 16:23:46 +01:00
}
namespace InventoryHelper
{
export interface InventoryItemHash
{
2023-11-13 17:07:59 +01:00
byItemId : Record < string , Item > ;
byParentId : Record < string , Item [ ] > ;
2023-03-03 16:23:46 +01:00
}
2023-11-13 17:07:59 +01:00
}