diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index 52236166..034b2e10 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -3,11 +3,11 @@ import { RequestHandler } from "express"; import { logger } from "@/src/utils/logger"; -import { getItemByBlueprint, getItemCategoryByUniqueName } from "@/src/services/itemDataService"; +import { getItemByBlueprint } from "@/src/services/itemDataService"; import { IOid } from "@/src/types/commonTypes"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { getInventory } from "@/src/services/inventoryService"; +import { getInventory, updateCurrency, addItem } from "@/src/services/inventoryService"; export interface IClaimCompletedRecipeRequest { RecipeIds: IOid[]; @@ -19,12 +19,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = const accountId = await getAccountIdForRequest(req); if (!accountId) throw new Error("no account id"); - console.log(claimCompletedRecipeRequest); const inventory = await getInventory(accountId); const pendingRecipe = inventory.PendingRecipes.find( recipe => recipe._id?.toString() === claimCompletedRecipeRequest.RecipeIds[0].$oid ); - console.log(pendingRecipe); if (!pendingRecipe) { logger.error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`); throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`); @@ -36,29 +34,29 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = // throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`); // } - //get completed Items - const completedItemName = getItemByBlueprint(pendingRecipe.ItemType)?.uniqueName; + inventory.PendingRecipes.pull(pendingRecipe._id); + await inventory.save(); - if (!completedItemName) { + const buildable = getItemByBlueprint(pendingRecipe.ItemType); + if (!buildable) { logger.error(`no completed item found for recipe ${pendingRecipe._id}`); throw new Error(`no completed item found for recipe ${pendingRecipe._id}`); } - const itemCategory = getItemCategoryByUniqueName(completedItemName) as keyof typeof inventory; - console.log(itemCategory); - //TODO: remove all Schema.Mixed for inventory[itemCategory] not to be any - //add item - //inventory[itemCategory]. - //add additional item components like mods or weapons for a sentinel. - //const additionalItemComponents = itemComponents[uniqueName] - //add these items to inventory - //return changes as InventoryChanges - - //remove pending recipe - inventory.PendingRecipes.pull(pendingRecipe._id); - // await inventory.save(); - - logger.debug("Claiming Completed Recipe", { completedItemName }); - - res.json({ InventoryChanges: {} }); + if (req.query.cancel) { + // TODO: Refund items + res.json({}); + } else { + logger.debug("Claiming Recipe", { buildable, pendingRecipe }); + let currencyChanges = {}; + if (req.query.rush && buildable.skipBuildTimePrice) { + currencyChanges = await updateCurrency(buildable.skipBuildTimePrice, true, accountId); + } + res.json({ + InventoryChanges: { + ...currencyChanges, + ...(await addItem(accountId, buildable.uniqueName, buildable.buildQuantity)).InventoryChanges + } + }); + } }; diff --git a/src/controllers/api/inventorySlotsController.ts b/src/controllers/api/inventorySlotsController.ts index fc3c68a7..b026077b 100644 --- a/src/controllers/api/inventorySlotsController.ts +++ b/src/controllers/api/inventorySlotsController.ts @@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { updateCurrency } from "@/src/services/inventoryService"; import { RequestHandler } from "express"; import { updateSlots } from "@/src/services/inventoryService"; -import { SlotNameToInventoryName } from "@/src/types/purchaseTypes"; +import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; /* loadout slots are additionally purchased slots only @@ -28,7 +28,7 @@ export const inventorySlotsController: RequestHandler = async (req, res) => { //TODO: check which slot was purchased because pvpBonus is also possible const currencyChanges = await updateCurrency(20, true, accountId); - await updateSlots(accountId, SlotNameToInventoryName.LOADOUT, 1, 1); + await updateSlots(accountId, InventorySlot.PVE_LOADOUTS, 1, 1); //console.log({ InventoryChanges: currencyChanges }, " added loadout changes:"); diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 357a2120..f21ea25a 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -14,7 +14,8 @@ import { IMission, IRawUpgrade, ISeasonChallengeHistory, - ITypeCount + ITypeCount, + InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { IGenericUpdate } from "../types/genericUpdate"; import { @@ -24,7 +25,7 @@ import { IUpdateChallengeProgressRequest } from "../types/requestTypes"; import { logger } from "@/src/utils/logger"; -import { WeaponTypeInternal, getExalted } from "@/src/services/itemDataService"; +import { WeaponTypeInternal, getWeaponType, getExalted } from "@/src/services/itemDataService"; import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes"; export const createInventory = async ( @@ -65,6 +66,132 @@ export const getInventory = async (accountOwnerId: string) => { return inventory; }; +export const addItem = async ( + accountId: string, + typeName: string, + quantity: number = 1 +): Promise<{ InventoryChanges: object }> => { + switch (typeName.substr(1).split("/")[1]) { + case "Powersuits": + if (typeName.includes("EntratiMech")) { + const mechSuit = await addMechSuit(typeName, accountId); + await updateSlots(accountId, InventorySlot.MECHSUITS, 0, 1); + logger.debug("mech suit", mechSuit); + return { + InventoryChanges: { + MechBin: { + count: 1, + platinum: 0, + Slots: -1 + }, + MechSuits: [mechSuit] + } + }; + } + const suit = await addPowerSuit(typeName, accountId); + await updateSlots(accountId, InventorySlot.SUITS, 0, 1); + return { + InventoryChanges: { + SuitBin: { + count: 1, + platinum: 0, + Slots: -1 + }, + Suits: [suit] + } + }; + case "Weapons": + const weaponType = getWeaponType(typeName); + const weapon = await addWeapon(weaponType, typeName, accountId); + await updateSlots(accountId, InventorySlot.WEAPONS, 0, 1); + return { + InventoryChanges: { + WeaponBin: { count: 1, platinum: 0, Slots: -1 }, + [weaponType]: [weapon] + } + }; + case "Interface": + return { + InventoryChanges: { + FlavourItems: [await addCustomization(typeName, accountId)] + } + }; + case "Types": + switch (typeName.substr(1).split("/")[2]) { + case "AvatarImages": + case "SuitCustomizations": + return { + InventoryChanges: { + FlavourItems: [await addCustomization(typeName, accountId)] + } + }; + case "Sentinels": + // TOOD: Sentinels should also grant their DefaultUpgrades & SentinelWeapon. + const sentinel = await addSentinel(typeName, accountId); + await updateSlots(accountId, InventorySlot.SENTINELS, 0, 1); + return { + InventoryChanges: { + SentinelBin: { count: 1, platinum: 0, Slots: -1 }, + Sentinels: [sentinel] + } + }; + case "Items": { + const inventory = await getInventory(accountId); + const miscItemChanges = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addMiscItems(inventory, miscItemChanges); + await inventory.save(); + return { + InventoryChanges: { + MiscItems: miscItemChanges + } + }; + } + case "Recipes": + case "Consumables": { + // Blueprints for Ciphers, Antitoxins + const inventory = await getInventory(accountId); + const recipeChanges = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies ITypeCount + ]; + addRecipes(inventory, recipeChanges); + await inventory.save(); + return { + InventoryChanges: { + Recipes: recipeChanges + } + }; + } + case "Restoratives": // Codex Scanner, Remote Observer, Starburst + const inventory = await getInventory(accountId); + const consumablesChanges = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IConsumable + ]; + addConsumables(inventory, consumablesChanges); + await inventory.save(); + return { + InventoryChanges: { + Consumables: consumablesChanges + } + }; + } + break; + } + const errorMessage = `unable to add item: ${typeName}`; + logger.error(errorMessage); + throw new Error(errorMessage); +}; + //TODO: maybe genericMethod for all the add methods, they share a lot of logic export const addSentinel = async (sentinelName: string, accountId: string) => { const inventory = await getInventory(accountId); diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index d9ca8e14..ba13666a 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -1,22 +1,7 @@ import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers"; -import { getWeaponType } from "@/src/services/itemDataService"; import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; -import { - addBooster, - addConsumables, - addCustomization, - addMechSuit, - addMiscItems, - addPowerSuit, - addRecipes, - addSentinel, - addWeapon, - getInventory, - updateCurrency, - updateSlots -} from "@/src/services/inventoryService"; -import { IConsumable, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; -import { IPurchaseRequest, IPurchaseResponse, SlotNameToInventoryName, SlotPurchase } from "@/src/types/purchaseTypes"; +import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService"; +import { IPurchaseRequest, SlotPurchase } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; export const getStoreItemCategory = (storeItem: string) => { @@ -40,34 +25,24 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI const internalName = purchaseRequest.PurchaseParams.StoreItem.replace("/StoreItems", ""); logger.debug(`store category ${storeCategory}`); - let inventoryChanges; + let purchaseResponse; switch (storeCategory) { - case "Powersuits": - inventoryChanges = await handlePowersuitPurchase(internalName, accountId); - break; - case "Weapons": - inventoryChanges = await handleWeaponsPurchase(internalName, accountId); + default: + purchaseResponse = await addItem(accountId, internalName); break; case "Types": - inventoryChanges = await handleTypesPurchase( + purchaseResponse = await handleTypesPurchase( internalName, accountId, purchaseRequest.PurchaseParams.Quantity ); break; case "Boosters": - inventoryChanges = await handleBoostersPurchase(internalName, accountId); + purchaseResponse = await handleBoostersPurchase(internalName, accountId); break; - case "Interface": - inventoryChanges = await handleCustomizationPurchase(internalName, accountId); - break; - default: - const errorMessage = `unknown store category: ${storeCategory} not implemented or new`; - logger.error(errorMessage); - throw new Error(errorMessage); } - if (!inventoryChanges) throw new Error("purchase response was undefined"); + if (!purchaseResponse) throw new Error("purchase response was undefined"); const currencyChanges = await updateCurrency( purchaseRequest.PurchaseParams.ExpectedPrice, @@ -75,12 +50,12 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI accountId ); - inventoryChanges.InventoryChanges = { + purchaseResponse.InventoryChanges = { ...currencyChanges, - ...inventoryChanges.InventoryChanges + ...purchaseResponse.InventoryChanges }; - return inventoryChanges; + return purchaseResponse; }; export const slotPurchaseNameToSlotName: SlotPurchase = { @@ -126,102 +101,18 @@ const handleSlotPurchase = async (slotPurchaseNameFull: string, accountId: strin }; }; -const handleWeaponsPurchase = async (weaponName: string, accountId: string) => { - const weaponType = getWeaponType(weaponName); - const addedWeapon = await addWeapon(weaponType, weaponName, accountId); - - await updateSlots(accountId, SlotNameToInventoryName.WEAPON, 0, 1); - - return { - InventoryChanges: { - WeaponBin: { count: 1, platinum: 0, Slots: -1 }, - [weaponType]: [addedWeapon] - } - } as IPurchaseResponse; -}; - -const handlePowersuitPurchase = async (powersuitName: string, accountId: string) => { - if (powersuitName.includes("EntratiMech")) { - const mechSuit = await addMechSuit(powersuitName, accountId); - - await updateSlots(accountId, SlotNameToInventoryName.MECHSUIT, 0, 1); - logger.debug("mech suit", mechSuit); - - return { - InventoryChanges: { - MechBin: { - count: 1, - platinum: 0, - Slots: -1 - }, - MechSuits: [mechSuit] - } - } as IPurchaseResponse; - } - - const suit = await addPowerSuit(powersuitName, accountId); - await updateSlots(accountId, SlotNameToInventoryName.SUIT, 0, 1); - - return { - InventoryChanges: { - SuitBin: { - count: 1, - platinum: 0, - Slots: -1 - }, - Suits: [suit] - } - }; -}; - //TODO: change to getInventory, apply changes then save at the end const handleTypesPurchase = async (typesName: string, accountId: string, quantity: number) => { const typeCategory = getStoreItemTypesCategory(typesName); logger.debug(`type category ${typeCategory}`); switch (typeCategory) { - case "AvatarImages": - case "SuitCustomizations": - return await handleCustomizationPurchase(typesName, accountId); - case "Sentinels": - return await handleSentinelPurchase(typesName, accountId); + default: + return await addItem(accountId, typesName, quantity); case "SlotItems": return await handleSlotPurchase(typesName, accountId); - case "Items": - return await handleMiscItemPurchase(typesName, accountId, quantity); - case "Recipes": - case "Consumables": // Blueprints for Ciphers, Antitoxins - return await handleRecipesPurchase(typesName, accountId, quantity); - case "Restoratives": // Codex Scanner, Remote Observer, Starburst - return await handleRestorativesPurchase(typesName, accountId, quantity); - break; - default: - throw new Error(`unknown Types category: ${typeCategory} not implemented or new`); } }; -const handleSentinelPurchase = async (sentinelName: string, accountId: string) => { - const sentinel = await addSentinel(sentinelName, accountId); - - await updateSlots(accountId, SlotNameToInventoryName.SENTINEL, 0, 1); - - return { - InventoryChanges: { - SentinelBin: { count: 1, platinum: 0, Slots: -1 }, - Sentinels: [sentinel] - } - }; -}; - -const handleCustomizationPurchase = async (customizationName: string, accountId: string) => { - const customization = await addCustomization(customizationName, accountId); - - return { - InventoryChanges: { - FlavourItems: [customization] - } - }; -}; - const boosterCollection = [ "/Lotus/Types/Boosters/ResourceAmountBooster", "/Lotus/Types/Boosters/AffinityBooster", @@ -247,54 +138,3 @@ const handleBoostersPurchase = async (boosterStoreName: string, accountId: strin } }; }; - -const handleMiscItemPurchase = async (uniqueName: string, accountId: string, quantity: number) => { - const inventory = await getInventory(accountId); - const miscItemChanges = [ - { - ItemType: uniqueName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addMiscItems(inventory, miscItemChanges); - await inventory.save(); - return { - InventoryChanges: { - MiscItems: miscItemChanges - } - }; -}; - -const handleRecipesPurchase = async (uniqueName: string, accountId: string, quantity: number) => { - const inventory = await getInventory(accountId); - const recipeChanges = [ - { - ItemType: uniqueName, - ItemCount: quantity - } satisfies ITypeCount - ]; - addRecipes(inventory, recipeChanges); - await inventory.save(); - return { - InventoryChanges: { - Recipes: recipeChanges - } - }; -}; - -const handleRestorativesPurchase = async (uniqueName: string, accountId: string, quantity: number) => { - const inventory = await getInventory(accountId); - const consumablesChanges = [ - { - ItemType: uniqueName, - ItemCount: quantity - } satisfies IConsumable - ]; - addConsumables(inventory, consumablesChanges); - await inventory.save(); - return { - InventoryChanges: { - Consumables: consumablesChanges - } - }; -}; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 66f863ee..3bc903cd 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -419,6 +419,14 @@ export interface ICrewShipHarnessConfig { Upgrades?: string[]; } +export enum InventorySlot { + SUITS = "SuitBin", + WEAPONS = "WeaponBin", + MECHSUITS = "MechBin", + PVE_LOADOUTS = "PveBonusLoadoutBin", + SENTINELS = "SentinelBin" +} + export interface ISlots { Extra: number; // can be undefined, but not if used via mongoose Slots: number; diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index c6b5d648..73ee085c 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -42,14 +42,6 @@ export type IBinChanges = { Extra?: number; }; -export enum SlotNameToInventoryName { - SUIT = "SuitBin", - WEAPON = "WeaponBin", - MECHSUIT = "MechBin", - LOADOUT = "PveBonusLoadoutBin", - SENTINEL = "SentinelBin" -} - export type SlotPurchaseName = | "SuitSlotItem" | "TwoSentinelSlotItem"