From 48b7138c1c66ac005f99c9d2811574a24b999e92 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:50:51 -0800 Subject: [PATCH] feat: initial foundry for U8 (#3014) endpoints should work, but we don't have data for required recipe items for U8, so in most cases, an unknown error will occur in the game, and a more detailed error will occur in the server console. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3014 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- .../api/checkPendingRecipesController.ts | 24 +++++++ .../api/claimCompletedRecipeController.ts | 63 +++++++++++++------ src/controllers/api/startRecipeController.ts | 14 ++++- src/routes/api.ts | 3 + 4 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 src/controllers/api/checkPendingRecipesController.ts diff --git a/src/controllers/api/checkPendingRecipesController.ts b/src/controllers/api/checkPendingRecipesController.ts new file mode 100644 index 00000000..4c5724d5 --- /dev/null +++ b/src/controllers/api/checkPendingRecipesController.ts @@ -0,0 +1,24 @@ +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import type { RequestHandler } from "express"; +import { getInventory } from "../../services/inventoryService.ts"; + +export const checkPendingRecipesController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "PendingRecipes"); + const now = Date.now(); + const resp: ICheckPendingRecipesResponse = { + PendingRecipes: inventory.PendingRecipes.map(recipe => ({ + ItemType: recipe.ItemType, + SecondsRemaining: Math.max(0, Math.floor((recipe.CompletionDate.getTime() - now) / 1000)) + })) + }; + + res.send(resp); +}; + +interface ICheckPendingRecipesResponse { + PendingRecipes: { + ItemType: string; + SecondsRemaining: number; + }[]; +} diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index 39782101..40966270 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -38,22 +38,47 @@ interface IClaimCompletedRecipeResponse { } export const claimCompletedRecipeController: RequestHandler = async (req, res) => { - const claimCompletedRecipeRequest = getJSONfromString(String(req.body)); const account = await getAccountForRequest(req); const inventory = await getInventory(account._id.toString()); const resp: IClaimCompletedRecipeResponse = { InventoryChanges: {} }; - 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)}`); - } + 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)}`); + } - //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`); - // } + //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 { + const recipeName = String(req.query.recipeName); // U8 + const pendingRecipe = inventory.PendingRecipes.find(r => r.ItemType == recipeName); + if (!pendingRecipe) { + throw new Error(`no pending recipe found with ItemType ${recipeName}`); + } inventory.PendingRecipes.pull(pendingRecipe._id); @@ -61,16 +86,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = 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); + await claimCompletedRecipe( + account, + inventory, + recipe, + pendingRecipe, + resp, + req.path.includes("instantCompleteRecipe.php") + ); } await inventory.save(); res.json(resp); diff --git a/src/controllers/api/startRecipeController.ts b/src/controllers/api/startRecipeController.ts index a2389a01..b9e6bad2 100644 --- a/src/controllers/api/startRecipeController.ts +++ b/src/controllers/api/startRecipeController.ts @@ -24,7 +24,8 @@ export const startRecipeController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); - const recipeName = startRecipeRequest.RecipeName; + let recipeName = startRecipeRequest.RecipeName; + if (req.query.recipeName) recipeName = String(req.query.recipeName); // U8 const recipe = getRecipe(recipeName); if (!recipe) { @@ -68,7 +69,16 @@ export const startRecipeController: RequestHandler = async (req, res) => { freeUpSlot(inventory, InventorySlot.WEAPONS); } } else { - await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1); + const itemType = recipe.ingredients[i].ItemType; + const itemCount = recipe.ingredients[i].ItemCount; + const inventoryItem = inventory.MiscItems.find(i => i.ItemType === itemType); + if (inventoryItem && inventoryItem.ItemCount >= itemCount) { + await addItem(inventory, itemType, itemCount * -1); + } else { + throw new Error( + `insufficient ${itemType} (in inventory ${inventoryItem?.ItemCount} - needed ${itemCount}) for recipe ${recipeName}` + ); + } } } diff --git a/src/routes/api.ts b/src/routes/api.ts index ee1f18fa..ec6f3aa7 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -20,6 +20,7 @@ import { cancelGuildAdvertisementController } from "../controllers/api/cancelGui import { changeDojoRootController } from "../controllers/api/changeDojoRootController.ts"; import { changeGuildRankController } from "../controllers/api/changeGuildRankController.ts"; import { checkDailyMissionBonusController } from "../controllers/api/checkDailyMissionBonusController.ts"; +import { checkPendingRecipesController } from "../controllers/api/checkPendingRecipesController.ts"; import { claimCompletedRecipeController } from "../controllers/api/claimCompletedRecipeController.ts"; import { claimJunctionChallengeRewardController } from "../controllers/api/claimJunctionChallengeRewardController.ts"; import { claimLibraryDailyTaskRewardController } from "../controllers/api/claimLibraryDailyTaskRewardController.ts"; @@ -184,6 +185,7 @@ apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementControlle apiRouter.get("/changeDojoRoot.php", changeDojoRootController); apiRouter.get("/changeGuildRank.php", changeGuildRankController); apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController); +apiRouter.get("/checkPendingRecipes.php", checkPendingRecipesController); // U8 apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController); apiRouter.get("/completeCalendarEvent.php", completeCalendarEventController); apiRouter.get("/confirmAllianceInvitation.php", confirmAllianceInvitationController); @@ -305,6 +307,7 @@ apiRouter.post("/guildTech.php", guildTechController); apiRouter.post("/hostSession.php", hostSessionController); apiRouter.post("/hubBlessing.php", hubBlessingController); apiRouter.post("/infestedFoundry.php", infestedFoundryController); +apiRouter.post("/instantCompleteRecipe.php", claimCompletedRecipeController); // U8 apiRouter.post("/inventorySlots.php", inventorySlotsController); apiRouter.post("/joinSession.php", joinSessionController); apiRouter.post("/login.php", loginController);