diff --git a/project/src/controllers/HideoutController.ts b/project/src/controllers/HideoutController.ts index 68279e7e..4f8679d9 100644 --- a/project/src/controllers/HideoutController.ts +++ b/project/src/controllers/HideoutController.ts @@ -592,23 +592,31 @@ export class HideoutController const recipe = this.databaseServer.getTables().hideout.production.find((p) => p._id === body.recipeId); // Find the actual amount of items we need to remove because body can send weird data - const recipeRequirementsClone = this.jsonUtil.clone(recipe.requirements.filter((i) => i.type === "Item")); + const recipeRequirementsClone = this.jsonUtil.clone(recipe.requirements.filter((i) => i.type === "Item" || i.type === "Tool")); const output = this.eventOutputHolder.getOutput(sessionID); - - for (const itemToDelete of body.items) + const itemsToDelete = body.items.concat(body.tools); + for (const itemToDelete of itemsToDelete) { const itemToCheck = pmcData.Inventory.items.find((i) => i._id === itemToDelete.id); const requirement = recipeRequirementsClone.find((requirement) => requirement.templateId === itemToCheck._tpl ); - if (requirement.count <= 0) + + // Handle tools not having a `count`, but always only requiring 1 + const requiredCount = requirement.count ?? 1; + if (requiredCount <= 0) { continue; } - this.inventoryHelper.removeItemByCount(pmcData, itemToDelete.id, requirement.count, sessionID, output); - requirement.count -= itemToDelete.count; + this.inventoryHelper.removeItemByCount(pmcData, itemToDelete.id, requiredCount, sessionID, output); + + // Tools don't have a count + if (requirement.type !== "Tool") + { + requirement.count -= itemToDelete.count; + } } return output; @@ -793,6 +801,46 @@ export class HideoutController output: IItemEventRouterResponse, ): void { + // Validate that we have a matching production + const productionDict = Object.entries(pmcData.Hideout.Production); + let prodId: string; + for (const [productionId, production] of productionDict) + { + // Skip null production objects + if (!production) + { + continue; + } + + if (this.hideoutHelper.isProductionType(production)) + { + // Production or ScavCase + if (production.RecipeId === request.recipeId) + { + prodId = productionId; // Set to objects key + break; + } + } + } + + // If we're unable to find the production, send an error to the client + if (prodId === undefined) + { + this.logger.error( + this.localisationService.getText( + "hideout-unable_to_find_production_in_profile_by_recipie_id", + request.recipeId, + ), + ); + + this.httpResponse.appendErrorToOutput(output, this.localisationService.getText( + "hideout-unable_to_find_production_in_profile_by_recipie_id", + request.recipeId, + )); + + return; + } + // Variables for managemnet of skill let craftingExpAmount = 0; @@ -865,42 +913,29 @@ export class HideoutController } } - // Loops over all current productions on profile - we want to find a matching production - const productionDict = Object.entries(pmcData.Hideout.Production); - let prodId: string; - for (const production of productionDict) + // Build an array of the tools that need to be returned to the player + const toolsToSendToPlayer: Item[][] = []; + const production = pmcData.Hideout.Production[prodId]; + if (production.sptRequiredTools?.length > 0) { - // Skip null production objects - if (!production[1]) + for (const tool of production.sptRequiredTools) { - continue; - } + const toolToAdd: Item = { + _id: this.hashUtil.generate(), + _tpl: tool, + }; - if (this.hideoutHelper.isProductionType(production[1])) - { - // Production or ScavCase - if ((production[1] as Production).RecipeId === request.recipeId) + if (this.itemHelper.isItemTplStackable(tool)) { - prodId = production[0]; // Set to objects key - break; + toolToAdd.upd = { + StackObjectsCount: 1, + } } + + toolsToSendToPlayer.push([toolToAdd]); } } - if (prodId === undefined) - { - this.logger.error( - this.localisationService.getText( - "hideout-unable_to_find_production_in_profile_by_recipie_id", - request.recipeId, - ), - ); - - this.httpResponse.appendErrorToOutput(output); - - return; - } - // Check if the recipe is the same as the last one - get bonus when crafting same thing multiple times const area = pmcData.Hideout.Areas.find((area) => area.type === recipe.areaType); if (area && request.recipeId !== area.lastRecipe) @@ -920,15 +955,34 @@ export class HideoutController hoursCrafting -= this.hideoutConfig.hoursForSkillCrafting * multiplierCrafting; } - // Create request for what we want to add to stash + // Make sure we can fit both the craft result and tools in the stash + const totalResultItems = toolsToSendToPlayer.concat(itemAndChildrenToSendToPlayer); + if (!this.inventoryHelper.canPlaceItemsInInventory(sessionID, totalResultItems)) + { + this.httpResponse.appendErrorToOutput(output, this.localisationService.getText("inventory-no_stash_space")); + return; + } + + // Add the used tools to the stash as non-FiR + const addToolsRequest: IAddItemsDirectRequest = { + itemsWithModsToAdd: toolsToSendToPlayer, + foundInRaid: false, + useSortingTable: false, + callback: null, + }; + this.inventoryHelper.addItemsToStash(sessionID, addToolsRequest, pmcData, output); + if (output.warnings.length > 0) + { + return; + } + + // Add the crafting result to the stash, marked as FiR const addItemsRequest: IAddItemsDirectRequest = { itemsWithModsToAdd: itemAndChildrenToSendToPlayer, foundInRaid: true, useSortingTable: false, callback: null, }; - - // Add FiR crafted items(s) to player inventory this.inventoryHelper.addItemsToStash(sessionID, addItemsRequest, pmcData, output); if (output.warnings.length > 0) { diff --git a/project/src/controllers/PresetController.ts b/project/src/controllers/PresetController.ts index ceaa0b12..a3365ebc 100644 --- a/project/src/controllers/PresetController.ts +++ b/project/src/controllers/PresetController.ts @@ -2,12 +2,14 @@ import { inject, injectable } from "tsyringe"; import { PresetHelper } from "@spt-aki/helpers/PresetHelper"; import { IPreset } from "@spt-aki/models/eft/common/IGlobals"; +import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; @injectable() export class PresetController { constructor( + @inject("WinstonLogger") protected logger: ILogger, @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("DatabaseServer") protected databaseServer: DatabaseServer, ) @@ -15,11 +17,17 @@ export class PresetController public initialize(): void { - const presets: IPreset[] = Object.values(this.databaseServer.getTables().globals.ItemPresets); + const presets: [string, IPreset][] = Object.entries(this.databaseServer.getTables().globals.ItemPresets); const reverse: Record = {}; - for (const preset of presets) + for (const [id, preset] of presets) { + if (id != preset._id) + { + this.logger.error(`Preset for template '${preset._items[0]._tpl}' has invalid id (${id} != ${preset._id}). Skipping`); + continue; + } + const tpl = preset._items[0]._tpl; if (!(tpl in reverse)) diff --git a/project/src/helpers/HideoutHelper.ts b/project/src/helpers/HideoutHelper.ts index 09631ac9..a9da0695 100644 --- a/project/src/helpers/HideoutHelper.ts +++ b/project/src/helpers/HideoutHelper.ts @@ -95,11 +95,20 @@ export class HideoutHelper modifiedProductionTime = 40; } - pmcData.Hideout.Production[body.recipeId] = this.initProduction( + const production = this.initProduction( body.recipeId, modifiedProductionTime, recipe.needFuelForAllProductionTime, ); + + // Store the tools used for this production, so we can return them later + const productionTools = recipe.requirements.filter(req => req.type === "Tool").map(req => req.templateId); + if (productionTools.length > 0) + { + production.sptRequiredTools = productionTools; + } + + pmcData.Hideout.Production[body.recipeId] = production; } /** @@ -539,6 +548,7 @@ export class HideoutHelper recipeId: HideoutHelper.waterCollector, Action: "HideoutSingleProductionStart", items: [], + tools: [], timestamp: this.timeUtil.getTimestamp(), }; diff --git a/project/src/models/eft/common/tables/IBotBase.ts b/project/src/models/eft/common/tables/IBotBase.ts index accbcaba..16627b6a 100644 --- a/project/src/models/eft/common/tables/IBotBase.ts +++ b/project/src/models/eft/common/tables/IBotBase.ts @@ -393,6 +393,8 @@ export interface Productive sptIsComplete?: boolean; /** Is the craft a Continuous, e.g bitcoins/water collector */ sptIsContinuous?: boolean; + /** Stores a list of tools used in this craft, to give back once the craft is done */ + sptRequiredTools?: string[]; } export interface Production extends Productive diff --git a/project/src/models/eft/hideout/IHideoutSingleProductionStartRequestData.ts b/project/src/models/eft/hideout/IHideoutSingleProductionStartRequestData.ts index b63bd356..8e4ff655 100644 --- a/project/src/models/eft/hideout/IHideoutSingleProductionStartRequestData.ts +++ b/project/src/models/eft/hideout/IHideoutSingleProductionStartRequestData.ts @@ -3,6 +3,7 @@ export interface IHideoutSingleProductionStartRequestData Action: "HideoutSingleProductionStart"; recipeId: string; items: Item[]; + tools: Item[]; timestamp: number; }