Properly take and return tools when crafting (!234)
When starting a craft, tools are now taken, and the templateId is stored in the production in the user profile When finishing a craft, space for both the tools and crafted item is verified, then both are added to the player stash correctly flagged as non-FiR and FiR respectively Included a bit of code cleanup/reorg in areas I was working in A few assumptions were made: - Tools are expected to be single items, not stacks of an item (productions.json doesn't include a count property for tools, so this seems safe) - Tools will never be a preset or have child items - That the `canPlaceItemsInInventory` method over a concatenation of the tools and crafted item(s) will result in the same result as calling it individually over the two collections of items individually Will need tested once merged into 380, I did basic testing, but there's a lot of different crafts that require tools Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/234 Co-authored-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com> Co-committed-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
This commit is contained in:
parent
19013a478f
commit
2adbb6a5fe
@ -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)
|
||||
{
|
||||
|
@ -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(),
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -3,6 +3,7 @@ export interface IHideoutSingleProductionStartRequestData
|
||||
Action: "HideoutSingleProductionStart";
|
||||
recipeId: string;
|
||||
items: Item[];
|
||||
tools: Item[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user