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" ;
2023-10-19 19:21:17 +02:00
import { AddItem , IAddItemRequestData } from "@spt-aki/models/eft/inventory/IAddItemRequestData" ;
import { IAddItemTempObject } from "@spt-aki/models/eft/inventory/IAddItemTempObject" ;
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" ;
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
export interface OwnerInventoryItems
{
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-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
* @returns IItemEventRouterResponse
* /
2024-01-14 22:12:56 +01:00
public addItemToStash ( sessionId : string , request : IAddItemDirectRequest , pmcData : IPmcData , output : IItemEventRouterResponse ) : IItemEventRouterResponse
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-14 11:09:43 +01:00
const errorOutput = this . placeItemInInventory (
stashFS2D ,
sortingTableFS2D ,
itemWithModsToAddClone ,
pmcData . Inventory ,
request . useSortingTable ,
output ,
) ;
if ( errorOutput )
{
// Failed to place, error out
return errorOutput ;
}
// 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" ) ;
return this . httpResponse . appendErrorToOutput ( output , message ) ;
}
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-01-14 23:30:05 +01:00
this . logger . debug ( ` Added ${ itemWithModsToAddClone [ 0 ] . upd ? . StackObjectsCount } item: ${ itemWithModsToAddClone [ 0 ] . _tpl } with: ${ itemWithModsToAddClone . length - 1 } mods to inventory ` ) ;
2024-01-14 11:09:43 +01:00
return output ;
}
/ * *
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 ;
}
}
}
}
/ * *
* @deprecated - use addItemToStash ( )
2024-01-14 11:09:43 +01:00
*
2023-03-03 16:23:46 +01:00
* BUG : Passing the same item multiple times with a count of 1 will cause multiples of that item to be added ( e . g . x3 separate objects of tar cola with count of 1 = 9 tarcolas being added to inventory )
* @param pmcData Profile to add items to
* @param request request data to add items
* @param output response to send back to client
* @param sessionID Session id
* @param callback Code to execute later ( function )
2024-01-13 16:01:29 +01:00
* @param foundInRaid Item added will be flagged as found in raid
2023-03-03 16:23:46 +01:00
* @param addUpd Additional upd properties for items being added to inventory
2023-07-23 13:29:00 +02:00
* @param useSortingTable Allow items to go into sorting table when stash has no space
2023-03-03 16:23:46 +01:00
* @returns IItemEventRouterResponse
* /
2023-11-13 17:07:59 +01:00
public addItem (
pmcData : IPmcData ,
request : IAddItemRequestData ,
output : IItemEventRouterResponse ,
sessionID : string ,
callback : ( ) = > void ,
foundInRaid = false ,
addUpd = null ,
useSortingTable = false ,
) : IItemEventRouterResponse
2023-03-03 16:23:46 +01:00
{
2024-01-08 13:41:52 +01:00
/** All items to add + their children */
const itemsToAddPool : Item [ ] = [ ] ;
/** Root items to add to inventory */
2024-01-13 16:01:29 +01:00
const rootItemsToAdd : IAddItemTempObject [ ] = [ ] ;
2023-03-03 16:23:46 +01:00
for ( const requestItem of request . items )
{
2024-01-13 16:00:31 +01:00
if ( this . presetHelper . isPreset ( requestItem . item_id ) )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:00:31 +01:00
const preset = this . jsonUtil . clone ( this . presetHelper . getPreset ( requestItem . item_id ) ) ;
const presetItems = preset . _items ;
2024-01-13 16:01:29 +01:00
// Add FiR status to preset if needed
if ( foundInRaid || this . inventoryConfig . newItemsMarkedFound )
{
for ( const item of presetItems )
{
if ( ! item . upd )
{
item . upd = { } ;
}
if ( foundInRaid )
{
item . upd . SpawnedInSession = true ;
}
}
}
2024-01-13 16:00:31 +01:00
// Push preset data into pool array
2024-01-08 13:41:52 +01:00
itemsToAddPool . push ( . . . presetItems ) ;
2024-01-13 16:00:31 +01:00
requestItem . sptIsPreset = true ;
2024-01-13 16:01:29 +01:00
// Remap requests item id to preset root items id
2023-03-03 16:23:46 +01:00
requestItem . item_id = presetItems [ 0 ] . _id ;
}
else if ( this . paymentHelper . isMoneyTpl ( requestItem . item_id ) )
{
2024-01-08 13:41:52 +01:00
itemsToAddPool . push ( { _id : requestItem.item_id , _tpl : requestItem.item_id } ) ;
2023-03-03 16:23:46 +01:00
}
else if ( request . tid === Traders . FENCE )
{
const fenceItems = this . fenceService . getRawFenceAssorts ( ) . items ;
2023-11-13 17:07:59 +01:00
const itemIndex = fenceItems . findIndex ( ( i ) = > i . _id === requestItem . item_id ) ;
2023-03-03 16:23:46 +01:00
if ( itemIndex === - 1 )
{
this . logger . debug ( ` Tried to buy item ${ requestItem . item_id } from fence that no longer exists ` ) ;
const message = this . localisationService . getText ( "ragfair-offer_no_longer_exists" ) ;
return this . httpResponse . appendErrorToOutput ( output , message ) ;
}
2023-10-10 13:03:20 +02:00
2023-11-13 17:07:59 +01:00
const purchasedItemWithChildren = this . itemHelper . findAndReturnChildrenAsItems (
fenceItems ,
requestItem . item_id ,
) ;
2023-10-10 13:03:20 +02:00
addUpd = purchasedItemWithChildren [ 0 ] . upd ; // Must persist the fence upd properties (e.g. durability/currentHp)
2024-01-08 13:41:52 +01:00
itemsToAddPool . push ( . . . purchasedItemWithChildren ) ;
2023-03-03 16:23:46 +01:00
}
else if ( request . tid === "RandomLootContainer" )
{
2024-01-08 13:41:52 +01:00
itemsToAddPool . push ( { _id : requestItem.item_id , _tpl : requestItem.item_id } ) ;
2023-03-03 16:23:46 +01:00
}
else
{
// Only grab the relevant trader items and add unique values
const traderItems = this . traderAssortHelper . getAssort ( sessionID , request . tid ) . items ;
const relevantItems = this . itemHelper . findAndReturnChildrenAsItems ( traderItems , requestItem . item_id ) ;
2023-11-13 17:07:59 +01:00
const toAdd = relevantItems . filter ( ( traderItem ) = >
2024-01-08 13:41:52 +01:00
! itemsToAddPool . some ( ( item ) = > traderItem . _id === item . _id )
2023-11-13 17:07:59 +01:00
) ; // what's this
2024-01-08 13:41:52 +01:00
itemsToAddPool . push ( . . . toAdd ) ;
2023-03-03 16:23:46 +01:00
}
// Split stacks into allowed sizes if needed
// e.g. when buying 300 ammo from flea but max stack size is 50
2024-01-13 16:01:29 +01:00
this . splitStackIntoSmallerStacks ( itemsToAddPool , requestItem , rootItemsToAdd ) ;
2023-03-03 16:23:46 +01:00
}
// Find an empty slot in stash for each of the items being added
2023-10-10 13:03:20 +02:00
const stashFS2D = this . getStashSlotMap ( pmcData , sessionID ) ;
const sortingTableFS2D = this . getSortingTableSlotMap ( pmcData ) ;
2023-03-03 16:23:46 +01:00
2024-01-13 16:01:29 +01:00
for ( const itemToAdd of rootItemsToAdd )
2023-03-03 16:23:46 +01:00
{
2024-01-08 12:35:20 +01:00
// Update Items `location` properties
2024-01-08 13:41:52 +01:00
const itemWithChildren = this . itemHelper . findAndReturnChildrenAsItems ( itemsToAddPool , itemToAdd . itemRef . _id ) ;
2024-01-14 11:09:43 +01:00
const errorOutput = this . placeItemInInventoryLegacy (
2023-11-13 17:07:59 +01:00
itemToAdd ,
stashFS2D ,
sortingTableFS2D ,
2024-01-08 13:41:52 +01:00
itemWithChildren ,
2023-11-13 17:07:59 +01:00
pmcData . Inventory ,
useSortingTable ,
output ,
) ;
2023-10-10 13:03:20 +02:00
if ( errorOutput )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
return errorOutput ;
2023-03-03 16:23:46 +01:00
}
}
2024-01-08 13:41:52 +01:00
// Found slot for every item (stash or sorting table), run callback and catch failure (e.g. payMoney() as player doesnt have enough)
2023-03-03 16:23:46 +01:00
try
{
if ( typeof callback === "function" )
{
callback ( ) ;
}
}
catch ( err )
{
2023-07-24 16:52:55 +02:00
// Callback failed
2023-11-13 18:31:52 +01:00
const message = typeof err === "string" ? err : this.localisationService.getText ( "http-unknown_error" ) ;
2023-03-03 16:23:46 +01:00
return this . httpResponse . appendErrorToOutput ( output , message ) ;
}
// Update UPD properties and add to output.profileChanges/pmcData.Inventory.items arrays
2024-01-13 16:01:29 +01:00
for ( const rootItemToAdd of rootItemsToAdd )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
let newIdForItem = this . hashUtil . generate ( ) ;
const originalKeyToNewKeyMap : string [ ] [ ] = [ [ rootItemToAdd . itemRef . _id , newIdForItem ] ] ; // Every item id + randomly generated id
let rootItemUpd : Upd = { StackObjectsCount : rootItemToAdd.count } ;
2023-03-03 16:23:46 +01:00
// If item being added is preset, load preset's upd data too.
2024-01-13 16:01:29 +01:00
if ( rootItemToAdd . isPreset )
2023-03-03 16:23:46 +01:00
{
2024-01-08 13:41:52 +01:00
// Iterate over properties in upd and add them
2024-01-13 16:01:29 +01:00
for ( const updID in rootItemToAdd . itemRef . upd )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
rootItemUpd [ updID ] = rootItemToAdd . itemRef . upd [ updID ] ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
if ( addUpd )
{
2024-01-08 13:41:52 +01:00
// Iterate over properties in addUpd and add them
2023-10-10 13:03:20 +02:00
for ( const updID in addUpd )
{
2024-01-13 16:01:29 +01:00
rootItemUpd [ updID ] = addUpd [ updID ] ;
2023-10-10 13:03:20 +02:00
}
}
2023-03-03 16:23:46 +01:00
}
// Item has buff, add to item being sent to player
2024-01-13 16:01:29 +01:00
if ( rootItemToAdd . itemRef . upd ? . Buff )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
rootItemUpd . Buff = this . jsonUtil . clone ( rootItemToAdd . itemRef . upd . Buff ) ;
2023-03-03 16:23:46 +01:00
}
2024-01-13 16:01:29 +01:00
// Add ragfair upd properties
2023-03-03 16:23:46 +01:00
if ( addUpd )
{
2024-01-13 16:01:29 +01:00
rootItemUpd = { . . . addUpd , . . . rootItemUpd } ;
2023-03-03 16:23:46 +01:00
}
// Hideout items need to be marked as found in raid
// Or in case people want all items to be marked as found in raid
if ( foundInRaid || this . inventoryConfig . newItemsMarkedFound )
{
2024-01-13 16:01:29 +01:00
rootItemUpd . SpawnedInSession = true ;
2023-03-03 16:23:46 +01:00
}
2023-11-13 17:07:59 +01:00
2023-10-10 13:03:20 +02:00
// Remove invalid properties prior to adding to inventory
2024-01-14 11:09:43 +01:00
this . removeTraderRagfairRelatedUpdProperties ( rootItemUpd ) ;
2023-10-10 13:03:20 +02:00
2024-01-13 16:01:29 +01:00
// Add root item to client return object
2023-03-03 16:23:46 +01:00
output . profileChanges [ sessionID ] . items . new . push ( {
2024-01-13 16:01:29 +01:00
_id : newIdForItem ,
_tpl : rootItemToAdd.itemRef._tpl ,
parentId : rootItemToAdd.containerId ,
2023-03-03 16:23:46 +01:00
slotId : "hideout" ,
2024-01-13 16:01:29 +01:00
location : { x : rootItemToAdd.location.x , y : rootItemToAdd.location.y , r : rootItemToAdd.location.rotation ? 1 : 0 } ,
upd : this.jsonUtil.clone ( rootItemUpd ) ,
2023-03-03 16:23:46 +01:00
} ) ;
2024-01-13 16:01:29 +01:00
// Add root item to player inventory
2023-03-03 16:23:46 +01:00
pmcData . Inventory . items . push ( {
2024-01-13 16:01:29 +01:00
_id : newIdForItem ,
_tpl : rootItemToAdd.itemRef._tpl ,
parentId : rootItemToAdd.containerId ,
2023-03-03 16:23:46 +01:00
slotId : "hideout" ,
2024-01-13 16:01:29 +01:00
location : { x : rootItemToAdd.location.x , y : rootItemToAdd.location.y , r : rootItemToAdd.location.rotation ? 1 : 0 } ,
upd : this.jsonUtil.clone ( rootItemUpd ) , // Clone upd to prevent multi-purchases of same item referencing same upd object in memory
2023-03-03 16:23:46 +01:00
} ) ;
2024-01-13 16:01:29 +01:00
// Edge case - ammo boxes need cartridges added to result
if ( this . itemHelper . isOfBaseclass ( rootItemToAdd . itemRef . _tpl , BaseClasses . AMMO_BOX ) )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
this . hydrateAmmoBoxWithAmmo ( pmcData , rootItemToAdd , originalKeyToNewKeyMap [ 0 ] [ 1 ] , sessionID , output , foundInRaid ) ;
2023-03-03 16:23:46 +01:00
}
2024-01-08 13:41:52 +01:00
// Loop over item + children
while ( originalKeyToNewKeyMap . length > 0 )
2024-01-13 16:01:29 +01:00
{
// Iterate item + children being added
2024-01-08 13:41:52 +01:00
for ( const arrayIndex in itemsToAddPool )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
const itemDetails = itemsToAddPool [ arrayIndex ] ;
2024-01-08 13:41:52 +01:00
// Does parent match original key
2024-01-13 16:01:29 +01:00
if ( itemDetails ? . parentId !== originalKeyToNewKeyMap [ 0 ] [ 0 ] )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
// Skip when items parent isnt on remap (root item)
2023-10-10 13:03:20 +02:00
continue ;
}
2023-03-03 16:23:46 +01:00
2024-01-13 16:01:29 +01:00
// Create new id for child item
newIdForItem = this . hashUtil . generate ( ) ;
const itemSlotId = itemDetails . slotId ;
2023-03-03 16:23:46 +01:00
2024-01-13 16:01:29 +01:00
// If its from ItemPreset, load presets upd data too.
if ( rootItemToAdd . isPreset )
2023-10-10 13:03:20 +02:00
{
2024-01-13 16:01:29 +01:00
rootItemUpd = { StackObjectsCount : rootItemToAdd.count } ;
2023-03-03 16:23:46 +01:00
2024-01-13 16:01:29 +01:00
for ( const updID in itemDetails . upd )
2023-10-10 13:03:20 +02:00
{
2024-01-13 16:01:29 +01:00
rootItemUpd [ updID ] = itemDetails . upd [ updID ] ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
if ( foundInRaid || this . inventoryConfig . newItemsMarkedFound )
2023-03-03 16:23:46 +01:00
{
2024-01-13 16:01:29 +01:00
rootItemUpd . SpawnedInSession = true ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
}
2024-01-08 13:41:52 +01:00
// Is root item
2024-01-13 16:01:29 +01:00
if ( itemSlotId === "hideout" )
2023-10-10 13:03:20 +02:00
{
2024-01-13 16:01:29 +01:00
// Add child item to client return object
2023-10-10 13:03:20 +02:00
output . profileChanges [ sessionID ] . items . new . push ( {
2024-01-13 16:01:29 +01:00
_id : newIdForItem ,
_tpl : itemDetails._tpl ,
2024-01-08 13:41:52 +01:00
parentId : originalKeyToNewKeyMap [ 0 ] [ 1 ] ,
2024-01-13 16:01:29 +01:00
slotId : itemSlotId ,
location : { x : rootItemToAdd.location.x , y : rootItemToAdd.location.y , r : "Horizontal" } ,
upd : this.jsonUtil.clone ( rootItemUpd ) ,
2023-10-10 13:03:20 +02:00
} ) ;
2024-01-13 16:01:29 +01:00
// Add child item to player inventory
2023-10-10 13:03:20 +02:00
pmcData . Inventory . items . push ( {
2024-01-13 16:01:29 +01:00
_id : newIdForItem ,
_tpl : itemDetails._tpl ,
2024-01-08 13:41:52 +01:00
parentId : originalKeyToNewKeyMap [ 0 ] [ 1 ] ,
2024-01-13 16:01:29 +01:00
slotId : itemDetails.slotId ,
location : { x : rootItemToAdd.location.x , y : rootItemToAdd.location.y , r : "Horizontal" } ,
upd : this.jsonUtil.clone ( rootItemUpd ) ,
2023-10-10 13:03:20 +02:00
} ) ;
}
else
{
2024-01-08 13:41:52 +01:00
// Child of item being added
2023-10-10 13:03:20 +02:00
// Item already has location property, use it
2024-01-08 13:41:52 +01:00
const itemLocation = { } ;
2024-01-13 16:01:29 +01:00
if ( itemDetails . location !== undefined )
2023-10-10 13:03:20 +02:00
{
2024-01-13 16:01:29 +01:00
itemLocation [ "location" ] = itemDetails . location ;
2023-03-03 16:23:46 +01:00
}
2024-01-13 16:01:29 +01:00
// Clone upd so we dont adjust the underlying data
const upd = this . jsonUtil . clone ( itemDetails . upd ) ;
2023-10-10 13:03:20 +02:00
output . profileChanges [ sessionID ] . items . new . push ( {
2024-01-13 16:01:29 +01:00
_id : newIdForItem ,
_tpl : itemDetails._tpl ,
2024-01-08 13:41:52 +01:00
parentId : originalKeyToNewKeyMap [ 0 ] [ 1 ] ,
2024-01-13 16:01:29 +01:00
slotId : itemSlotId ,
2023-10-10 13:03:20 +02:00
. . . itemLocation ,
2024-01-08 13:41:52 +01:00
upd : upd ,
2023-10-10 13:03:20 +02:00
} ) ;
pmcData . Inventory . items . push ( {
2024-01-13 16:01:29 +01:00
_id : newIdForItem ,
_tpl : itemDetails._tpl ,
2024-01-08 13:41:52 +01:00
parentId : originalKeyToNewKeyMap [ 0 ] [ 1 ] ,
2024-01-13 16:01:29 +01:00
slotId : itemDetails.slotId ,
2023-10-10 13:03:20 +02:00
. . . itemLocation ,
2024-01-08 13:41:52 +01:00
upd : upd ,
2023-10-10 13:03:20 +02:00
} ) ;
2024-01-13 16:01:29 +01:00
this . logger . debug ( ` Added: ${ itemDetails . _tpl } with id: ${ newIdForItem } to inventory ` ) ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
2024-01-08 13:41:52 +01:00
// Add mapping of child item to new id
2024-01-13 16:01:29 +01:00
originalKeyToNewKeyMap . push ( [ itemDetails . _id , newIdForItem ] ) ;
2023-03-03 16:23:46 +01:00
}
2024-01-08 13:41:52 +01:00
// Remove mapping now we're done with it
originalKeyToNewKeyMap . splice ( 0 , 1 ) ;
2023-03-03 16:23:46 +01:00
}
}
return output ;
}
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-01-14 11:09:43 +01:00
protected placeItemInInventory (
stashFS2D : number [ ] [ ] ,
sortingTableFS2D : number [ ] [ ] ,
itemWithChildren : Item [ ] ,
playerInventory : Inventory ,
useSortingTable : boolean ,
output : IItemEventRouterResponse ) : IItemEventRouterResponse
{
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 )
{
/ * F i l l i n t h e S t a s h F S _ 2 D w i t h a n i m a g i n a r y i t e m , t o s i m u l a t e i t a l r e a d y b e i n g a d d e d
* so the next item to search for a free slot won ' t find the same one * /
const itemSizeX = findSlotResult . rotation ? itemSize [ 1 ] : itemSize [ 0 ] ;
const itemSizeY = findSlotResult . rotation ? itemSize [ 0 ] : itemSize [ 1 ] ;
try
{
stashFS2D = this . containerHelper . fillContainerMapWithItem (
stashFS2D ,
findSlotResult . x ,
findSlotResult . y ,
itemSizeX ,
itemSizeY ,
false ,
) ; // TODO: rotation not passed in, bad?
}
catch ( err )
{
2024-01-14 22:12:56 +01:00
const errorText = ( typeof err === "string" )
? ` -> ${ err } `
: "" ;
2024-01-14 11:09:43 +01:00
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
return this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
}
// 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 ] ,
) ;
const itemSizeX = findSortingSlotResult . rotation ? itemSize [ 1 ] : itemSize [ 0 ] ;
const itemSizeY = findSortingSlotResult . rotation ? itemSize [ 0 ] : itemSize [ 1 ] ;
try
{
sortingTableFS2D = this . containerHelper . fillContainerMapWithItem (
sortingTableFS2D ,
findSortingSlotResult . x ,
findSortingSlotResult . y ,
itemSizeX ,
itemSizeY ,
false ,
) ; // TODO: rotation not passed in, bad?
}
catch ( err )
{
const errorText = typeof err === "string" ? ` -> ${ err } ` : "" ;
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
return this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
}
// 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
{
return this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
}
}
2023-10-10 13:03:20 +02:00
/ * *
* Take the given item , find a free slot in passed in inventory and place it there
* If no space in inventory , place in sorting table
* @param itemToAdd Item to add to inventory
* @param stashFS2D Two dimentional stash map
* @param sortingTableFS2D Two dimentional sorting table stash map
2023-11-13 17:07:59 +01:00
* @param itemLib
2023-10-10 13:03:20 +02:00
* @param pmcData Player profile
* @param useSortingTable Should sorting table be used for overflow items when no inventory space for item
* @param output Client output object
* @returns Client error output if placing item failed
* /
2024-01-14 11:09:43 +01:00
protected placeItemInInventoryLegacy (
2023-11-13 17:07:59 +01:00
itemToAdd : IAddItemTempObject ,
stashFS2D : number [ ] [ ] ,
sortingTableFS2D : number [ ] [ ] ,
itemLib : Item [ ] ,
playerInventory : Inventory ,
useSortingTable : boolean ,
output : IItemEventRouterResponse ,
) : IItemEventRouterResponse
2023-10-10 13:03:20 +02:00
{
const itemSize = this . getItemSize ( itemToAdd . itemRef . _tpl , itemToAdd . itemRef . _id , itemLib ) ;
const findSlotResult = this . containerHelper . findSlotForItem ( stashFS2D , itemSize [ 0 ] , itemSize [ 1 ] ) ;
if ( findSlotResult . success )
{
/ * F i l l i n t h e S t a s h F S _ 2 D w i t h a n i m a g i n a r y i t e m , t o s i m u l a t e i t a l r e a d y b e i n g a d d e d
* so the next item to search for a free slot won ' t find the same one * /
const itemSizeX = findSlotResult . rotation ? itemSize [ 1 ] : itemSize [ 0 ] ;
const itemSizeY = findSlotResult . rotation ? itemSize [ 0 ] : itemSize [ 1 ] ;
try
{
2023-11-13 17:07:59 +01:00
stashFS2D = this . containerHelper . fillContainerMapWithItem (
stashFS2D ,
findSlotResult . x ,
findSlotResult . y ,
itemSizeX ,
itemSizeY ,
false ,
) ; // TODO: rotation not passed in, bad?
2023-10-10 13:03:20 +02:00
}
catch ( err )
{
2023-11-13 18:31:52 +01:00
const errorText = typeof err === "string" ? ` -> ${ err } ` : "" ;
2023-10-10 13:03:20 +02:00
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
2023-11-13 17:07:59 +01:00
return this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
2023-10-10 13:03:20 +02:00
}
// Store details for object, incuding container item will be placed in
itemToAdd . containerId = playerInventory . stash ;
itemToAdd . location = {
x : findSlotResult.x ,
y : findSlotResult.y ,
r : findSlotResult.rotation ? 1 : 0 ,
2023-11-13 17:07:59 +01:00
rotation : findSlotResult.rotation ,
2023-10-10 13:03:20 +02:00
} ;
// Success! exit
return ;
}
// Space not found in main stash, use sorting table
if ( useSortingTable )
{
2023-11-13 17:07:59 +01:00
const findSortingSlotResult = this . containerHelper . findSlotForItem (
sortingTableFS2D ,
itemSize [ 0 ] ,
itemSize [ 1 ] ,
) ;
2023-10-10 13:03:20 +02:00
const itemSizeX = findSortingSlotResult . rotation ? itemSize [ 1 ] : itemSize [ 0 ] ;
const itemSizeY = findSortingSlotResult . rotation ? itemSize [ 0 ] : itemSize [ 1 ] ;
try
{
2023-11-13 17:07:59 +01:00
sortingTableFS2D = this . containerHelper . fillContainerMapWithItem (
sortingTableFS2D ,
findSortingSlotResult . x ,
findSortingSlotResult . y ,
itemSizeX ,
itemSizeY ,
false ,
) ; // TODO: rotation not passed in, bad?
2023-10-10 13:03:20 +02:00
}
catch ( err )
{
const errorText = typeof err === "string" ? ` -> ${ err } ` : "" ;
this . logger . error ( this . localisationService . getText ( "inventory-fill_container_failed" , errorText ) ) ;
2023-11-13 17:07:59 +01:00
return this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
2023-10-10 13:03:20 +02:00
}
// Store details for object, incuding container item will be placed in
itemToAdd . containerId = playerInventory . sortingTable ;
itemToAdd . location = {
x : findSortingSlotResult.x ,
y : findSortingSlotResult.y ,
r : findSortingSlotResult.rotation ? 1 : 0 ,
2023-11-13 17:07:59 +01:00
rotation : findSortingSlotResult.rotation ,
2023-10-10 13:03:20 +02:00
} ;
}
else
{
2023-11-13 17:07:59 +01:00
return this . httpResponse . appendErrorToOutput (
output ,
this . localisationService . getText ( "inventory-no_stash_space" ) ,
) ;
2023-10-10 13:03:20 +02:00
}
}
2023-03-03 16:23:46 +01:00
/ * *
* Add ammo to ammo boxes
* @param itemToAdd Item to check is ammo box
* @param parentId Ammo box parent id
* @param output IItemEventRouterResponse object
* @param sessionID Session id
* @param pmcData Profile to add ammobox to
2023-10-10 13:03:20 +02:00
* @param output object to send to client
* @param foundInRaid should ammo be FiR
2023-03-03 16:23:46 +01:00
* /
2023-11-13 17:07:59 +01:00
protected hydrateAmmoBoxWithAmmo (
pmcData : IPmcData ,
itemToAdd : IAddItemTempObject ,
parentId : string ,
sessionID : string ,
output : IItemEventRouterResponse ,
foundInRaid : boolean ,
) : void
2023-03-03 16:23:46 +01:00
{
const itemInfo = this . itemHelper . getItem ( itemToAdd . itemRef . _tpl ) [ 1 ] ;
const stackSlots = itemInfo . _props . StackSlots ;
if ( stackSlots !== undefined )
{
// Cartridge info seems to be an array of size 1 for some reason... (See AmmoBox constructor in client code)
let maxCount = stackSlots [ 0 ] . _max_count ;
const ammoTpl = stackSlots [ 0 ] . _props . filters [ 0 ] . Filter [ 0 ] ;
const ammoStackMaxSize = this . itemHelper . getItem ( ammoTpl ) [ 1 ] . _props . StackMaxSize ;
const ammos = [ ] ;
let location = 0 ;
// Place stacks in ammo box no larger than StackMaxSize, prevents player when opening item getting stack of ammo > StackMaxSize
while ( maxCount > 0 )
{
const ammoStackSize = maxCount <= ammoStackMaxSize ? maxCount : ammoStackMaxSize ;
2023-10-10 13:03:20 +02:00
const ammoItem : Item = {
2023-03-03 16:23:46 +01:00
_id : this.hashUtil.generate ( ) ,
_tpl : ammoTpl ,
parentId : parentId ,
slotId : "cartridges" ,
location : location ,
2023-11-13 18:38:16 +01:00
upd : { StackObjectsCount : ammoStackSize } ,
2023-10-10 13:03:20 +02:00
} ;
if ( foundInRaid )
{
ammoItem . upd . SpawnedInSession = true ;
}
ammos . push ( ammoItem ) ;
2023-03-03 16:23:46 +01:00
location ++ ;
maxCount -= ammoStackMaxSize ;
}
for ( const item of [ output . profileChanges [ sessionID ] . items . new , pmcData . Inventory . items ] )
{
item . push ( . . . ammos ) ;
}
}
}
/ * *
* @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
* /
protected splitStackIntoSmallerStacks ( assortItems : Item [ ] , requestItem : AddItem , result : IAddItemTempObject [ ] ) : void
{
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
* @param output Existing IItemEventRouterResponse object to append data to , creates new one by default if not supplied
* @returns IItemEventRouterResponse
* /
2023-11-13 17:07:59 +01:00
public removeItem (
profile : IPmcData ,
itemId : string ,
sessionID : string ,
output : IItemEventRouterResponse = undefined ,
) : IItemEventRouterResponse
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" ) ;
2023-03-03 16:23:46 +01:00
return output ;
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 ) ;
}
}
return output ;
}
2023-11-13 17:07:59 +01:00
public removeItemAndChildrenFromMailRewards (
sessionId : string ,
removeRequest : IInventoryRemoveRequestData ,
output : IItemEventRouterResponse ,
) : IItemEventRouterResponse
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 ;
}
}
return output ;
}
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 ;
}
else if ( childFoldable && rootFolded && childFolded )
{
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 ;
}
public getContainerMap ( containerW : number , containerH : number , itemList : Item [ ] , containerId : string ) : number [ ] [ ]
{
const container2D : number [ ] [ ] = Array ( containerH ) . fill ( 0 ) . map ( ( ) = > Array ( containerW ) . fill ( 0 ) ) ;
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 (
request : IInventoryMoveRequestData | IInventorySplitRequestData | IInventoryMergeRequestData ,
sessionId : string ,
) : OwnerInventoryItems
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
}
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
}
2023-11-13 18:29:16 +01:00
const stashX = stashItemDetails [ 1 ] . _props . Grids [ 0 ] . _props . cellsH !== 0
? 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 ;
2023-03-03 16:23:46 +01:00
return [ stashX , stashY ] ;
}
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 ;
}
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
}