返回提交历史
Modified
src/controllers/api/claimCompletedRecipeController.ts
+22
-24
Modified
src/controllers/api/inventorySlotsController.ts
+2
-2
Modified
src/services/inventoryService.ts
+129
-2
Modified
src/services/purchaseService.ts
+13
-173
Modified
src/types/inventoryTypes/inventoryTypes.ts
+8
-0
Modified
src/types/purchaseTypes.ts
+0
-8
XFEstudio/XFESpaceNinjaServer
feat: rushing recipes, refactor: addItem (#248)
b08fff19
代码差异
6 个文件
+174
-209
@@ -3,11 +3,11 @@
3
3
4
4
import { RequestHandler } from "express";
5
5
import { logger } from "@/src/utils/logger";
6
import { getItemByBlueprint, getItemCategoryByUniqueName } from "@/src/services/itemDataService";
6
import { getItemByBlueprint } from "@/src/services/itemDataService";
7
7
import { IOid } from "@/src/types/commonTypes";
8
8
import { getJSONfromString } from "@/src/helpers/stringHelpers";
9
9
import { getAccountIdForRequest } from "@/src/services/loginService";
10
import { getInventory } from "@/src/services/inventoryService";
10
import { getInventory, updateCurrency, addItem } from "@/src/services/inventoryService";
11
11
12
12
export interface IClaimCompletedRecipeRequest {
13
13
RecipeIds: IOid[];
@@ -19,12 +19,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
19
19
const accountId = await getAccountIdForRequest(req);
20
20
if (!accountId) throw new Error("no account id");
21
21
22
console.log(claimCompletedRecipeRequest);
23
22
const inventory = await getInventory(accountId);
24
23
const pendingRecipe = inventory.PendingRecipes.find(
25
24
recipe => recipe._id?.toString() === claimCompletedRecipeRequest.RecipeIds[0].$oid
26
25
);
27
console.log(pendingRecipe);
28
26
if (!pendingRecipe) {
29
27
logger.error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
30
28
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
@@ -36,29 +34,29 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
36
34
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
37
35
// }
38
36
39
//get completed Items
40
const completedItemName = getItemByBlueprint(pendingRecipe.ItemType)?.uniqueName;
37
inventory.PendingRecipes.pull(pendingRecipe._id);
38
await inventory.save();
41
39
42
if (!completedItemName) {
40
const buildable = getItemByBlueprint(pendingRecipe.ItemType);
41
if (!buildable) {
43
42
logger.error(`no completed item found for recipe ${pendingRecipe._id}`);
44
43
throw new Error(`no completed item found for recipe ${pendingRecipe._id}`);
45
44
}
46
const itemCategory = getItemCategoryByUniqueName(completedItemName) as keyof typeof inventory;
47
console.log(itemCategory);
48
//TODO: remove all Schema.Mixed for inventory[itemCategory] not to be any
49
//add item
50
//inventory[itemCategory].
51
52
//add additional item components like mods or weapons for a sentinel.
53
//const additionalItemComponents = itemComponents[uniqueName]
54
//add these items to inventory
55
//return changes as InventoryChanges
56
57
//remove pending recipe
58
inventory.PendingRecipes.pull(pendingRecipe._id);
59
// await inventory.save();
60
61
logger.debug("Claiming Completed Recipe", { completedItemName });
62
45
63
res.json({ InventoryChanges: {} });
46
if (req.query.cancel) {
47
// TODO: Refund items
48
res.json({});
49
} else {
50
logger.debug("Claiming Recipe", { buildable, pendingRecipe });
51
let currencyChanges = {};
52
if (req.query.rush && buildable.skipBuildTimePrice) {
53
currencyChanges = await updateCurrency(buildable.skipBuildTimePrice, true, accountId);
54
}
55
res.json({
56
InventoryChanges: {
57
...currencyChanges,
58
...(await addItem(accountId, buildable.uniqueName, buildable.buildQuantity)).InventoryChanges
59
}
60
});
61
}
64
62
};
@@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
2
2
import { updateCurrency } from "@/src/services/inventoryService";
3
3
import { RequestHandler } from "express";
4
4
import { updateSlots } from "@/src/services/inventoryService";
5
import { SlotNameToInventoryName } from "@/src/types/purchaseTypes";
5
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
6
6
7
7
/*
8
8
loadout slots are additionally purchased slots only
@@ -28,7 +28,7 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
28
28
//TODO: check which slot was purchased because pvpBonus is also possible
29
29
30
30
const currencyChanges = await updateCurrency(20, true, accountId);
31
await updateSlots(accountId, SlotNameToInventoryName.LOADOUT, 1, 1);
31
await updateSlots(accountId, InventorySlot.PVE_LOADOUTS, 1, 1);
32
32
33
33
//console.log({ InventoryChanges: currencyChanges }, " added loadout changes:");
34
34
@@ -14,7 +14,8 @@ import {
14
14
IMission,
15
15
IRawUpgrade,
16
16
ISeasonChallengeHistory,
17
ITypeCount
17
ITypeCount,
18
InventorySlot
18
19
} from "@/src/types/inventoryTypes/inventoryTypes";
19
20
import { IGenericUpdate } from "../types/genericUpdate";
20
21
import {
@@ -24,7 +25,7 @@ import {
24
25
IUpdateChallengeProgressRequest
25
26
} from "../types/requestTypes";
26
27
import { logger } from "@/src/utils/logger";
27
import { WeaponTypeInternal, getExalted } from "@/src/services/itemDataService";
28
import { WeaponTypeInternal, getWeaponType, getExalted } from "@/src/services/itemDataService";
28
29
import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes";
29
30
30
31
export const createInventory = async (
@@ -65,6 +66,132 @@ export const getInventory = async (accountOwnerId: string) => {
65
66
return inventory;
66
67
};
67
68
69
export const addItem = async (
70
accountId: string,
71
typeName: string,
72
quantity: number = 1
73
): Promise<{ InventoryChanges: object }> => {
74
switch (typeName.substr(1).split("/")[1]) {
75
case "Powersuits":
76
if (typeName.includes("EntratiMech")) {
77
const mechSuit = await addMechSuit(typeName, accountId);
78
await updateSlots(accountId, InventorySlot.MECHSUITS, 0, 1);
79
logger.debug("mech suit", mechSuit);
80
return {
81
InventoryChanges: {
82
MechBin: {
83
count: 1,
84
platinum: 0,
85
Slots: -1
86
},
87
MechSuits: [mechSuit]
88
}
89
};
90
}
91
const suit = await addPowerSuit(typeName, accountId);
92
await updateSlots(accountId, InventorySlot.SUITS, 0, 1);
93
return {
94
InventoryChanges: {
95
SuitBin: {
96
count: 1,
97
platinum: 0,
98
Slots: -1
99
},
100
Suits: [suit]
101
}
102
};
103
case "Weapons":
104
const weaponType = getWeaponType(typeName);
105
const weapon = await addWeapon(weaponType, typeName, accountId);
106
await updateSlots(accountId, InventorySlot.WEAPONS, 0, 1);
107
return {
108
InventoryChanges: {
109
WeaponBin: { count: 1, platinum: 0, Slots: -1 },
110
[weaponType]: [weapon]
111
}
112
};
113
case "Interface":
114
return {
115
InventoryChanges: {
116
FlavourItems: [await addCustomization(typeName, accountId)]
117
}
118
};
119
case "Types":
120
switch (typeName.substr(1).split("/")[2]) {
121
case "AvatarImages":
122
case "SuitCustomizations":
123
return {
124
InventoryChanges: {
125
FlavourItems: [await addCustomization(typeName, accountId)]
126
}
127
};
128
case "Sentinels":
129
// TOOD: Sentinels should also grant their DefaultUpgrades & SentinelWeapon.
130
const sentinel = await addSentinel(typeName, accountId);
131
await updateSlots(accountId, InventorySlot.SENTINELS, 0, 1);
132
return {
133
InventoryChanges: {
134
SentinelBin: { count: 1, platinum: 0, Slots: -1 },
135
Sentinels: [sentinel]
136
}
137
};
138
case "Items": {
139
const inventory = await getInventory(accountId);
140
const miscItemChanges = [
141
{
142
ItemType: typeName,
143
ItemCount: quantity
144
} satisfies IMiscItem
145
];
146
addMiscItems(inventory, miscItemChanges);
147
await inventory.save();
148
return {
149
InventoryChanges: {
150
MiscItems: miscItemChanges
151
}
152
};
153
}
154
case "Recipes":
155
case "Consumables": {
156
// Blueprints for Ciphers, Antitoxins
157
const inventory = await getInventory(accountId);
158
const recipeChanges = [
159
{
160
ItemType: typeName,
161
ItemCount: quantity
162
} satisfies ITypeCount
163
];
164
addRecipes(inventory, recipeChanges);
165
await inventory.save();
166
return {
167
InventoryChanges: {
168
Recipes: recipeChanges
169
}
170
};
171
}
172
case "Restoratives": // Codex Scanner, Remote Observer, Starburst
173
const inventory = await getInventory(accountId);
174
const consumablesChanges = [
175
{
176
ItemType: typeName,
177
ItemCount: quantity
178
} satisfies IConsumable
179
];
180
addConsumables(inventory, consumablesChanges);
181
await inventory.save();
182
return {
183
InventoryChanges: {
184
Consumables: consumablesChanges
185
}
186
};
187
}
188
break;
189
}
190
const errorMessage = `unable to add item: ${typeName}`;
191
logger.error(errorMessage);
192
throw new Error(errorMessage);
193
};
194
68
195
//TODO: maybe genericMethod for all the add methods, they share a lot of logic
69
196
export const addSentinel = async (sentinelName: string, accountId: string) => {
70
197
const inventory = await getInventory(accountId);
@@ -1,22 +1,7 @@
1
1
import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers";
2
import { getWeaponType } from "@/src/services/itemDataService";
3
2
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
4
import {
5
addBooster,
6
addConsumables,
7
addCustomization,
8
addMechSuit,
9
addMiscItems,
10
addPowerSuit,
11
addRecipes,
12
addSentinel,
13
addWeapon,
14
getInventory,
15
updateCurrency,
16
updateSlots
17
} from "@/src/services/inventoryService";
18
import { IConsumable, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
19
import { IPurchaseRequest, IPurchaseResponse, SlotNameToInventoryName, SlotPurchase } from "@/src/types/purchaseTypes";
3
import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService";
4
import { IPurchaseRequest, SlotPurchase } from "@/src/types/purchaseTypes";
20
5
import { logger } from "@/src/utils/logger";
21
6
22
7
export const getStoreItemCategory = (storeItem: string) => {
@@ -40,34 +25,24 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI
40
25
const internalName = purchaseRequest.PurchaseParams.StoreItem.replace("/StoreItems", "");
41
26
logger.debug(`store category ${storeCategory}`);
42
27
43
let inventoryChanges;
28
let purchaseResponse;
44
29
switch (storeCategory) {
45
case "Powersuits":
46
inventoryChanges = await handlePowersuitPurchase(internalName, accountId);
47
break;
48
case "Weapons":
49
inventoryChanges = await handleWeaponsPurchase(internalName, accountId);
30
default:
31
purchaseResponse = await addItem(accountId, internalName);
50
32
break;
51
33
case "Types":
52
inventoryChanges = await handleTypesPurchase(
34
purchaseResponse = await handleTypesPurchase(
53
35
internalName,
54
36
accountId,
55
37
purchaseRequest.PurchaseParams.Quantity
56
38
);
57
39
break;
58
40
case "Boosters":
59
inventoryChanges = await handleBoostersPurchase(internalName, accountId);
41
purchaseResponse = await handleBoostersPurchase(internalName, accountId);
60
42
break;
61
case "Interface":
62
inventoryChanges = await handleCustomizationPurchase(internalName, accountId);
63
break;
64
default:
65
const errorMessage = `unknown store category: ${storeCategory} not implemented or new`;
66
logger.error(errorMessage);
67
throw new Error(errorMessage);
68
43
}
69
44
70
if (!inventoryChanges) throw new Error("purchase response was undefined");
45
if (!purchaseResponse) throw new Error("purchase response was undefined");
71
46
72
47
const currencyChanges = await updateCurrency(
73
48
purchaseRequest.PurchaseParams.ExpectedPrice,
@@ -75,12 +50,12 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI
75
50
accountId
76
51
);
77
52
78
inventoryChanges.InventoryChanges = {
53
purchaseResponse.InventoryChanges = {
79
54
...currencyChanges,
80
...inventoryChanges.InventoryChanges
55
...purchaseResponse.InventoryChanges
81
56
};
82
57
83
return inventoryChanges;
58
return purchaseResponse;
84
59
};
85
60
86
61
export const slotPurchaseNameToSlotName: SlotPurchase = {
@@ -126,102 +101,18 @@ const handleSlotPurchase = async (slotPurchaseNameFull: string, accountId: strin
126
101
};
127
102
};
128
103
129
const handleWeaponsPurchase = async (weaponName: string, accountId: string) => {
130
const weaponType = getWeaponType(weaponName);
131
const addedWeapon = await addWeapon(weaponType, weaponName, accountId);
132
133
await updateSlots(accountId, SlotNameToInventoryName.WEAPON, 0, 1);
134
135
return {
136
InventoryChanges: {
137
WeaponBin: { count: 1, platinum: 0, Slots: -1 },
138
[weaponType]: [addedWeapon]
139
}
140
} as IPurchaseResponse;
141
};
142
143
const handlePowersuitPurchase = async (powersuitName: string, accountId: string) => {
144
if (powersuitName.includes("EntratiMech")) {
145
const mechSuit = await addMechSuit(powersuitName, accountId);
146
147
await updateSlots(accountId, SlotNameToInventoryName.MECHSUIT, 0, 1);
148
logger.debug("mech suit", mechSuit);
149
150
return {
151
InventoryChanges: {
152
MechBin: {
153
count: 1,
154
platinum: 0,
155
Slots: -1
156
},
157
MechSuits: [mechSuit]
158
}
159
} as IPurchaseResponse;
160
}
161
162
const suit = await addPowerSuit(powersuitName, accountId);
163
await updateSlots(accountId, SlotNameToInventoryName.SUIT, 0, 1);
164
165
return {
166
InventoryChanges: {
167
SuitBin: {
168
count: 1,
169
platinum: 0,
170
Slots: -1
171
},
172
Suits: [suit]
173
}
174
};
175
};
176
177
104
//TODO: change to getInventory, apply changes then save at the end
178
105
const handleTypesPurchase = async (typesName: string, accountId: string, quantity: number) => {
179
106
const typeCategory = getStoreItemTypesCategory(typesName);
180
107
logger.debug(`type category ${typeCategory}`);
181
108
switch (typeCategory) {
182
case "AvatarImages":
183
case "SuitCustomizations":
184
return await handleCustomizationPurchase(typesName, accountId);
185
case "Sentinels":
186
return await handleSentinelPurchase(typesName, accountId);
109
default:
110
return await addItem(accountId, typesName, quantity);
187
111
case "SlotItems":
188
112
return await handleSlotPurchase(typesName, accountId);
189
case "Items":
190
return await handleMiscItemPurchase(typesName, accountId, quantity);
191
case "Recipes":
192
case "Consumables": // Blueprints for Ciphers, Antitoxins
193
return await handleRecipesPurchase(typesName, accountId, quantity);
194
case "Restoratives": // Codex Scanner, Remote Observer, Starburst
195
return await handleRestorativesPurchase(typesName, accountId, quantity);
196
break;
197
default:
198
throw new Error(`unknown Types category: ${typeCategory} not implemented or new`);
199
113
}
200
114
};
201
115
202
const handleSentinelPurchase = async (sentinelName: string, accountId: string) => {
203
const sentinel = await addSentinel(sentinelName, accountId);
204
205
await updateSlots(accountId, SlotNameToInventoryName.SENTINEL, 0, 1);
206
207
return {
208
InventoryChanges: {
209
SentinelBin: { count: 1, platinum: 0, Slots: -1 },
210
Sentinels: [sentinel]
211
}
212
};
213
};
214
215
const handleCustomizationPurchase = async (customizationName: string, accountId: string) => {
216
const customization = await addCustomization(customizationName, accountId);
217
218
return {
219
InventoryChanges: {
220
FlavourItems: [customization]
221
}
222
};
223
};
224
225
116
const boosterCollection = [
226
117
"/Lotus/Types/Boosters/ResourceAmountBooster",
227
118
"/Lotus/Types/Boosters/AffinityBooster",
@@ -247,54 +138,3 @@ const handleBoostersPurchase = async (boosterStoreName: string, accountId: strin
247
138
}
248
139
};
249
140
};
250
251
const handleMiscItemPurchase = async (uniqueName: string, accountId: string, quantity: number) => {
252
const inventory = await getInventory(accountId);
253
const miscItemChanges = [
254
{
255
ItemType: uniqueName,
256
ItemCount: quantity
257
} satisfies IMiscItem
258
];
259
addMiscItems(inventory, miscItemChanges);
260
await inventory.save();
261
return {
262
InventoryChanges: {
263
MiscItems: miscItemChanges
264
}
265
};
266
};
267
268
const handleRecipesPurchase = async (uniqueName: string, accountId: string, quantity: number) => {
269
const inventory = await getInventory(accountId);
270
const recipeChanges = [
271
{
272
ItemType: uniqueName,
273
ItemCount: quantity
274
} satisfies ITypeCount
275
];
276
addRecipes(inventory, recipeChanges);
277
await inventory.save();
278
return {
279
InventoryChanges: {
280
Recipes: recipeChanges
281
}
282
};
283
};
284
285
const handleRestorativesPurchase = async (uniqueName: string, accountId: string, quantity: number) => {
286
const inventory = await getInventory(accountId);
287
const consumablesChanges = [
288
{
289
ItemType: uniqueName,
290
ItemCount: quantity
291
} satisfies IConsumable
292
];
293
addConsumables(inventory, consumablesChanges);
294
await inventory.save();
295
return {
296
InventoryChanges: {
297
Consumables: consumablesChanges
298
}
299
};
300
};
@@ -419,6 +419,14 @@ export interface ICrewShipHarnessConfig {
419
419
Upgrades?: string[];
420
420
}
421
421
422
export enum InventorySlot {
423
SUITS = "SuitBin",
424
WEAPONS = "WeaponBin",
425
MECHSUITS = "MechBin",
426
PVE_LOADOUTS = "PveBonusLoadoutBin",
427
SENTINELS = "SentinelBin"
428
}
429
422
430
export interface ISlots {
423
431
Extra: number; // can be undefined, but not if used via mongoose
424
432
Slots: number;
@@ -42,14 +42,6 @@ export type IBinChanges = {
42
42
Extra?: number;
43
43
};
44
44
45
export enum SlotNameToInventoryName {
46
SUIT = "SuitBin",
47
WEAPON = "WeaponBin",
48
MECHSUIT = "MechBin",
49
LOADOUT = "PveBonusLoadoutBin",
50
SENTINEL = "SentinelBin"
51
}
52
53
45
export type SlotPurchaseName =
54
46
| "SuitSlotItem"
55
47
| "TwoSentinelSlotItem"