2023-03-03 15:23:46 +00:00
|
|
|
import { inject, injectable } from "tsyringe";
|
|
|
|
|
2023-10-19 17:21:17 +00:00
|
|
|
import { LootGenerator } from "@spt-aki/generators/LootGenerator";
|
2024-01-13 16:41:06 +00:00
|
|
|
import { HideoutHelper } from "@spt-aki/helpers/HideoutHelper";
|
2023-10-19 17:21:17 +00:00
|
|
|
import { InventoryHelper } from "@spt-aki/helpers/InventoryHelper";
|
|
|
|
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
|
|
|
import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper";
|
|
|
|
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
|
|
|
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
|
|
|
import { QuestHelper } from "@spt-aki/helpers/QuestHelper";
|
|
|
|
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
|
|
|
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
2024-02-01 13:23:03 +00:00
|
|
|
import { IAddItemsDirectRequest } from "@spt-aki/models/eft/inventory/IAddItemsDirectRequest";
|
2023-10-19 17:21:17 +00:00
|
|
|
import { IInventoryBindRequestData } from "@spt-aki/models/eft/inventory/IInventoryBindRequestData";
|
|
|
|
import { IInventoryCreateMarkerRequestData } from "@spt-aki/models/eft/inventory/IInventoryCreateMarkerRequestData";
|
|
|
|
import { IInventoryDeleteMarkerRequestData } from "@spt-aki/models/eft/inventory/IInventoryDeleteMarkerRequestData";
|
|
|
|
import { IInventoryEditMarkerRequestData } from "@spt-aki/models/eft/inventory/IInventoryEditMarkerRequestData";
|
|
|
|
import { IInventoryExamineRequestData } from "@spt-aki/models/eft/inventory/IInventoryExamineRequestData";
|
|
|
|
import { IInventoryFoldRequestData } from "@spt-aki/models/eft/inventory/IInventoryFoldRequestData";
|
|
|
|
import { IInventoryMergeRequestData } from "@spt-aki/models/eft/inventory/IInventoryMergeRequestData";
|
|
|
|
import { IInventoryMoveRequestData } from "@spt-aki/models/eft/inventory/IInventoryMoveRequestData";
|
|
|
|
import { IInventoryReadEncyclopediaRequestData } from "@spt-aki/models/eft/inventory/IInventoryReadEncyclopediaRequestData";
|
|
|
|
import { IInventoryRemoveRequestData } from "@spt-aki/models/eft/inventory/IInventoryRemoveRequestData";
|
|
|
|
import { IInventorySortRequestData } from "@spt-aki/models/eft/inventory/IInventorySortRequestData";
|
|
|
|
import { IInventorySplitRequestData } from "@spt-aki/models/eft/inventory/IInventorySplitRequestData";
|
|
|
|
import { IInventorySwapRequestData } from "@spt-aki/models/eft/inventory/IInventorySwapRequestData";
|
|
|
|
import { IInventoryTagRequestData } from "@spt-aki/models/eft/inventory/IInventoryTagRequestData";
|
|
|
|
import { IInventoryToggleRequestData } from "@spt-aki/models/eft/inventory/IInventoryToggleRequestData";
|
|
|
|
import { IInventoryTransferRequestData } from "@spt-aki/models/eft/inventory/IInventoryTransferRequestData";
|
|
|
|
import { IOpenRandomLootContainerRequestData } from "@spt-aki/models/eft/inventory/IOpenRandomLootContainerRequestData";
|
2023-12-07 20:16:04 +00:00
|
|
|
import { IRedeemProfileRequestData } from "@spt-aki/models/eft/inventory/IRedeemProfileRequestData";
|
2023-12-29 20:22:50 +00:00
|
|
|
import { ISetFavoriteItems } from "@spt-aki/models/eft/inventory/ISetFavoriteItems";
|
2023-10-19 17:21:17 +00:00
|
|
|
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
|
|
|
|
import { BackendErrorCodes } from "@spt-aki/models/enums/BackendErrorCodes";
|
|
|
|
import { SkillTypes } from "@spt-aki/models/enums/SkillTypes";
|
|
|
|
import { Traders } from "@spt-aki/models/enums/Traders";
|
|
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
|
|
import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder";
|
|
|
|
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
|
|
|
import { FenceService } from "@spt-aki/services/FenceService";
|
|
|
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
2023-12-07 20:16:04 +00:00
|
|
|
import { PlayerService } from "@spt-aki/services/PlayerService";
|
2023-10-19 17:21:17 +00:00
|
|
|
import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService";
|
|
|
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
|
|
|
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
|
|
|
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
|
|
|
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
@injectable()
|
|
|
|
export class InventoryController
|
|
|
|
{
|
|
|
|
constructor(
|
|
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
|
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
|
|
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
2023-06-20 16:07:05 +01:00
|
|
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
2023-03-03 15:23:46 +00:00
|
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
|
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
|
|
@inject("FenceService") protected fenceService: FenceService,
|
|
|
|
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
|
|
|
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
|
2023-07-13 10:26:47 +01:00
|
|
|
@inject("QuestHelper") protected questHelper: QuestHelper,
|
2024-01-13 16:41:06 +00:00
|
|
|
@inject("HideoutHelper") protected hideoutHelper: HideoutHelper,
|
2023-03-03 15:23:46 +00:00
|
|
|
@inject("RagfairOfferService") protected ragfairOfferService: RagfairOfferService,
|
|
|
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
|
|
|
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
|
|
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
2023-12-07 20:16:04 +00:00
|
|
|
@inject("PlayerService") protected playerService: PlayerService,
|
2023-06-20 16:07:05 +01:00
|
|
|
@inject("LootGenerator") protected lootGenerator: LootGenerator,
|
2023-03-03 15:23:46 +00:00
|
|
|
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
2023-11-16 21:42:06 +00:00
|
|
|
@inject("HttpResponseUtil") protected httpResponseUtil: HttpResponseUtil,
|
2023-03-03 15:23:46 +00:00
|
|
|
)
|
|
|
|
{}
|
|
|
|
|
|
|
|
/**
|
2023-11-16 21:42:06 +00:00
|
|
|
* Move Item
|
|
|
|
* change location of item with parentId and slotId
|
|
|
|
* transfers items from one profile to another if fromOwner/toOwner is set in the body.
|
|
|
|
* otherwise, move is contained within the same profile_f.
|
2023-03-03 15:23:46 +00:00
|
|
|
* @param pmcData Profile
|
|
|
|
* @param moveRequest Move request data
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public moveItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
moveRequest: IInventoryMoveRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
|
|
|
|
if (output.warnings.length > 0)
|
|
|
|
{
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// Changes made to result apply to character inventory
|
|
|
|
const ownerInventoryItems = this.inventoryHelper.getOwnerInventoryItems(moveRequest, sessionID);
|
|
|
|
if (ownerInventoryItems.sameInventory)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Dont move items from trader to profile, this can happen when editing a traders preset weapons
|
2023-10-10 11:03:20 +00:00
|
|
|
if (moveRequest.fromOwner?.type === "Trader" && !ownerInventoryItems.isMail)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-06-30 19:30:49 +01:00
|
|
|
return this.getTraderExploitErrorResponse(output);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for item in inventory before allowing internal transfer
|
2023-11-16 21:42:06 +00:00
|
|
|
const originalItemLocation = ownerInventoryItems.from.find((x) => x._id === moveRequest.item);
|
2024-01-13 16:41:06 +00:00
|
|
|
const originalLocationSlotId = originalItemLocation.slotId;
|
2023-06-30 19:30:49 +01:00
|
|
|
if (!originalItemLocation)
|
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
// Internal item move but item never existed, possible dupe glitch
|
2023-06-30 19:30:49 +01:00
|
|
|
return this.getTraderExploitErrorResponse(output);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
const moveResult = this.inventoryHelper.moveItemInternal(pmcData, ownerInventoryItems.from, moveRequest);
|
|
|
|
if (!moveResult.success)
|
|
|
|
{
|
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, moveResult.errorMessage);
|
|
|
|
}
|
2024-01-13 16:41:06 +00:00
|
|
|
|
|
|
|
// Item is moving into or out of place of fame dogtag slot
|
2024-02-02 13:54:07 -05:00
|
|
|
if (moveRequest.to.container.startsWith("dogtag") || originalLocationSlotId.startsWith("dogtag"))
|
2024-01-13 16:41:06 +00:00
|
|
|
{
|
|
|
|
this.hideoutHelper.applyPlaceOfFameDogtagBonus(pmcData);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
this.inventoryHelper.moveItemToProfile(ownerInventoryItems.from, ownerInventoryItems.to, moveRequest);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2023-06-30 19:30:49 +01:00
|
|
|
/**
|
|
|
|
* Get a event router response with inventory trader message
|
|
|
|
* @param output Item event router response
|
|
|
|
* @returns Item event router response
|
|
|
|
*/
|
|
|
|
protected getTraderExploitErrorResponse(output: IItemEventRouterResponse): IItemEventRouterResponse
|
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
return this.httpResponseUtil.appendErrorToOutput(
|
|
|
|
output,
|
|
|
|
this.localisationService.getText("inventory-edit_trader_item"),
|
|
|
|
<BackendErrorCodes>228,
|
|
|
|
);
|
2023-06-30 19:30:49 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
/**
|
2023-11-16 21:42:06 +00:00
|
|
|
* Remove Item from Profile
|
|
|
|
* Deep tree item deletion, also removes items from insurance list
|
|
|
|
*/
|
|
|
|
public removeItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
itemId: string,
|
|
|
|
sessionID: string,
|
|
|
|
output: IItemEventRouterResponse = undefined,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
return this.inventoryHelper.removeItem(pmcData, itemId, sessionID, output);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-10 11:03:20 +00:00
|
|
|
* Handle Remove event
|
2023-03-03 15:23:46 +00:00
|
|
|
* Implements functionality "Discard" from Main menu (Stash etc.)
|
|
|
|
* Removes item from PMC Profile
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public discardItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
body: IInventoryRemoveRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-01-16 12:21:42 +00:00
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
if (body.fromOwner?.type === "Mail")
|
|
|
|
{
|
2024-02-02 13:54:07 -05:00
|
|
|
this.inventoryHelper.removeItemAndChildrenFromMailRewards(sessionID, body, output);
|
2024-01-16 12:21:42 +00:00
|
|
|
|
2024-02-04 19:51:19 +00:00
|
|
|
return output;
|
2023-10-10 11:03:20 +00:00
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
const profileToRemoveItemFrom = (!body.fromOwner || body.fromOwner.id === pmcData._id)
|
|
|
|
? pmcData
|
|
|
|
: this.profileHelper.getFullProfile(sessionID).characters.scav;
|
|
|
|
|
2024-02-02 13:54:07 -05:00
|
|
|
return this.inventoryHelper.removeItem(profileToRemoveItemFrom, body.item, sessionID, output);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-10 11:03:20 +00:00
|
|
|
* Split Item
|
|
|
|
* spliting 1 stack into 2
|
|
|
|
* @param pmcData Player profile (unused, getOwnerInventoryItems() gets profile)
|
|
|
|
* @param request Split request
|
|
|
|
* @param sessionID Session/player id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public splitItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IInventorySplitRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// Changes made to result apply to character inventory
|
|
|
|
const inventoryItems = this.inventoryHelper.getOwnerInventoryItems(request, sessionID);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// Handle cartridge edge-case
|
|
|
|
if (!request.container.location && request.container.container === "cartridges")
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const matchingItems = inventoryItems.to.filter((x) => x.parentId === request.container.id);
|
2023-10-10 11:03:20 +00:00
|
|
|
request.container.location = matchingItems.length; // Wrong location for first cartridge
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// The item being merged has three possible sources: pmc, scav or mail, getOwnerInventoryItems() handles getting correct one
|
2023-11-16 21:42:06 +00:00
|
|
|
const itemToSplit = inventoryItems.from.find((x) => x._id === request.splitItem);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (!itemToSplit)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const errorMessage = `Unable to split stack as source item: ${request.splitItem} cannot be found`;
|
2023-10-10 11:03:20 +00:00
|
|
|
this.logger.error(errorMessage);
|
|
|
|
|
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, errorMessage);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// Create new upd object that retains properties of original upd + new stack count size
|
|
|
|
const updatedUpd = this.jsonUtil.clone(itemToSplit.upd);
|
|
|
|
updatedUpd.StackObjectsCount = request.count;
|
|
|
|
|
|
|
|
// Remove split item count from source stack
|
|
|
|
itemToSplit.upd.StackObjectsCount -= request.count;
|
|
|
|
|
|
|
|
// Inform client of change
|
|
|
|
output.profileChanges[sessionID].items.new.push({
|
|
|
|
_id: request.newItem,
|
|
|
|
_tpl: itemToSplit._tpl,
|
2023-11-16 21:42:06 +00:00
|
|
|
upd: updatedUpd,
|
2023-10-10 11:03:20 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Update player inventory
|
|
|
|
inventoryItems.to.push({
|
|
|
|
_id: request.newItem,
|
|
|
|
_tpl: itemToSplit._tpl,
|
|
|
|
parentId: request.container.id,
|
|
|
|
slotId: request.container.container,
|
|
|
|
location: request.container.location,
|
2023-11-16 21:42:06 +00:00
|
|
|
upd: updatedUpd,
|
2023-10-10 11:03:20 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return output;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-10 11:03:20 +00:00
|
|
|
* Fully merge 2 inventory stacks together into one stack (merging where both stacks remain is called 'transfer')
|
|
|
|
* Deletes item from `body.item` and adding number of stacks into `body.with`
|
|
|
|
* @param pmcData Player profile (unused, getOwnerInventoryItems() gets profile)
|
|
|
|
* @param body Merge request
|
|
|
|
* @param sessionID Player id
|
|
|
|
* @returns IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
*/
|
|
|
|
public mergeItem(pmcData: IPmcData, body: IInventoryMergeRequestData, sessionID: string): IItemEventRouterResponse
|
|
|
|
{
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// Changes made to result apply to character inventory
|
|
|
|
const inventoryItems = this.inventoryHelper.getOwnerInventoryItems(body, sessionID);
|
|
|
|
|
|
|
|
// Get source item (can be from player or trader or mail)
|
2023-11-16 21:42:06 +00:00
|
|
|
const sourceItem = inventoryItems.from.find((x) => x._id === body.item);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (!sourceItem)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const errorMessage = `Unable to merge stacks as source item: ${body.with} cannot be found`;
|
2023-10-10 11:03:20 +00:00
|
|
|
this.logger.error(errorMessage);
|
|
|
|
|
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, errorMessage);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// Get item being merged into
|
2023-11-16 21:42:06 +00:00
|
|
|
const destinationItem = inventoryItems.to.find((x) => x._id === body.with);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (!destinationItem)
|
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const errorMessage = `Unable to merge stacks as destination item: ${body.with} cannot be found`;
|
2023-10-10 11:03:20 +00:00
|
|
|
this.logger.error(errorMessage);
|
|
|
|
|
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, errorMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(destinationItem.upd?.StackObjectsCount))
|
|
|
|
{
|
|
|
|
// No stackcount on destination, add one
|
|
|
|
destinationItem.upd = { StackObjectsCount: 1 };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sourceItem.upd)
|
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
sourceItem.upd = { StackObjectsCount: 1 };
|
2023-10-10 11:03:20 +00:00
|
|
|
}
|
|
|
|
else if (!sourceItem.upd.StackObjectsCount)
|
|
|
|
{
|
|
|
|
// Items pulled out of raid can have no stackcount if the stack should be 1
|
|
|
|
sourceItem.upd.StackObjectsCount = 1;
|
|
|
|
}
|
|
|
|
|
2023-12-20 19:48:26 +00:00
|
|
|
// Remove FiR status from destination stack when source stack has no FiR but destination does
|
|
|
|
if (!sourceItem.upd.SpawnedInSession && destinationItem.upd.SpawnedInSession)
|
|
|
|
{
|
|
|
|
delete destinationItem.upd.SpawnedInSession;
|
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
destinationItem.upd.StackObjectsCount += sourceItem.upd.StackObjectsCount; // Add source stackcount to destination
|
|
|
|
output.profileChanges[sessionID].items.del.push({ _id: sourceItem._id }); // Inform client source item being deleted
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
const indexOfItemToRemove = inventoryItems.from.findIndex((x) => x._id === sourceItem._id);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (indexOfItemToRemove === -1)
|
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const errorMessage = `Unable to find item: ${sourceItem._id} to remove from sender inventory`;
|
2023-10-10 11:03:20 +00:00
|
|
|
this.logger.error(errorMessage);
|
|
|
|
|
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, errorMessage);
|
|
|
|
}
|
|
|
|
inventoryItems.from.splice(indexOfItemToRemove, 1); // remove source item from 'from' inventory
|
|
|
|
|
|
|
|
return output;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-10 11:03:20 +00:00
|
|
|
* TODO: Adds no data to output to send to client, is this by design?
|
|
|
|
* TODO: should make use of getOwnerInventoryItems(), stack being transferred may not always be on pmc
|
|
|
|
* Transfer items from one stack into another while keeping original stack
|
|
|
|
* Used to take items from scav inventory into stash or to insert ammo into mags (shotgun ones) and reloading weapon by clicking "Reload"
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param body Transfer request
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public transferItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
body: IInventoryTransferRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
let sourceItem: Item = null;
|
|
|
|
let destinationItem: Item = null;
|
2023-03-03 15:23:46 +00:00
|
|
|
for (const iterItem of pmcData.Inventory.items)
|
|
|
|
{
|
|
|
|
if (iterItem._id === body.item)
|
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
// Found source item
|
|
|
|
sourceItem = iterItem;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
else if (iterItem._id === body.with)
|
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
// Found destination item
|
|
|
|
destinationItem = iterItem;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
if (sourceItem !== null && destinationItem !== null)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
// Both items found, exit loop
|
2023-11-16 21:42:06 +00:00
|
|
|
break;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
if (sourceItem === null)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
const errorMessage = `Unable to transfer stack, cannot find source: ${body.item}`;
|
|
|
|
this.logger.error(errorMessage);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, errorMessage);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
if (destinationItem === null)
|
|
|
|
{
|
|
|
|
const errorMessage = `Unable to transfer stack, cannot find destination: ${body.with} `;
|
|
|
|
this.logger.error(errorMessage);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
return this.httpResponseUtil.appendErrorToOutput(output, errorMessage);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
let sourceStackCount = 1;
|
|
|
|
if (!sourceItem.upd)
|
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
sourceItem.upd = { StackObjectsCount: 1 };
|
2023-10-10 11:03:20 +00:00
|
|
|
}
|
|
|
|
sourceStackCount = sourceItem.upd.StackObjectsCount;
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
if (sourceStackCount > body.count)
|
|
|
|
{
|
|
|
|
// Source items stack count greater than new desired count
|
|
|
|
sourceItem.upd.StackObjectsCount = sourceStackCount - body.count;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Moving a full stack onto a smaller stack
|
|
|
|
sourceItem.upd.StackObjectsCount = sourceStackCount - 1;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
let destinationStackCount = 1;
|
|
|
|
if (destinationItem.upd)
|
|
|
|
{
|
|
|
|
destinationStackCount = destinationItem.upd.StackObjectsCount;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Object.assign(destinationItem, { upd: { StackObjectsCount: 1 } });
|
|
|
|
}
|
|
|
|
|
|
|
|
destinationItem.upd.StackObjectsCount = destinationStackCount + body.count;
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-11-16 21:42:06 +00:00
|
|
|
* Swap Item
|
|
|
|
* its used for "reload" if you have weapon in hands and magazine is somewhere else in rig or backpack in equipment
|
|
|
|
* Also used to swap items using quick selection on character screen
|
|
|
|
*/
|
2023-10-10 11:03:20 +00:00
|
|
|
public swapItem(pmcData: IPmcData, request: IInventorySwapRequestData, sessionID: string): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const itemOne = pmcData.Inventory.items.find((x) => x._id === request.item);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (!itemOne)
|
|
|
|
{
|
|
|
|
this.logger.error(`Unable to find item: ${request.item} to swap positions with: ${request.item2}`);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
const itemTwo = pmcData.Inventory.items.find((x) => x._id === request.item2);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (!itemTwo)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
this.logger.error(`Unable to find item: ${request.item2} to swap positions with: ${request.item}`);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// to.id is the parentid
|
|
|
|
itemOne.parentId = request.to.id;
|
2023-11-16 21:42:06 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
// to.container is the slotid
|
|
|
|
itemOne.slotId = request.to.container;
|
|
|
|
|
|
|
|
// Request object has location data, add it in, otherwise remove existing location from object
|
|
|
|
if (request.to.location)
|
|
|
|
{
|
|
|
|
itemOne.location = request.to.location;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-10-10 11:03:20 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
delete itemOne.location;
|
|
|
|
}
|
|
|
|
|
|
|
|
itemTwo.parentId = request.to2.id;
|
|
|
|
itemTwo.slotId = request.to2.container;
|
|
|
|
if (request.to2.location)
|
|
|
|
{
|
|
|
|
itemTwo.location = request.to2.location;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
delete itemTwo.location;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client already informed of inventory locations, nothing for us to do
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles folding of Weapons
|
|
|
|
*/
|
|
|
|
public foldItem(pmcData: IPmcData, body: IInventoryFoldRequestData, sessionID: string): IItemEventRouterResponse
|
|
|
|
{
|
|
|
|
// Fix for folding weapons while on they're in the Scav inventory
|
2023-11-16 21:42:06 +00:00
|
|
|
if (body.fromOwner && body.fromOwner.type === "Profile" && body.fromOwner.id !== pmcData._id)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
pmcData = this.profileHelper.getScavProfile(sessionID);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const item of pmcData.Inventory.items)
|
|
|
|
{
|
|
|
|
if (item._id && item._id === body.item)
|
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
item.upd.Foldable = { Folded: body.value };
|
2023-03-03 15:23:46 +00:00
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
return { warnings: [], profileChanges: {} };
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles "Toggleable" items like night vision goggles and face shields.
|
2023-03-26 16:52:40 +01:00
|
|
|
* @param pmcData player profile
|
|
|
|
* @param body Toggle request
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
*/
|
|
|
|
public toggleItem(pmcData: IPmcData, body: IInventoryToggleRequestData, sessionID: string): IItemEventRouterResponse
|
|
|
|
{
|
|
|
|
// Fix for toggling items while on they're in the Scav inventory
|
|
|
|
if (body.fromOwner && body.fromOwner.type === "Profile" && body.fromOwner.id !== pmcData._id)
|
|
|
|
{
|
|
|
|
pmcData = this.profileHelper.getScavProfile(sessionID);
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
const itemToToggle = pmcData.Inventory.items.find((x) => x._id === body.item);
|
2023-03-26 16:52:40 +01:00
|
|
|
if (itemToToggle)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-03-26 16:52:40 +01:00
|
|
|
if (!itemToToggle.upd)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
this.logger.warning(
|
|
|
|
this.localisationService.getText("inventory-item_to_toggle_missing_upd", itemToToggle._id),
|
|
|
|
);
|
2023-03-26 16:52:40 +01:00
|
|
|
itemToToggle.upd = {};
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-03-26 16:52:40 +01:00
|
|
|
|
|
|
|
itemToToggle.upd.Togglable = { On: body.value };
|
|
|
|
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
2024-02-01 13:58:06 +00:00
|
|
|
|
2024-02-02 13:54:07 -05:00
|
|
|
this.logger.warning(this.localisationService.getText("inventory-unable_to_toggle_item_not_found", body.item));
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
return { warnings: [], profileChanges: {} };
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a tag to an inventory item
|
|
|
|
* @param pmcData profile with item to add tag to
|
|
|
|
* @param body tag request data
|
|
|
|
* @param sessionID session id
|
|
|
|
* @returns client response object
|
|
|
|
*/
|
|
|
|
public tagItem(pmcData: IPmcData, body: IInventoryTagRequestData, sessionID: string): IItemEventRouterResponse
|
|
|
|
{
|
|
|
|
for (const item of pmcData.Inventory.items)
|
|
|
|
{
|
|
|
|
if (item._id === body.item)
|
|
|
|
{
|
|
|
|
if ("upd" in item)
|
|
|
|
{
|
|
|
|
item.upd.Tag = { Color: body.TagColor, Name: body.TagName };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
item.upd = { Tag: { Color: body.TagColor, Name: body.TagName } };
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
return { warnings: [], profileChanges: {} };
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 17:53:28 +00:00
|
|
|
/**
|
|
|
|
* Bind an inventory item to the quick access menu at bottom of player screen
|
2023-11-12 10:10:34 +00:00
|
|
|
* Handle bind event
|
2023-03-03 17:53:28 +00:00
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param bindRequest Reqeust object
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public bindItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
bindRequest: IInventoryBindRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
for (const index in pmcData.Inventory.fastPanel)
|
|
|
|
{
|
2023-11-12 10:10:34 +00:00
|
|
|
// Find item with existing item in it and remove existing binding, you cant have same item bound to more than 1 slot
|
2023-03-03 17:53:28 +00:00
|
|
|
if (pmcData.Inventory.fastPanel[index] === bindRequest.item)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
pmcData.Inventory.fastPanel[index] = "";
|
2023-11-12 10:10:34 +00:00
|
|
|
|
|
|
|
break;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-12 10:10:34 +00:00
|
|
|
// Create link between fast panel slot and requested item
|
2023-03-03 17:53:28 +00:00
|
|
|
pmcData.Inventory.fastPanel[bindRequest.index] = bindRequest.item;
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
|
|
|
|
2023-11-12 10:10:34 +00:00
|
|
|
/**
|
|
|
|
* Unbind an inventory item from quick access menu at bottom of player screen
|
|
|
|
* Handle unbind event
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param bindRequest Request object
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public unbindItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IInventoryBindRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-11-12 10:10:34 +00:00
|
|
|
{
|
|
|
|
// Remove kvp from requested fast panel index
|
|
|
|
delete pmcData.Inventory.fastPanel[request.index];
|
|
|
|
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
/**
|
|
|
|
* Handles examining an item
|
|
|
|
* @param pmcData player profile
|
|
|
|
* @param body request object
|
|
|
|
* @param sessionID session id
|
|
|
|
* @returns response
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public examineItem(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
body: IInventoryExamineRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
let itemId = "";
|
|
|
|
if ("fromOwner" in body)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
itemId = this.getExaminedItemTpl(body);
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
this.logger.error(this.localisationService.getText("inventory-examine_item_does_not_exist", body.item));
|
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
// get hideout item
|
|
|
|
if (body.fromOwner.type === "HideoutProduction")
|
|
|
|
{
|
|
|
|
itemId = body.item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!itemId)
|
|
|
|
{
|
|
|
|
// item template
|
|
|
|
if (body.item in this.databaseServer.getTables().templates.items)
|
|
|
|
{
|
|
|
|
itemId = body.item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!itemId)
|
|
|
|
{
|
|
|
|
// player inventory
|
|
|
|
const target = pmcData.Inventory.items.find((item) =>
|
|
|
|
{
|
|
|
|
return body.item === item._id;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (target)
|
|
|
|
{
|
|
|
|
itemId = target._tpl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (itemId)
|
|
|
|
{
|
2023-12-07 20:16:04 +00:00
|
|
|
this.flagItemsAsInspectedAndRewardXp([itemId], pmcData);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-12-07 20:16:04 +00:00
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
2023-07-13 10:26:47 +01:00
|
|
|
|
2023-12-07 20:16:04 +00:00
|
|
|
protected flagItemsAsInspectedAndRewardXp(itemTpls: string[], pmcProfile: IPmcData): void
|
|
|
|
{
|
|
|
|
for (const itemTpl of itemTpls)
|
|
|
|
{
|
|
|
|
// item found
|
|
|
|
const item = this.databaseServer.getTables().templates.items[itemTpl];
|
|
|
|
if (!item)
|
|
|
|
{
|
2024-02-02 13:54:07 -05:00
|
|
|
this.logger.warning(`Unable to find item with id ${itemTpl}, skipping inspection`);
|
2023-12-07 20:16:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pmcProfile.Info.Experience += item._props.ExamineExperience;
|
2023-12-07 20:37:56 +00:00
|
|
|
pmcProfile.Encyclopedia[itemTpl] = false;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-12-07 20:16:04 +00:00
|
|
|
// TODO: update this with correct calculation using values from globals json
|
|
|
|
this.profileHelper.addSkillPointsToPlayer(pmcProfile, SkillTypes.INTELLECT, 0.05 * itemTpls.length);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the tplid of an item from the examine request object
|
2024-02-01 13:53:36 +00:00
|
|
|
* @param request Response request
|
|
|
|
* @returns tplId
|
2023-03-03 15:23:46 +00:00
|
|
|
*/
|
2024-02-01 13:53:36 +00:00
|
|
|
protected getExaminedItemTpl(request: IInventoryExamineRequestData): string
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-02-01 13:53:36 +00:00
|
|
|
if (this.presetHelper.isPreset(request.item))
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-02-01 13:53:36 +00:00
|
|
|
return this.presetHelper.getBaseItemTpl(request.item);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2024-02-01 13:53:36 +00:00
|
|
|
|
|
|
|
if (request.fromOwner.id === Traders.FENCE)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-02-01 13:53:36 +00:00
|
|
|
// Get tpl from fence assorts
|
|
|
|
return this.fenceService.getRawFenceAssorts().items.find((x) => x._id === request.item)._tpl;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2024-02-01 13:53:36 +00:00
|
|
|
|
|
|
|
if (request.fromOwner.type === "Trader")
|
|
|
|
{
|
|
|
|
// Not fence
|
2023-03-03 15:23:46 +00:00
|
|
|
// get tpl from trader assort
|
2024-02-01 13:53:36 +00:00
|
|
|
return this.databaseServer.getTables().traders[request.fromOwner.id].assort.items.find((item) =>
|
|
|
|
item._id === request.item
|
2023-11-16 21:42:06 +00:00
|
|
|
)._tpl;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2024-02-01 13:53:36 +00:00
|
|
|
|
|
|
|
if (request.fromOwner.type === "RagFair")
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-02-01 13:53:36 +00:00
|
|
|
// Try to get tplid from items.json first
|
|
|
|
const item = this.itemHelper.getItem(request.item);
|
|
|
|
if (item[0])
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-02-01 13:53:36 +00:00
|
|
|
return item[1]._id;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 13:53:36 +00:00
|
|
|
// Try alternate way of getting offer if first approach fails
|
|
|
|
let offer = this.ragfairOfferService.getOfferByOfferId(request.item);
|
2023-03-03 15:23:46 +00:00
|
|
|
if (!offer)
|
|
|
|
{
|
2024-02-01 13:53:36 +00:00
|
|
|
offer = this.ragfairOfferService.getOfferByOfferId(request.fromOwner.id);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 13:53:36 +00:00
|
|
|
// Try find examine item inside offer items array
|
|
|
|
const matchingItem = offer.items.find((offerItem) => offerItem._id === request.item);
|
2023-03-03 15:23:46 +00:00
|
|
|
if (matchingItem)
|
|
|
|
{
|
|
|
|
return matchingItem._tpl;
|
2023-11-16 21:42:06 +00:00
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-02-01 13:53:36 +00:00
|
|
|
// Unable to find item in database or ragfair
|
|
|
|
throw new Error(this.localisationService.getText("inventory-unable_to_find_item", request.item));
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
public readEncyclopedia(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
body: IInventoryReadEncyclopediaRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
for (const id of body.ids)
|
|
|
|
{
|
|
|
|
pmcData.Encyclopedia[id] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle ApplyInventoryChanges
|
|
|
|
* Sorts supplied items.
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param request sort request
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public sortInventory(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IInventorySortRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-10-10 11:03:20 +00:00
|
|
|
for (const change of request.changedItems)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const inventoryItem = pmcData.Inventory.items.find((x) => x._id === change._id);
|
2023-10-10 11:03:20 +00:00
|
|
|
if (!inventoryItem)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
this.logger.error(
|
|
|
|
`Unable to find inventory item: ${change._id} to auto-sort, YOU MUST RELOAD YOUR GAME`,
|
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
continue;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 11:03:20 +00:00
|
|
|
inventoryItem.parentId = change.parentId;
|
|
|
|
inventoryItem.slotId = change.slotId;
|
|
|
|
if (change.location)
|
|
|
|
{
|
|
|
|
inventoryItem.location = change.location;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
delete inventoryItem.location;
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add note to a map
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param request Add marker request
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public createMapMarker(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IInventoryCreateMarkerRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Get map from inventory
|
2023-11-16 21:42:06 +00:00
|
|
|
const mapItem = pmcData.Inventory.items.find((i) => i._id === request.item);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
// add marker
|
|
|
|
mapItem.upd.Map = mapItem.upd.Map || { Markers: [] };
|
|
|
|
request.mapMarker.Note = this.sanitiseMapMarkerText(request.mapMarker.Note);
|
|
|
|
mapItem.upd.Map.Markers.push(request.mapMarker);
|
|
|
|
|
|
|
|
// sync with client
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
output.profileChanges[sessionID].items.change.push(mapItem);
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a map marker
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param request Delete marker request
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public deleteMapMarker(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IInventoryDeleteMarkerRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Get map from inventory
|
2023-11-16 21:42:06 +00:00
|
|
|
const mapItem = pmcData.Inventory.items.find((i) => i._id === request.item);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
// remove marker
|
|
|
|
const markers = mapItem.upd.Map.Markers.filter((marker) =>
|
|
|
|
{
|
|
|
|
return marker.X !== request.X && marker.Y !== request.Y;
|
|
|
|
});
|
|
|
|
mapItem.upd.Map.Markers = markers;
|
|
|
|
|
|
|
|
// sync with client
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
output.profileChanges[sessionID].items.change.push(mapItem);
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Edit an existing map marker
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param request Edit marker request
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public editMapMarker(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IInventoryEditMarkerRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Get map from inventory
|
2023-11-16 21:42:06 +00:00
|
|
|
const mapItem = pmcData.Inventory.items.find((i) => i._id === request.item);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
// edit marker
|
2023-11-16 21:42:06 +00:00
|
|
|
const indexOfExistingNote = mapItem.upd.Map.Markers.findIndex((m) => m.X === request.X && m.Y === request.Y);
|
2023-03-03 15:23:46 +00:00
|
|
|
request.mapMarker.Note = this.sanitiseMapMarkerText(request.mapMarker.Note);
|
|
|
|
mapItem.upd.Map.Markers[indexOfExistingNote] = request.mapMarker;
|
|
|
|
|
|
|
|
// sync with client
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
output.profileChanges[sessionID].items.change.push(mapItem);
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Strip out characters from note string that are not: letter/numbers/unicode/spaces
|
|
|
|
* @param mapNoteText Marker text to sanitise
|
|
|
|
* @returns Sanitised map marker text
|
|
|
|
*/
|
|
|
|
protected sanitiseMapMarkerText(mapNoteText: string): string
|
|
|
|
{
|
|
|
|
return mapNoteText.replace(/[^\p{L}\d ]/gu, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-07-15 14:49:25 +01:00
|
|
|
* Handle OpenRandomLootContainer event
|
2023-03-03 15:23:46 +00:00
|
|
|
* Handle event fired when a container is unpacked (currently only the halloween pumpkin)
|
|
|
|
* @param pmcData Profile data
|
|
|
|
* @param body open loot container request data
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @returns IItemEventRouterResponse
|
|
|
|
*/
|
2023-11-16 21:42:06 +00:00
|
|
|
public openRandomLootContainer(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
body: IOpenRandomLootContainerRequestData,
|
|
|
|
sessionID: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2024-01-20 22:13:47 +00:00
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-01-20 22:13:47 +00:00
|
|
|
/** Container player opened in their inventory */
|
2024-02-01 13:26:18 +00:00
|
|
|
const openedItem = pmcData.Inventory.items.find((item) => item._id === body.item);
|
2024-01-20 22:13:47 +00:00
|
|
|
const containerDetailsDb = this.itemHelper.getItem(openedItem._tpl);
|
|
|
|
const isSealedWeaponBox = containerDetailsDb[1]._name.includes("event_container_airdrop");
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-01-20 22:13:47 +00:00
|
|
|
let foundInRaid = openedItem.upd?.SpawnedInSession;
|
|
|
|
const rewards: Item[][] = [];
|
2023-06-20 16:07:05 +01:00
|
|
|
if (isSealedWeaponBox)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-06-20 17:19:53 +01:00
|
|
|
const containerSettings = this.inventoryHelper.getInventoryConfig().sealedAirdropContainer;
|
2024-01-20 22:13:47 +00:00
|
|
|
rewards.push(...this.lootGenerator.getSealedWeaponCaseLoot(containerSettings));
|
2023-06-20 17:19:53 +01:00
|
|
|
|
2024-01-20 22:13:47 +00:00
|
|
|
if (containerSettings.foundInRaid)
|
|
|
|
{
|
2024-02-02 13:54:07 -05:00
|
|
|
foundInRaid = containerSettings.foundInRaid;
|
2024-01-20 22:13:47 +00:00
|
|
|
}
|
2023-06-20 16:07:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-20 17:19:53 +01:00
|
|
|
const rewardContainerDetails = this.inventoryHelper.getRandomLootContainerRewardDetails(openedItem._tpl);
|
2024-01-20 22:13:47 +00:00
|
|
|
rewards.push(...this.lootGenerator.getRandomLootContainerLoot(rewardContainerDetails));
|
2023-06-20 17:19:53 +01:00
|
|
|
|
2024-01-20 22:13:47 +00:00
|
|
|
if (rewardContainerDetails.foundInRaid)
|
|
|
|
{
|
|
|
|
foundInRaid = rewardContainerDetails.foundInRaid;
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 13:23:03 +00:00
|
|
|
const addItemsRequest: IAddItemsDirectRequest = {
|
|
|
|
itemsWithModsToAdd: rewards,
|
|
|
|
foundInRaid: foundInRaid,
|
|
|
|
callback: null,
|
2024-02-02 13:54:07 -05:00
|
|
|
useSortingTable: true,
|
|
|
|
};
|
2024-02-01 13:23:03 +00:00
|
|
|
this.inventoryHelper.addItemsToStash(sessionID, addItemsRequest, pmcData, output);
|
2024-02-01 13:26:18 +00:00
|
|
|
if (output.warnings.length > 0)
|
|
|
|
{
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2024-02-01 13:31:36 +00:00
|
|
|
// Find and delete opened container item from player inventory
|
2024-02-01 13:26:18 +00:00
|
|
|
this.inventoryHelper.removeItem(pmcData, body.item, sessionID, output);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
2023-12-07 20:16:04 +00:00
|
|
|
|
2024-02-02 13:54:07 -05:00
|
|
|
public redeemProfileReward(
|
|
|
|
pmcData: IPmcData,
|
|
|
|
request: IRedeemProfileRequestData,
|
|
|
|
sessionId: string,
|
|
|
|
): IItemEventRouterResponse
|
2023-12-07 20:16:04 +00:00
|
|
|
{
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionId);
|
|
|
|
|
|
|
|
const fullprofile = this.profileHelper.getFullProfile(sessionId);
|
|
|
|
for (const event of request.events)
|
|
|
|
{
|
|
|
|
// Hard coded to `SYSTEM` for now
|
|
|
|
// TODO: make this dynamic
|
|
|
|
const dialog = fullprofile.dialogues["59e7125688a45068a6249071"];
|
2024-02-02 13:54:07 -05:00
|
|
|
const mail = dialog.messages.find((x) => x._id === event.MessageId);
|
|
|
|
const mailEvent = mail.profileChangeEvents.find((x) => x._id === event.EventId);
|
2023-12-07 20:16:04 +00:00
|
|
|
|
|
|
|
switch (mailEvent.Type)
|
|
|
|
{
|
|
|
|
case "TraderSalesSum":
|
|
|
|
pmcData.TradersInfo[mailEvent.entity].salesSum = mailEvent.value;
|
|
|
|
this.logger.success(`Set trader ${mailEvent.entity}: Sales Sum to: ${mailEvent.value}`);
|
|
|
|
break;
|
|
|
|
case "TraderStanding":
|
|
|
|
pmcData.TradersInfo[mailEvent.entity].standing = mailEvent.value;
|
|
|
|
this.logger.success(`Set trader ${mailEvent.entity}: Standing to: ${mailEvent.value}`);
|
|
|
|
break;
|
|
|
|
case "ProfileLevel":
|
|
|
|
pmcData.Info.Experience = mailEvent.value;
|
|
|
|
pmcData.Info.Level = this.playerService.calculateLevel(pmcData);
|
|
|
|
this.logger.success(`Set profile xp to: ${mailEvent.value}`);
|
|
|
|
break;
|
|
|
|
case "SkillPoints":
|
|
|
|
{
|
2024-02-02 13:54:07 -05:00
|
|
|
const profileSkill = pmcData.Skills.Common.find((x) => x.Id === mailEvent.entity);
|
2023-12-07 20:16:04 +00:00
|
|
|
profileSkill.Progress = mailEvent.value;
|
|
|
|
this.logger.success(`Set profile skill: ${mailEvent.entity} to: ${mailEvent.value}`);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "ExamineAllItems":
|
|
|
|
{
|
2024-02-02 13:54:07 -05:00
|
|
|
const itemsToInspect = this.itemHelper.getItems().filter((x) => x._type !== "Node");
|
|
|
|
this.flagItemsAsInspectedAndRewardXp(itemsToInspect.map((x) => x._id), pmcData);
|
2023-12-07 20:16:04 +00:00
|
|
|
this.logger.success(`Flagged ${itemsToInspect.length} items as examined`);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "UnlockTrader":
|
|
|
|
pmcData.TradersInfo[mailEvent.entity].unlocked = true;
|
|
|
|
this.logger.success(`Trader ${mailEvent.entity} Unlocked`);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this.logger.success(`Unhandled profile reward event: ${mailEvent.Type}`);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
2023-12-29 20:22:50 +00:00
|
|
|
|
|
|
|
public setFavoriteItem(pmcData: IPmcData, request: ISetFavoriteItems, sessionId: string): IItemEventRouterResponse
|
|
|
|
{
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionId);
|
|
|
|
|
|
|
|
if (!pmcData.Inventory.favoriteItems)
|
|
|
|
{
|
|
|
|
pmcData.Inventory.favoriteItems = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const itemId of request.items)
|
|
|
|
{
|
|
|
|
// If id already exists in array, we're removing it
|
2024-02-02 13:54:07 -05:00
|
|
|
const indexOfItemAlreadyFavorited = pmcData.Inventory.favoriteItems.findIndex((x) => x === itemId);
|
2023-12-29 20:22:50 +00:00
|
|
|
if (indexOfItemAlreadyFavorited > -1)
|
|
|
|
{
|
|
|
|
pmcData.Inventory.favoriteItems.splice(indexOfItemAlreadyFavorited, 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pmcData.Inventory.favoriteItems.push(itemId);
|
|
|
|
}
|
|
|
|
}
|
2024-02-02 13:54:07 -05:00
|
|
|
|
2023-12-29 20:22:50 +00:00
|
|
|
return output;
|
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
}
|