From 50f602ba66a166ae8aaa12eab62ab7ebbc805fd0 Mon Sep 17 00:00:00 2001 From: Sainan Date: Wed, 26 Feb 2025 11:51:02 +0100 Subject: [PATCH] feat: recipes that consume weapons --- .../api/claimCompletedRecipeController.ts | 37 +++++++++--- src/controllers/api/startRecipeController.ts | 51 +++++++++++------ src/models/inventoryModels/inventoryModel.ts | 56 ++++++++++--------- src/types/inventoryTypes/inventoryTypes.ts | 18 +++--- 4 files changed, 106 insertions(+), 56 deletions(-) diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index d8208f7f..47d606bf 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -8,6 +8,9 @@ import { IOid } from "@/src/types/commonTypes"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; export interface IClaimCompletedRecipeRequest { RecipeIds: IOid[]; @@ -37,15 +40,35 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = } if (req.query.cancel) { - const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false); - addMiscItems(inventory, recipe.ingredients); + const inventoryChanges: IInventoryChanges = { + ...updateCurrency(inventory, recipe.buildPrice * -1, false) + }; + + const nonMiscItemIngredients = 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()); + nonMiscItemIngredients.add(item.ItemType); + + inventoryChanges.WeaponBin ??= { Slots: 0 }; + inventoryChanges.WeaponBin.Slots -= 1; + }); + } + } + const miscItemChanges: IMiscItem[] = []; + recipe.ingredients.forEach(ingredient => { + if (!nonMiscItemIngredients.has(ingredient.ItemType)) { + miscItemChanges.push(ingredient); + } + }); + addMiscItems(inventory, miscItemChanges); + inventoryChanges.MiscItems = miscItemChanges; await inventory.save(); - // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root. - res.json({ - ...currencyChanges, - MiscItems: recipe.ingredients - }); + 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 }); diff --git a/src/controllers/api/startRecipeController.ts b/src/controllers/api/startRecipeController.ts index 642d0cfc..d2f37230 100644 --- a/src/controllers/api/startRecipeController.ts +++ b/src/controllers/api/startRecipeController.ts @@ -7,6 +7,8 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven import { unixTimesInMs } from "@/src/constants/timeConstants"; import { Types } from "mongoose"; import { ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes"; +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { ExportWeapons } from "warframe-public-export-plus"; interface IStartRecipeRequest { RecipeName: string; @@ -26,23 +28,40 @@ export const startRecipeController: RequestHandler = async (req, res) => { throw new Error(`unknown recipe ${recipeName}`); } - const ingredientsInverse = recipe.ingredients.map(component => ({ - ItemType: component.ItemType, - ItemCount: component.ItemCount * -1 - })); - const inventory = await getInventory(accountId); updateCurrency(inventory, recipe.buildPrice, false); - addMiscItems(inventory, ingredientsInverse); - //buildtime is in seconds - const completionDate = new Date(Date.now() + recipe.buildTime * unixTimesInMs.second); + const pr = + inventory.PendingRecipes[ + inventory.PendingRecipes.push({ + ItemType: recipeName, + CompletionDate: new Date(Date.now() + recipe.buildTime * unixTimesInMs.second), + _id: new Types.ObjectId() + }) - 1 + ]; - inventory.PendingRecipes.push({ - ItemType: recipeName, - CompletionDate: completionDate, - _id: new Types.ObjectId() - }); + for (let i = 0; i != recipe.ingredients.length; ++i) { + if (startRecipeRequest.Ids[i]) { + const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory; + if (category != "LongGuns" && category != "Pistols" && category != "Melee") { + throw new Error(`unexpected equipment ingredient type: ${category}`); + } + const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i])); + if (equipmentIndex == -1) { + throw new Error(`could not find equipment item to use for recipe`); + } + pr[category] ??= []; + pr[category].push(inventory[category][equipmentIndex]); + inventory[category].splice(equipmentIndex, 1); + } else { + addMiscItems(inventory, [ + { + ItemType: recipe.ingredients[i].ItemType, + ItemCount: recipe.ingredients[i].ItemCount * -1 + } + ]); + } + } if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { const spectreLoadout: ISpectreLoadout = { @@ -98,9 +117,7 @@ export const startRecipeController: RequestHandler = async (req, res) => { } } - const newInventory = await inventory.save(); + await inventory.save(); - res.json({ - RecipeId: { $oid: newInventory.PendingRecipes[newInventory.PendingRecipes.length - 1]._id.toString() } - }); + res.json({ RecipeId: toOid(pr._id) }); }; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 5ddfbdec..1d81bbca 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -9,8 +9,8 @@ import { ISlots, IMailboxDatabase, IDuviriInfo, - IPendingRecipe as IPendingRecipeDatabase, - IPendingRecipeResponse, + IPendingRecipeDatabase, + IPendingRecipeClient, ITypeCount, IFocusXP, IFocusUpgrade, @@ -108,29 +108,6 @@ const focusUpgradeSchema = new Schema( { _id: false } ); -const pendingRecipeSchema = new Schema( - { - ItemType: String, - CompletionDate: Date - }, - { id: false } -); - -pendingRecipeSchema.virtual("ItemId").get(function () { - return { $oid: this._id.toString() }; -}); - -pendingRecipeSchema.set("toJSON", { - virtuals: true, - transform(_document, returnedObject) { - delete returnedObject._id; - delete returnedObject.__v; - (returnedObject as IPendingRecipeResponse).CompletionDate = { - $date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() } - }; - } -}); - const polaritySchema = new Schema( { Slot: Number, @@ -865,6 +842,35 @@ equipmentKeys.forEach(key => { equipmentFields[key] = { type: [EquipmentSchema] }; }); +const pendingRecipeSchema = new Schema( + { + ItemType: String, + CompletionDate: Date, + LongGuns: { type: [EquipmentSchema], default: undefined }, + Pistols: { type: [EquipmentSchema], default: undefined }, + Melee: { type: [EquipmentSchema], default: undefined } + }, + { id: false } +); + +pendingRecipeSchema.virtual("ItemId").get(function () { + return { $oid: this._id.toString() }; +}); + +pendingRecipeSchema.set("toJSON", { + virtuals: true, + transform(_document, returnedObject) { + delete returnedObject._id; + delete returnedObject.__v; + delete returnedObject.LongGuns; + delete returnedObject.Pistols; + delete returnedObject.Melees; + (returnedObject as IPendingRecipeClient).CompletionDate = { + $date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() } + }; + } +}); + const infestedFoundrySchema = new Schema( { Name: String, diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index ed6da027..6fb8d2af 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -49,7 +49,7 @@ export interface IInventoryDatabase LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population Mailbox?: IMailboxDatabase; GuildId?: Types.ObjectId; - PendingRecipes: IPendingRecipe[]; + PendingRecipes: IPendingRecipeDatabase[]; QuestKeys: IQuestKeyDatabase[]; BlessingCooldown?: Date; Ships: Types.ObjectId[]; @@ -143,10 +143,6 @@ export type TSolarMapRegion = //TODO: perhaps split response and database into their own files -export interface IPendingRecipeResponse extends Omit { - CompletionDate: IMongoDate; -} - export interface IDailyAffiliations { DailyAffiliation: number; DailyAffiliationPvp: number; @@ -217,7 +213,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu XPInfo: ITypeXPItem[]; Recipes: ITypeCount[]; WeaponSkins: IWeaponSkinClient[]; - PendingRecipes: IPendingRecipeResponse[]; + PendingRecipes: IPendingRecipeClient[]; TrainingDate: IMongoDate; PlayerLevel: number; Staff?: boolean; @@ -783,12 +779,20 @@ export interface IPendingCouponClient { Discount: number; } -export interface IPendingRecipe { +export interface IPendingRecipeDatabase { ItemType: string; CompletionDate: Date; ItemId: IOid; TargetItemId?: string; // likely related to liches TargetFingerprint?: string; // likely related to liches + LongGuns?: IEquipmentDatabase[]; + Pistols?: IEquipmentDatabase[]; + Melee?: IEquipmentDatabase[]; +} + +export interface IPendingRecipeClient + extends Omit { + CompletionDate: IMongoDate; } export interface IPendingTrade {