From 90ffd8948bc21413da691cbf25ef7be32e4a2de8 Mon Sep 17 00:00:00 2001 From: VoltPrime Date: Fri, 14 Nov 2025 01:50:19 -0800 Subject: [PATCH] fix: claiming recipes in U22.20-U24.1 + disable rush cost scaling for builds older than U18 (#3024) With this, the Foundry should be fully functional in all game versions now (excluding incorrect recipe data for things that got changed over the years). This also disables rush cost scaling for versions older than U18, as U18 is the version that introduced it. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3024 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: VoltPrime Co-committed-by: VoltPrime --- .../api/claimCompletedRecipeController.ts | 83 ++++++++++++------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index 40966270..b4325da9 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -22,14 +22,15 @@ import { import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { IPendingRecipeDatabase } from "../../types/inventoryTypes/inventoryTypes.ts"; import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; -import { fromOid, toOid2 } from "../../helpers/inventoryHelpers.ts"; +import { fromOid, toOid2, version_compare } from "../../helpers/inventoryHelpers.ts"; import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts"; import type { IRecipe } from "warframe-public-export-plus"; import type { IEquipmentClient } from "../../types/equipmentTypes.ts"; import { EquipmentFeatures, Status } from "../../types/equipmentTypes.ts"; interface IClaimCompletedRecipeRequest { - RecipeIds: IOidWithLegacySupport[]; + RecipeIds?: IOidWithLegacySupport[]; // U24.4 and beyond + recipeNames?: string[]; // Builds before U24.4 down to U22.20 } interface IClaimCompletedRecipeResponse { @@ -45,33 +46,46 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = }; if (!req.query.recipeName) { const claimCompletedRecipeRequest = getJSONfromString(String(req.body)); - for (const recipeId of claimCompletedRecipeRequest.RecipeIds) { - const pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId)); - if (!pendingRecipe) { - throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`); + const recipes = claimCompletedRecipeRequest.recipeNames ?? claimCompletedRecipeRequest.RecipeIds; + if (recipes) { + for (const recipeId of recipes) { + let pendingRecipe; + if (typeof recipeId === "string") { + pendingRecipe = inventory.PendingRecipes.find(r => r.ItemType == recipeId); + if (!pendingRecipe) { + throw new Error(`no pending recipe found of type ${recipeId}`); + } + } else { + pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId)); + if (!pendingRecipe) { + throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`); + } + } + + //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. + return; + } + + await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush); } - - //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. - return; - } - - await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush); + } else { + throw new Error(`recipe list from request was undefined?`); } } else { const recipeName = String(req.query.recipeName); // U8 @@ -92,7 +106,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = recipe, pendingRecipe, resp, - req.path.includes("instantCompleteRecipe.php") + req.path.includes("instantCompleteRecipe.php") || req.query.rush ); } await inventory.save(); @@ -144,13 +158,20 @@ const claimCompletedRecipe = async ( } if (rush) { + let cost = recipe.skipBuildTimePrice; 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 = - progress > 0.5 ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5))) : recipe.skipBuildTimePrice; + // U18 introduced rush cost scaling, don't use it for older versions. + if (account.BuildLabel && version_compare(account.BuildLabel, "2015.12.03.00.00") >= 0) { + // Haven't found the real build label for U18.0.0 yet, but this works + cost = + progress > 0.5 + ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5))) + : recipe.skipBuildTimePrice; + } combineInventoryChanges(resp.InventoryChanges, updateCurrency(inventory, cost, true)); }