XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: rushing recipes, refactor: addItem (#248)

b08fff19
Sainan <sainan@calamity.gg>
提交于

代码差异

6 个文件 +174 -209
Modified src/controllers/api/claimCompletedRecipeController.ts +22 -24
@@ -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 };
Modified src/controllers/api/inventorySlotsController.ts +2 -2
@@ -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
Modified src/services/inventoryService.ts +129 -2
@@ -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);
Modified src/services/purchaseService.ts +13 -173
@@ -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 };
Modified src/types/inventoryTypes/inventoryTypes.ts +8 -0
@@ -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;
Modified src/types/purchaseTypes.ts +0 -8
@@ -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"