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