forked from OpenWF/SpaceNinjaServer
		
	feat: recipes that consume weapons (#1032)
Closes #720 Reviewed-on: OpenWF/SpaceNinjaServer#1032
This commit is contained in:
		
							parent
							
								
									79147786f6
								
							
						
					
					
						commit
						caec5a6cbf
					
				@ -8,6 +8,9 @@ import { IOid } from "@/src/types/commonTypes";
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
 | 
					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 {
 | 
					export interface IClaimCompletedRecipeRequest {
 | 
				
			||||||
    RecipeIds: IOid[];
 | 
					    RecipeIds: IOid[];
 | 
				
			||||||
@ -37,15 +40,35 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (req.query.cancel) {
 | 
					    if (req.query.cancel) {
 | 
				
			||||||
        const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false);
 | 
					        const inventoryChanges: IInventoryChanges = {
 | 
				
			||||||
        addMiscItems(inventory, recipe.ingredients);
 | 
					            ...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<IEquipmentClient>());
 | 
				
			||||||
 | 
					                    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();
 | 
					        await inventory.save();
 | 
				
			||||||
        // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
 | 
					        res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
 | 
				
			||||||
        res.json({
 | 
					 | 
				
			||||||
            ...currencyChanges,
 | 
					 | 
				
			||||||
            MiscItems: recipe.ingredients
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
					        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
 | 
				
			|||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
					import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { ExportWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IStartRecipeRequest {
 | 
					interface IStartRecipeRequest {
 | 
				
			||||||
    RecipeName: string;
 | 
					    RecipeName: string;
 | 
				
			||||||
@ -26,23 +28,40 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        throw new Error(`unknown recipe ${recipeName}`);
 | 
					        throw new Error(`unknown recipe ${recipeName}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ingredientsInverse = recipe.ingredients.map(component => ({
 | 
					 | 
				
			||||||
        ItemType: component.ItemType,
 | 
					 | 
				
			||||||
        ItemCount: component.ItemCount * -1
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    updateCurrency(inventory, recipe.buildPrice, false);
 | 
					    updateCurrency(inventory, recipe.buildPrice, false);
 | 
				
			||||||
    addMiscItems(inventory, ingredientsInverse);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //buildtime is in seconds
 | 
					    const pr =
 | 
				
			||||||
    const completionDate = new Date(Date.now() + recipe.buildTime * unixTimesInMs.second);
 | 
					        inventory.PendingRecipes[
 | 
				
			||||||
 | 
					            inventory.PendingRecipes.push({
 | 
				
			||||||
 | 
					                ItemType: recipeName,
 | 
				
			||||||
 | 
					                CompletionDate: new Date(Date.now() + recipe.buildTime * unixTimesInMs.second),
 | 
				
			||||||
 | 
					                _id: new Types.ObjectId()
 | 
				
			||||||
 | 
					            }) - 1
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inventory.PendingRecipes.push({
 | 
					    for (let i = 0; i != recipe.ingredients.length; ++i) {
 | 
				
			||||||
        ItemType: recipeName,
 | 
					        if (startRecipeRequest.Ids[i]) {
 | 
				
			||||||
        CompletionDate: completionDate,
 | 
					            const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
 | 
				
			||||||
        _id: new Types.ObjectId()
 | 
					            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") {
 | 
					    if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
				
			||||||
        const spectreLoadout: ISpectreLoadout = {
 | 
					        const spectreLoadout: ISpectreLoadout = {
 | 
				
			||||||
@ -98,9 +117,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const newInventory = await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({ RecipeId: toOid(pr._id) });
 | 
				
			||||||
        RecipeId: { $oid: newInventory.PendingRecipes[newInventory.PendingRecipes.length - 1]._id.toString() }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -9,8 +9,8 @@ import {
 | 
				
			|||||||
    ISlots,
 | 
					    ISlots,
 | 
				
			||||||
    IMailboxDatabase,
 | 
					    IMailboxDatabase,
 | 
				
			||||||
    IDuviriInfo,
 | 
					    IDuviriInfo,
 | 
				
			||||||
    IPendingRecipe as IPendingRecipeDatabase,
 | 
					    IPendingRecipeDatabase,
 | 
				
			||||||
    IPendingRecipeResponse,
 | 
					    IPendingRecipeClient,
 | 
				
			||||||
    ITypeCount,
 | 
					    ITypeCount,
 | 
				
			||||||
    IFocusXP,
 | 
					    IFocusXP,
 | 
				
			||||||
    IFocusUpgrade,
 | 
					    IFocusUpgrade,
 | 
				
			||||||
@ -108,29 +108,6 @@ const focusUpgradeSchema = new Schema<IFocusUpgrade>(
 | 
				
			|||||||
    { _id: false }
 | 
					    { _id: false }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        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<IPolarity>(
 | 
					const polaritySchema = new Schema<IPolarity>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Slot: Number,
 | 
					        Slot: Number,
 | 
				
			||||||
@ -865,6 +842,35 @@ equipmentKeys.forEach(key => {
 | 
				
			|||||||
    equipmentFields[key] = { type: [EquipmentSchema] };
 | 
					    equipmentFields[key] = { type: [EquipmentSchema] };
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        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<IInfestedFoundryDatabase>(
 | 
					const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Name: String,
 | 
					        Name: String,
 | 
				
			||||||
 | 
				
			|||||||
@ -49,7 +49,7 @@ export interface IInventoryDatabase
 | 
				
			|||||||
    LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
 | 
					    LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
 | 
				
			||||||
    Mailbox?: IMailboxDatabase;
 | 
					    Mailbox?: IMailboxDatabase;
 | 
				
			||||||
    GuildId?: Types.ObjectId;
 | 
					    GuildId?: Types.ObjectId;
 | 
				
			||||||
    PendingRecipes: IPendingRecipe[];
 | 
					    PendingRecipes: IPendingRecipeDatabase[];
 | 
				
			||||||
    QuestKeys: IQuestKeyDatabase[];
 | 
					    QuestKeys: IQuestKeyDatabase[];
 | 
				
			||||||
    BlessingCooldown?: Date;
 | 
					    BlessingCooldown?: Date;
 | 
				
			||||||
    Ships: Types.ObjectId[];
 | 
					    Ships: Types.ObjectId[];
 | 
				
			||||||
@ -143,10 +143,6 @@ export type TSolarMapRegion =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
//TODO: perhaps split response and database into their own files
 | 
					//TODO: perhaps split response and database into their own files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IPendingRecipeResponse extends Omit<IPendingRecipe, "CompletionDate"> {
 | 
					 | 
				
			||||||
    CompletionDate: IMongoDate;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface IDailyAffiliations {
 | 
					export interface IDailyAffiliations {
 | 
				
			||||||
    DailyAffiliation: number;
 | 
					    DailyAffiliation: number;
 | 
				
			||||||
    DailyAffiliationPvp: number;
 | 
					    DailyAffiliationPvp: number;
 | 
				
			||||||
@ -217,7 +213,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
				
			|||||||
    XPInfo: ITypeXPItem[];
 | 
					    XPInfo: ITypeXPItem[];
 | 
				
			||||||
    Recipes: ITypeCount[];
 | 
					    Recipes: ITypeCount[];
 | 
				
			||||||
    WeaponSkins: IWeaponSkinClient[];
 | 
					    WeaponSkins: IWeaponSkinClient[];
 | 
				
			||||||
    PendingRecipes: IPendingRecipeResponse[];
 | 
					    PendingRecipes: IPendingRecipeClient[];
 | 
				
			||||||
    TrainingDate: IMongoDate;
 | 
					    TrainingDate: IMongoDate;
 | 
				
			||||||
    PlayerLevel: number;
 | 
					    PlayerLevel: number;
 | 
				
			||||||
    Staff?: boolean;
 | 
					    Staff?: boolean;
 | 
				
			||||||
@ -783,12 +779,20 @@ export interface IPendingCouponClient {
 | 
				
			|||||||
    Discount: number;
 | 
					    Discount: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IPendingRecipe {
 | 
					export interface IPendingRecipeDatabase {
 | 
				
			||||||
    ItemType: string;
 | 
					    ItemType: string;
 | 
				
			||||||
    CompletionDate: Date;
 | 
					    CompletionDate: Date;
 | 
				
			||||||
    ItemId: IOid;
 | 
					    ItemId: IOid;
 | 
				
			||||||
    TargetItemId?: string; // likely related to liches
 | 
					    TargetItemId?: string; // likely related to liches
 | 
				
			||||||
    TargetFingerprint?: string; // likely related to liches
 | 
					    TargetFingerprint?: string; // likely related to liches
 | 
				
			||||||
 | 
					    LongGuns?: IEquipmentDatabase[];
 | 
				
			||||||
 | 
					    Pistols?: IEquipmentDatabase[];
 | 
				
			||||||
 | 
					    Melee?: IEquipmentDatabase[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IPendingRecipeClient
 | 
				
			||||||
 | 
					    extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee"> {
 | 
				
			||||||
 | 
					    CompletionDate: IMongoDate;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IPendingTrade {
 | 
					export interface IPendingTrade {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user