返回提交历史
Modified
src/controllers/api/claimCompletedRecipeController.ts
+30
-7
Modified
src/controllers/api/startRecipeController.ts
+34
-17
Modified
src/models/inventoryModels/inventoryModel.ts
+31
-25
Modified
src/types/inventoryTypes/inventoryTypes.ts
+11
-7
XFEstudio/XFESpaceNinjaServer
feat: recipes that consume weapons (#1032)
Closes #720 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1032
caec5a6c
代码差异
4 个文件
+106
-56
@@ -8,6 +8,9 @@ import { IOid } from "@/src/types/commonTypes";
8
8
import { getJSONfromString } from "@/src/helpers/stringHelpers";
9
9
import { getAccountIdForRequest } from "@/src/services/loginService";
10
10
import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
11
import { IInventoryChanges } from "@/src/types/purchaseTypes";
12
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
13
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
11
14
12
15
export interface IClaimCompletedRecipeRequest {
13
16
RecipeIds: IOid[];
@@ -37,15 +40,35 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
37
40
}
38
41
39
42
if (req.query.cancel) {
40
const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false);
41
addMiscItems(inventory, recipe.ingredients);
43
const inventoryChanges: IInventoryChanges = {
44
...updateCurrency(inventory, recipe.buildPrice * -1, false)
45
};
42
46
43
await inventory.save();
44
// Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
45
res.json({
46
...currencyChanges,
47
MiscItems: recipe.ingredients
47
const nonMiscItemIngredients = new Set();
48
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
49
if (pendingRecipe[category]) {
50
pendingRecipe[category].forEach(item => {
51
const index = inventory[category].push(item) - 1;
52
inventoryChanges[category] ??= [];
53
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
54
nonMiscItemIngredients.add(item.ItemType);
55
56
inventoryChanges.WeaponBin ??= { Slots: 0 };
57
inventoryChanges.WeaponBin.Slots -= 1;
58
});
59
}
60
}
61
const miscItemChanges: IMiscItem[] = [];
62
recipe.ingredients.forEach(ingredient => {
63
if (!nonMiscItemIngredients.has(ingredient.ItemType)) {
64
miscItemChanges.push(ingredient);
65
}
48
66
});
67
addMiscItems(inventory, miscItemChanges);
68
inventoryChanges.MiscItems = miscItemChanges;
69
70
await inventory.save();
71
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
49
72
} else {
50
73
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
51
74
@@ -7,6 +7,8 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
7
7
import { unixTimesInMs } from "@/src/constants/timeConstants";
8
8
import { Types } from "mongoose";
9
9
import { ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
10
import { toOid } from "@/src/helpers/inventoryHelpers";
11
import { ExportWeapons } from "warframe-public-export-plus";
10
12
11
13
interface IStartRecipeRequest {
12
14
RecipeName: string;
@@ -26,23 +28,40 @@ export const startRecipeController: RequestHandler = async (req, res) => {
26
28
throw new Error(`unknown recipe ${recipeName}`);
27
29
}
28
30
29
const ingredientsInverse = recipe.ingredients.map(component => ({
30
ItemType: component.ItemType,
31
ItemCount: component.ItemCount * -1
32
}));
33
34
31
const inventory = await getInventory(accountId);
35
32
updateCurrency(inventory, recipe.buildPrice, false);
36
addMiscItems(inventory, ingredientsInverse);
37
33
38
//buildtime is in seconds
39
const completionDate = new Date(Date.now() + recipe.buildTime * unixTimesInMs.second);
34
const pr =
35
inventory.PendingRecipes[
36
inventory.PendingRecipes.push({
37
ItemType: recipeName,
38
CompletionDate: new Date(Date.now() + recipe.buildTime * unixTimesInMs.second),
39
_id: new Types.ObjectId()
40
}) - 1
41
];
40
42
41
inventory.PendingRecipes.push({
42
ItemType: recipeName,
43
CompletionDate: completionDate,
44
_id: new Types.ObjectId()
45
});
43
for (let i = 0; i != recipe.ingredients.length; ++i) {
44
if (startRecipeRequest.Ids[i]) {
45
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
46
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
47
throw new Error(`unexpected equipment ingredient type: ${category}`);
48
}
49
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
50
if (equipmentIndex == -1) {
51
throw new Error(`could not find equipment item to use for recipe`);
52
}
53
pr[category] ??= [];
54
pr[category].push(inventory[category][equipmentIndex]);
55
inventory[category].splice(equipmentIndex, 1);
56
} else {
57
addMiscItems(inventory, [
58
{
59
ItemType: recipe.ingredients[i].ItemType,
60
ItemCount: recipe.ingredients[i].ItemCount * -1
61
}
62
]);
63
}
64
}
46
65
47
66
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
48
67
const spectreLoadout: ISpectreLoadout = {
@@ -98,9 +117,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
98
117
}
99
118
}
100
119
101
const newInventory = await inventory.save();
120
await inventory.save();
102
121
103
res.json({
104
RecipeId: { $oid: newInventory.PendingRecipes[newInventory.PendingRecipes.length - 1]._id.toString() }
105
});
122
res.json({ RecipeId: toOid(pr._id) });
106
123
};
@@ -9,8 +9,8 @@ import {
9
9
ISlots,
10
10
IMailboxDatabase,
11
11
IDuviriInfo,
12
IPendingRecipe as IPendingRecipeDatabase,
13
IPendingRecipeResponse,
12
IPendingRecipeDatabase,
13
IPendingRecipeClient,
14
14
ITypeCount,
15
15
IFocusXP,
16
16
IFocusUpgrade,
@@ -108,29 +108,6 @@ const focusUpgradeSchema = new Schema<IFocusUpgrade>(
108
108
{ _id: false }
109
109
);
110
110
111
const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
112
{
113
ItemType: String,
114
CompletionDate: Date
115
},
116
{ id: false }
117
);
118
119
pendingRecipeSchema.virtual("ItemId").get(function () {
120
return { $oid: this._id.toString() };
121
});
122
123
pendingRecipeSchema.set("toJSON", {
124
virtuals: true,
125
transform(_document, returnedObject) {
126
delete returnedObject._id;
127
delete returnedObject.__v;
128
(returnedObject as IPendingRecipeResponse).CompletionDate = {
129
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
130
};
131
}
132
});
133
134
111
const polaritySchema = new Schema<IPolarity>(
135
112
{
136
113
Slot: Number,
@@ -865,6 +842,35 @@ equipmentKeys.forEach(key => {
865
842
equipmentFields[key] = { type: [EquipmentSchema] };
866
843
});
867
844
845
const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
846
{
847
ItemType: String,
848
CompletionDate: Date,
849
LongGuns: { type: [EquipmentSchema], default: undefined },
850
Pistols: { type: [EquipmentSchema], default: undefined },
851
Melee: { type: [EquipmentSchema], default: undefined }
852
},
853
{ id: false }
854
);
855
856
pendingRecipeSchema.virtual("ItemId").get(function () {
857
return { $oid: this._id.toString() };
858
});
859
860
pendingRecipeSchema.set("toJSON", {
861
virtuals: true,
862
transform(_document, returnedObject) {
863
delete returnedObject._id;
864
delete returnedObject.__v;
865
delete returnedObject.LongGuns;
866
delete returnedObject.Pistols;
867
delete returnedObject.Melees;
868
(returnedObject as IPendingRecipeClient).CompletionDate = {
869
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
870
};
871
}
872
});
873
868
874
const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
869
875
{
870
876
Name: String,
@@ -49,7 +49,7 @@ export interface IInventoryDatabase
49
49
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
50
50
Mailbox?: IMailboxDatabase;
51
51
GuildId?: Types.ObjectId;
52
PendingRecipes: IPendingRecipe[];
52
PendingRecipes: IPendingRecipeDatabase[];
53
53
QuestKeys: IQuestKeyDatabase[];
54
54
BlessingCooldown?: Date;
55
55
Ships: Types.ObjectId[];
@@ -143,10 +143,6 @@ export type TSolarMapRegion =
143
143
144
144
//TODO: perhaps split response and database into their own files
145
145
146
export interface IPendingRecipeResponse extends Omit<IPendingRecipe, "CompletionDate"> {
147
CompletionDate: IMongoDate;
148
}
149
150
146
export interface IDailyAffiliations {
151
147
DailyAffiliation: number;
152
148
DailyAffiliationPvp: number;
@@ -217,7 +213,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
217
213
XPInfo: ITypeXPItem[];
218
214
Recipes: ITypeCount[];
219
215
WeaponSkins: IWeaponSkinClient[];
220
PendingRecipes: IPendingRecipeResponse[];
216
PendingRecipes: IPendingRecipeClient[];
221
217
TrainingDate: IMongoDate;
222
218
PlayerLevel: number;
223
219
Staff?: boolean;
@@ -783,12 +779,20 @@ export interface IPendingCouponClient {
783
779
Discount: number;
784
780
}
785
781
786
export interface IPendingRecipe {
782
export interface IPendingRecipeDatabase {
787
783
ItemType: string;
788
784
CompletionDate: Date;
789
785
ItemId: IOid;
790
786
TargetItemId?: string; // likely related to liches
791
787
TargetFingerprint?: string; // likely related to liches
788
LongGuns?: IEquipmentDatabase[];
789
Pistols?: IEquipmentDatabase[];
790
Melee?: IEquipmentDatabase[];
791
}
792
793
export interface IPendingRecipeClient
794
extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee"> {
795
CompletionDate: IMongoDate;
792
796
}
793
797
794
798
export interface IPendingTrade {