Closes #1922 Reviewed-on: #2034 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
161 lines
6.7 KiB
TypeScript
161 lines
6.7 KiB
TypeScript
//this is a controller for the claimCompletedRecipe route
|
|
//it will claim a recipe for the user
|
|
|
|
import { RequestHandler } from "express";
|
|
import { logger } from "@/src/utils/logger";
|
|
import { getRecipe } from "@/src/services/itemDataService";
|
|
import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
import { getAccountForRequest } from "@/src/services/loginService";
|
|
import {
|
|
getInventory,
|
|
updateCurrency,
|
|
addItem,
|
|
addRecipes,
|
|
occupySlot,
|
|
combineInventoryChanges
|
|
} from "@/src/services/inventoryService";
|
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
import { IRecipe } from "warframe-public-export-plus";
|
|
import { config } from "@/src/services/configService";
|
|
|
|
interface IClaimCompletedRecipeRequest {
|
|
RecipeIds: IOid[];
|
|
}
|
|
|
|
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
|
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
|
const account = await getAccountForRequest(req);
|
|
const inventory = await getInventory(account._id.toString());
|
|
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
|
if (!pendingRecipe) {
|
|
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
|
}
|
|
|
|
//check recipe is indeed ready to be completed
|
|
// if (pendingRecipe.CompletionDate > new Date()) {
|
|
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
|
|
// }
|
|
|
|
inventory.PendingRecipes.pull(pendingRecipe._id);
|
|
|
|
const recipe = getRecipe(pendingRecipe.ItemType);
|
|
if (!recipe) {
|
|
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
|
|
}
|
|
|
|
if (req.query.cancel) {
|
|
const inventoryChanges: IInventoryChanges = {};
|
|
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
|
await inventory.save();
|
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
|
} else {
|
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
|
|
|
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
|
inventory.PendingSpectreLoadouts ??= [];
|
|
inventory.SpectreLoadouts ??= [];
|
|
|
|
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
|
|
x => x.ItemType == recipe.resultType
|
|
);
|
|
if (pendingLoadoutIndex != -1) {
|
|
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
|
if (loadoutIndex != -1) {
|
|
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
|
|
}
|
|
logger.debug(
|
|
"moving spectre loadout from pending to active",
|
|
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
|
|
);
|
|
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
|
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
|
}
|
|
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
|
inventory.BrandedSuits!.splice(
|
|
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
|
1
|
|
);
|
|
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
|
}
|
|
|
|
let InventoryChanges: IInventoryChanges = {};
|
|
if (recipe.consumeOnUse) {
|
|
addRecipes(inventory, [
|
|
{
|
|
ItemType: pendingRecipe.ItemType,
|
|
ItemCount: -1
|
|
}
|
|
]);
|
|
}
|
|
if (req.query.rush) {
|
|
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
|
const start = end - recipe.buildTime;
|
|
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
|
const progress = secondsElapsed / recipe.buildTime;
|
|
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
|
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
|
InventoryChanges = {
|
|
...InventoryChanges,
|
|
...updateCurrency(inventory, cost, true)
|
|
};
|
|
}
|
|
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
|
InventoryChanges = {
|
|
...InventoryChanges,
|
|
...(await addItem(
|
|
inventory,
|
|
recipe.resultType,
|
|
recipe.num,
|
|
false,
|
|
undefined,
|
|
pendingRecipe.TargetFingerprint
|
|
))
|
|
};
|
|
}
|
|
if (config.claimingBlueprintRefundsIngredients) {
|
|
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
|
}
|
|
await inventory.save();
|
|
res.json({ InventoryChanges, BrandedSuits });
|
|
}
|
|
};
|
|
|
|
const refundRecipeIngredients = async (
|
|
inventory: TInventoryDatabaseDocument,
|
|
inventoryChanges: IInventoryChanges,
|
|
recipe: IRecipe,
|
|
pendingRecipe: IPendingRecipeDatabase
|
|
): Promise<void> => {
|
|
updateCurrency(inventory, recipe.buildPrice * -1, false, inventoryChanges);
|
|
|
|
const equipmentIngredients = new Set();
|
|
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
|
if (pendingRecipe[category]) {
|
|
pendingRecipe[category].forEach(item => {
|
|
const index = inventory[category].push(item) - 1;
|
|
inventoryChanges[category] ??= [];
|
|
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
|
equipmentIngredients.add(item.ItemType);
|
|
|
|
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
|
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
|
inventoryChanges.WeaponBin.Slots -= 1;
|
|
});
|
|
}
|
|
}
|
|
for (const ingredient of recipe.ingredients) {
|
|
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
|
combineInventoryChanges(
|
|
inventoryChanges,
|
|
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
|
);
|
|
}
|
|
}
|
|
};
|