diff --git a/src/controllers/api/focusController.ts b/src/controllers/api/focusController.ts index 9b6129ac..8861584b 100644 --- a/src/controllers/api/focusController.ts +++ b/src/controllers/api/focusController.ts @@ -103,7 +103,7 @@ export const focusController: RequestHandler = async (req, res) => { "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel" ]; - const result = await addEquipment("OperatorAmps", request.StartingWeaponType, accountId, parts); + const result = await addEquipment("OperatorAmps", request.StartingWeaponType, accountId, parts, true); res.json(result); break; } diff --git a/src/controllers/api/gildWeaponController.ts b/src/controllers/api/gildWeaponController.ts index 0b77513f..00daad44 100644 --- a/src/controllers/api/gildWeaponController.ts +++ b/src/controllers/api/gildWeaponController.ts @@ -2,16 +2,8 @@ import { RequestHandler } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getInventory } from "@/src/services/inventoryService"; -import { WeaponTypeInternal } from "@/src/services/itemDataService"; import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes"; - -const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [ - "LongGuns", - "Pistols", - "Melee", - "OperatorAmps", - "Hoverboards" // Not sure about hoverboards just coppied from modual crafting -]; +import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; interface IGildWeaponRequest { ItemName: string; @@ -19,7 +11,7 @@ interface IGildWeaponRequest { PolarizeSlot?: number; PolarizeValue?: ArtifactPolarity; ItemId: string; - Category: WeaponTypeInternal | "Hoverboards"; + Category: TEquipmentKey; } // In export there no recipes for gild action, so reputation and ressources only consumed visually @@ -29,10 +21,10 @@ export const gildWeaponController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const data: IGildWeaponRequest = getJSONfromString(String(req.body)); data.ItemId = String(req.query.ItemId); - if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) { + if (!equipmentKeys.includes(req.query.Category as TEquipmentKey)) { throw new Error(`Unknown modular weapon Category: ${req.query.Category}`); } - data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards"; + data.Category = req.query.Category as TEquipmentKey; const inventory = await getInventory(accountId); if (!inventory[data.Category]) { diff --git a/src/controllers/api/setWeaponSkillTreeController.ts b/src/controllers/api/setWeaponSkillTreeController.ts index dae03f03..63a5fe0e 100644 --- a/src/controllers/api/setWeaponSkillTreeController.ts +++ b/src/controllers/api/setWeaponSkillTreeController.ts @@ -2,7 +2,7 @@ import { RequestHandler } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { getInventory } from "@/src/services/inventoryService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; -import { WeaponTypeInternal } from "@/src/services/itemDataService"; +import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; // eslint-disable-next-line @typescript-eslint/no-misused-promises export const setWeaponSkillTreeController: RequestHandler = async (req, res) => { @@ -10,7 +10,7 @@ export const setWeaponSkillTreeController: RequestHandler = async (req, res) => const inventory = await getInventory(accountId); const payload = getJSONfromString(String(req.body)) as ISetWeaponSkillTreeRequest; - const item = inventory[req.query.Category as WeaponTypeInternal].find( + const item = inventory[req.query.Category as TEquipmentKey].find( item => item._id.toString() == (req.query.ItemId as string) )!; item.SkillTree = payload.SkillTree; diff --git a/src/controllers/custom/addItemController.ts b/src/controllers/custom/addItemController.ts index 7488710f..18fa6dc9 100644 --- a/src/controllers/custom/addItemController.ts +++ b/src/controllers/custom/addItemController.ts @@ -1,28 +1,15 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; -import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers"; -import { getWeaponType } from "@/src/services/itemDataService"; -import { addPowerSuit, addEquipment } from "@/src/services/inventoryService"; +import { toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers"; +import { addItem } from "@/src/services/inventoryService"; import { RequestHandler } from "express"; // eslint-disable-next-line @typescript-eslint/no-misused-promises const addItemController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const request = toAddItemRequest(req.body); - - switch (request.type) { - case ItemType.Powersuit: - const powersuit = await addPowerSuit(request.InternalName, accountId); - res.json(powersuit); - return; - case ItemType.Weapon: - const weaponType = getWeaponType(request.InternalName); - const weapon = await addEquipment(weaponType, request.InternalName, accountId); - res.json(weapon); - break; - default: - res.status(400).json({ error: "something went wrong" }); - break; - } + const response = await addItem(request.InternalName, accountId, 1, true); + res.json(response); + return; }; export { addItemController }; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 392952b7..4b2269ed 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -36,7 +36,9 @@ import { IPeriodicMissionCompletionDatabase, IPeriodicMissionCompletionResponse, ILoreFragmentScan, - IEvolutionProgress + IEvolutionProgress, + IKubrowPetEgg as IKubrowPetEggDatabase, + IKubrowPetEggResponse } from "../../types/inventoryTypes/inventoryTypes"; import { IOid } from "../../types/commonTypes"; import { @@ -578,6 +580,26 @@ const evolutionProgressSchema = new Schema( { _id: false } ); +const kubrowPetEggSchema = new Schema({ + ItemType: String, + ExpirationDate: Date +}); + +kubrowPetEggSchema.virtual("ItemId").get(function () { + return { $oid: this._id.toString() }; +}); + +kubrowPetEggSchema.set("toJSON", { + virtuals: true, + transform(_document, returnedObject) { + delete returnedObject._id; + delete returnedObject.__v; + (returnedObject as IKubrowPetEggResponse).ExpirationDate = { + $date: { $numberLong: (returnedObject as IKubrowPetEggDatabase).ExpirationDate.getTime().toString() } + }; + } +}); + const inventorySchema = new Schema( { accountOwnerId: Schema.Types.ObjectId, @@ -676,7 +698,7 @@ const inventorySchema = new Schema( //Modular Pets MoaPets: [EquipmentSchema], - KubrowPetEggs: [Schema.Types.Mixed], + KubrowPetEggs: [kubrowPetEggSchema], //Like PowerSuit Cat\Kubrow or etc Pets KubrowPets: [EquipmentSchema], //Prints Cat(3 Prints)\Kubrow(2 Prints) Pets @@ -726,7 +748,7 @@ const inventorySchema = new Schema( CrewShipRawSalvage: [Schema.Types.Mixed], //Default RailJack - CrewShips: [Schema.Types.Mixed], + CrewShips: [EquipmentSchema], CrewShipAmmo: [typeCountSchema], CrewShipWeapons: [Schema.Types.Mixed], CrewShipWeaponSkins: [Schema.Types.Mixed], @@ -984,7 +1006,12 @@ type InventoryDocumentProps = { SentinelWeapons: Types.DocumentArray; Hoverboards: Types.DocumentArray; MoaPets: Types.DocumentArray; + KubrowPets: Types.DocumentArray; + CrewShips: Types.DocumentArray; + CrewShipHarnesses: Types.DocumentArray; + KubrowPetEggs: Types.DocumentArray; WeaponSkins: Types.DocumentArray; + QuestKeys: Types.DocumentArray; }; // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index b7a1059f..06837c97 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -13,31 +13,38 @@ import { IRawUpgrade, ISeasonChallenge, ITypeCount, - InventorySlot, IWeaponSkinClient, TEquipmentKey, equipmentKeys, - IFusionTreasure + IFusionTreasure, + IKubrowPetEgg, + IQuestKeyResponse } from "@/src/types/inventoryTypes/inventoryTypes"; -import { IGenericUpdate } from "../types/genericUpdate"; +import { IGenericUpdate } from "@/src/types/genericUpdate"; import { IArtifactsRequest, IMissionInventoryUpdateRequest, IThemeUpdateRequest, IUpdateChallengeProgressRequest -} from "../types/requestTypes"; +} from "@/src/types/requestTypes"; import { logger } from "@/src/utils/logger"; -import { getWeaponType, getExalted } from "@/src/services/itemDataService"; +import { getDefaultGear, getBinKey } from "@/src/services/itemDataService"; import { getRandomWeightedReward } from "@/src/services/rngService"; -import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes"; -import { IEquipmentClient } from "../types/inventoryTypes/commonInventoryTypes"; +import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "@/src/types/syndicateTypes"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { ExportBoosterPacks, ExportCustoms, ExportFlavour, + ExportKeys, ExportRecipes, - ExportResources + ExportResources, + ExportSentinels, + ExportUpgrades, + ExportWarframes, + ExportWeapons } from "warframe-public-export-plus"; +import { creditBundles, fusionBundles } from "@/src/services/missionInventoryUpdateService"; export const createInventory = async ( accountOwnerId: Types.ObjectId, @@ -105,8 +112,9 @@ export const getInventory = async (accountOwnerId: string) => { export const addItem = async ( accountId: string, typeName: string, - quantity: number = 1 -): Promise<{ InventoryChanges: IInventoryChanges }> => { + quantity: number = 1, + isStorePurchase: boolean = false +): Promise<{ InventoryChanges: any }> => { // Strict typing if (typeName in ExportRecipes) { const inventory = await getInventory(accountId); @@ -124,22 +132,105 @@ export const addItem = async ( } }; } + if (typeName in ExportResources) { - const inventory = await getInventory(accountId); - const miscItemChanges = [ - { - ItemType: typeName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addMiscItems(inventory, miscItemChanges); - await inventory.save(); - return { - InventoryChanges: { - MiscItems: miscItemChanges + switch (ExportResources[typeName].productCategory) { + case "ShipDecorations": { + const inventory = await getInventory(accountId); + const changes = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addShipDecorations(inventory, changes); + await inventory.save(); + return { + InventoryChanges: { + ShipDecorations: changes + } + }; } - }; + case "MiscItems": { + const inventory = await getInventory(accountId); + const miscItemChanges = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addMiscItems(inventory, miscItemChanges); + await inventory.save(); + return { + InventoryChanges: { + MiscItems: miscItemChanges + } + }; + } + case "CrewShips": { + const inventory = await getInventory(accountId); + const miscItemChanges = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addMiscItems(inventory, miscItemChanges); + const crewShipChanges = await addEquipment( + ExportResources[typeName].productCategory, + typeName, + accountId, + undefined, + isStorePurchase + ); + const crewShipHarnessChanges = await addEquipment( + "CrewShipHarnesses", + "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness", + accountId, + undefined, + isStorePurchase + ); + await inventory.save(); + return { + InventoryChanges: { + ...crewShipChanges.InventoryChanges, + ...crewShipHarnessChanges.InventoryChanges, + MiscItems: miscItemChanges + } + }; + } + case "KubrowPetEggs": { + const inventory = await getInventory(accountId); + const changes = { + ItemType: typeName, + ExpirationDate: new Date() + } satisfies IKubrowPetEgg; + return { + InventoryChanges: { + kubrowPetEggs: [await addKubrowEgg(inventory, changes)] + } + }; + } + case "FusionTreasures": { + const inventory = await getInventory(accountId); + const changes = [ + { + ItemType: typeName, + ItemCount: quantity, + Sockets: 0 + } satisfies IFusionTreasure + ]; + addFusionTreasures(inventory, changes); + await inventory.save(); + return { + InventoryChanges: { + FusionTreasures: changes + } + }; + } + } } + if (typeName in ExportCustoms) { return { InventoryChanges: { @@ -147,6 +238,7 @@ export const addItem = async ( } }; } + if (typeName in ExportFlavour) { return { InventoryChanges: { @@ -154,6 +246,7 @@ export const addItem = async ( } }; } + if (typeName in ExportBoosterPacks) { const pack = ExportBoosterPacks[typeName]; const InventoryChanges = {}; @@ -163,248 +256,130 @@ export const addItem = async ( logger.debug(`booster pack rolled`, result); combineInventoryChanges( InventoryChanges, - (await addItem(accountId, result.type, result.itemCount)).InventoryChanges + (await addItem(accountId, result.type, result.itemCount, isStorePurchase)).InventoryChanges ); } } return { InventoryChanges }; } - // Path-based duck typing - switch (typeName.substr(1).split("/")[1]) { - case "Powersuits": - switch (typeName.substr(1).split("/")[2]) { - default: { - 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 "Archwing": { - const spaceSuit = await addSpaceSuit(typeName, accountId); - await updateSlots(accountId, InventorySlot.SPACESUITS, 0, 1); - return { - InventoryChanges: { - SpaceSuitBin: { - count: 1, - platinum: 0, - Slots: -1 - }, - SpaceSuits: [spaceSuit] - } - }; - } - case "EntratiMech": { - const mechSuit = await addMechSuit(typeName, accountId); - await updateSlots(accountId, InventorySlot.MECHSUITS, 0, 1); - return { - InventoryChanges: { - MechBin: { - count: 1, - platinum: 0, - Slots: -1 - }, - MechSuits: [mechSuit] - } - }; - } + if (typeName in ExportUpgrades) { + const inventory = await getInventory(accountId); + const changes = [ + { + ItemType: typeName, + ItemCount: quantity } - break; - case "Weapons": - const weaponType = getWeaponType(typeName); - const weapon = await addEquipment(weaponType, typeName, accountId); - await updateSlots(accountId, InventorySlot.WEAPONS, 0, 1); - return { - InventoryChanges: { - WeaponBin: { count: 1, platinum: 0, Slots: -1 }, - [weaponType]: [weapon] - } - }; - case "Upgrades": { - const inventory = await getInventory(accountId); - const changes = [ - { - ItemType: typeName, - ItemCount: quantity - } - ]; - addMods(inventory, changes); - await inventory.save(); - return { - InventoryChanges: { - RawUpgrades: changes - } - }; + ]; + addMods(inventory, changes); + await inventory.save(); + return { + InventoryChanges: { + RawUpgrades: changes + } + }; + } + + for (const exportCategory of [ExportWarframes, ExportWeapons, ExportSentinels]) { + if (typeName in exportCategory) { + const categoryData = exportCategory[typeName]; + const productCategory = categoryData.productCategory; + const changes = await addEquipment(productCategory, typeName, accountId, undefined, isStorePurchase); + const binKey = getBinKey(productCategory); + const inventoryChanges = { ...changes.InventoryChanges }; + + if (binKey && !isStorePurchase) { + await updateSlots(accountId, binKey, 0, 1); + inventoryChanges[binKey] = inventoryChanges[binKey] + ? { + ...inventoryChanges[binKey], + count: inventoryChanges[binKey].count + 1, + Slots: inventoryChanges[binKey].Slots - 1 + } + : { count: 1, platinum: 0, Slots: -1 }; + } + + return { InventoryChanges: inventoryChanges }; } - case "Objects": { - // /Lotus/Objects/Tenno/Props/TnoLisetTextProjector (Note Beacon) - const inventory = await getInventory(accountId); - const changes = [ - { - ItemType: typeName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addShipDecorations(inventory, changes); - await inventory.save(); - return { - InventoryChanges: { - ShipDecorations: changes - } - }; - } - case "Types": - switch (typeName.substr(1).split("/")[2]) { - 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": { - switch (typeName.substr(1).split("/")[3]) { - case "ShipDecos": { - const inventory = await getInventory(accountId); - const changes = [ - { - ItemType: typeName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addShipDecorations(inventory, changes); - await inventory.save(); - return { - InventoryChanges: { - ShipDecorations: changes - } - }; - } - default: { - const inventory = await getInventory(accountId); - const miscItemChanges = [ - { - ItemType: typeName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addMiscItems(inventory, miscItemChanges); - await inventory.save(); - return { - InventoryChanges: { - MiscItems: miscItemChanges - } - }; - } - } - } - case "Game": - if (typeName.substr(1).split("/")[3] == "Projections") { - // Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze - const inventory = await getInventory(accountId); - const miscItemChanges = [ - { - ItemType: typeName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addMiscItems(inventory, miscItemChanges); - await inventory.save(); - return { - InventoryChanges: { - MiscItems: miscItemChanges - } - }; - } - break; - case "Restoratives": // Codex Scanner, Remote Observer, Starburst + } + + if (typeName in ExportKeys) { + const changes = { + ItemType: typeName + }; + addKey(typeName, accountId); + return { + InventoryChanges: { + QuestKeys: [changes] + } + }; + } + + // Path-based duck typing + const pathParts = typeName.slice(1).split("/"); + if (pathParts[1] === "Types") { + switch (pathParts[2]) { + case "Game": + if (pathParts[3] === "Projections") { + // Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze const inventory = await getInventory(accountId); - const consumablesChanges = [ + const miscItemChanges = [ { ItemType: typeName, ItemCount: quantity - } satisfies IConsumable + } satisfies IMiscItem ]; - addConsumables(inventory, consumablesChanges); + addMiscItems(inventory, miscItemChanges); await inventory.save(); return { InventoryChanges: { - Consumables: consumablesChanges + MiscItems: miscItemChanges } }; + } + break; + 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 + } + }; + case "StoreItems": + if (pathParts[3] === "CreditBundles") { + const currencyChanges = await updateCurrency(creditBundles[typeName] * -quantity, false, accountId); + return { + InventoryChanges: { + ...currencyChanges + } + }; + } + break; + } + } else if (pathParts[2] === "Mods" && pathParts[3] === "FusionBundles") { + const inventory = await getInventory(accountId); + const fusionPoints = fusionBundles[typeName] * quantity; + inventory.FusionPoints += fusionPoints; + await inventory.save(); + return { + InventoryChanges: { + FusionPoints: fusionPoints } - 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); - const sentinelIndex = inventory.Sentinels.push({ ItemType: sentinelName, Configs: [], XP: 0 }); - const changedInventory = await inventory.save(); - return changedInventory.Sentinels[sentinelIndex - 1].toJSON(); -}; - -export const addPowerSuit = async (powersuitName: string, accountId: string): Promise => { - const specialItems = getExalted(powersuitName); - if (specialItems != false) { - for await (const specialItem of specialItems) { - await addSpecialItem(specialItem, accountId); - } - } - const inventory = await getInventory(accountId); - const suitIndex = inventory.Suits.push({ ItemType: powersuitName, Configs: [], UpgradeVer: 101, XP: 0 }); - const changedInventory = await inventory.save(); - return changedInventory.Suits[suitIndex - 1].toJSON(); -}; - -export const addMechSuit = async (mechsuitName: string, accountId: string) => { - const specialItems = getExalted(mechsuitName); - if (specialItems != false) { - for await (const specialItem of specialItems) { - await addSpecialItem(specialItem, accountId); - } - } - const inventory = await getInventory(accountId); - const suitIndex = inventory.MechSuits.push({ ItemType: mechsuitName, Configs: [], UpgradeVer: 101, XP: 0 }); - const changedInventory = await inventory.save(); - return changedInventory.MechSuits[suitIndex - 1].toJSON(); -}; - -export const addSpecialItem = async (itemName: string, accountId: string) => { - const inventory = await getInventory(accountId); - const specialItemIndex = inventory.SpecialItems.push({ - ItemType: itemName, - Configs: [], - Features: 1, - UpgradeVer: 101, - XP: 0 - }); - const changedInventory = await inventory.save(); - return changedInventory.SpecialItems[specialItemIndex - 1].toJSON(); -}; - -export const addSpaceSuit = async (spacesuitName: string, accountId: string) => { - const inventory = await getInventory(accountId); - const suitIndex = inventory.SpaceSuits.push({ ItemType: spacesuitName, Configs: [], UpgradeVer: 101, XP: 0 }); - const changedInventory = await inventory.save(); - return changedInventory.SpaceSuits[suitIndex - 1].toJSON(); -}; - export const updateSlots = async (accountId: string, slotName: SlotNames, slotAmount: number, extraAmount: number) => { const inventory = await getInventory(accountId); @@ -515,8 +490,23 @@ export const addEquipment = async ( category: TEquipmentKey, type: string, accountId: string, - modularParts: string[] | undefined = undefined -): Promise => { + modularParts: string[] | undefined = undefined, + isStorePurchase: boolean = false +): Promise<{ InventoryChanges: any }> => { + const defaultGear = getDefaultGear(type); + let InventoryChanges: any = {}; + + if (defaultGear != false) { + for await (const item of defaultGear) { + logger.debug(`defaultGear ${item}`); + const result = await addItem(accountId, item, 1, isStorePurchase); + InventoryChanges = { + ...InventoryChanges, + ...result.InventoryChanges + }; + } + } + const inventory = await getInventory(accountId); const index = inventory[category].push({ @@ -527,7 +517,12 @@ export const addEquipment = async ( }); const changedInventory = await inventory.save(); - return changedInventory[category][index - 1].toJSON(); + + InventoryChanges[category] = [changedInventory[category][index - 1].toJSON()]; + + return { + InventoryChanges + }; }; export const addCustomization = async (customizatonName: string, accountId: string): Promise => { @@ -544,6 +539,23 @@ export const addSkin = async (typeName: string, accountId: string): Promise => { + const index = + inventory.KubrowPetEggs.push({ ItemType: kubrowEgg.ItemType, ExpirationDate: kubrowEgg.ExpirationDate }) - 1; + const changedInventory = await inventory.save(); + return changedInventory.KubrowPetEggs[index - 1]; +}; + +export const addKey = async (typeName: string, accountId: string): Promise => { + const inventory = await getInventory(accountId); + const index = inventory.QuestKeys.push({ ItemType: typeName }) - 1; + const changedInventory = await inventory.save(); + return changedInventory.QuestKeys[index].toJSON(); +}; + const addGearExpByCategory = ( inventory: IInventoryDatabaseDocument, gearArray: IEquipmentClient[] | undefined, diff --git a/src/services/itemDataService.ts b/src/services/itemDataService.ts index 3bf039dd..38d363a6 100644 --- a/src/services/itemDataService.ts +++ b/src/services/itemDataService.ts @@ -1,80 +1,55 @@ -import { getIndexAfter } from "@/src/helpers/stringHelpers"; -import { logger } from "@/src/utils/logger"; -import { - dict_en, - ExportRecipes, - ExportWarframes, - ExportWeapons, - IPowersuit, - IRecipe -} from "warframe-public-export-plus"; - -export type WeaponTypeInternal = - | "LongGuns" - | "Pistols" - | "Melee" - | "SpaceMelee" - | "SpaceGuns" - | "SentinelWeapons" - | "OperatorAmps" - | "SpecialItems"; - -export const getWeaponType = (weaponName: string): WeaponTypeInternal => { - const weaponInfo = ExportWeapons[weaponName]; - - if (!weaponInfo) { - throw new Error(`unknown weapon ${weaponName}`); - } - - // Many non-weapon items are "Pistols" in Public Export, so some duck typing is needed. - if (weaponInfo.totalDamage == 0) { - throw new Error(`${weaponName} doesn't quack like a weapon`); - } - - const weaponType = weaponInfo.productCategory; - - if (!weaponType) { - logger.error(`unknown weapon category for item ${weaponName}`); - throw new Error(`unknown weapon category for item ${weaponName}`); - } - - return weaponType; -}; +import { dict_en, ExportRecipes, ExportSentinels, ExportWarframes, IRecipe } from "warframe-public-export-plus"; +import { InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; export const getRecipe = (uniqueName: string): IRecipe | undefined => { return ExportRecipes[uniqueName]; }; -export const getExalted = (uniqueName: string) => { - const suit = getSuitByUniqueName(uniqueName); - if (suit?.exalted !== undefined) { - return suit.exalted; - } else { - return false; - } -}; - -export const getItemCategoryByUniqueName = (uniqueName: string) => { - //Lotus/Types/Items/MiscItems/PolymerBundle - - let splitWord = "Items/"; - if (!uniqueName.includes("/Items/")) { - splitWord = "/Types/"; +export const getDefaultGear = (itemType: string) => { + if (itemType in ExportWarframes) { + return ExportWarframes[itemType]?.exalted ?? false; } - const index = getIndexAfter(uniqueName, splitWord); - if (index === -1) { - logger.error(`error parsing item category ${uniqueName}`); - throw new Error(`error parsing item category ${uniqueName}`); + if (itemType in ExportSentinels) { + const { defaultUpgrades = [], defaultWeapon } = ExportSentinels[itemType] || {}; + const defaultGear = [ + ...defaultUpgrades.map(upgrade => upgrade.ItemType), + ...(defaultWeapon ? [defaultWeapon] : []) + ]; + return defaultGear.length ? defaultGear : false; } - const category = uniqueName.substring(index).split("/")[0]; - return category; -}; -export const getSuitByUniqueName = (uniqueName: string): IPowersuit | undefined => { - return ExportWarframes[uniqueName]; + return false; }; export const getEnglishString = (key: string): string => { return dict_en[key] ?? key; }; + +export const getBinKey = (equipmentType: TEquipmentKey): InventorySlot | null => { + switch (equipmentType) { + case "Suits": + return InventorySlot.SUITS; + case "MechSuits": + return InventorySlot.MECHSUITS; + case "LongGuns": + case "Pistols": + case "Melee": + return InventorySlot.WEAPONS; + case "Sentinels": + case "SentinelWeapons": + case "MoaPets": + case "KubrowPets": + return InventorySlot.SENTINELS; + case "SpaceSuits": + case "Hoverboards": + return InventorySlot.SPACESUITS; + case "SpaceGuns": + case "SpaceMelee": + return InventorySlot.SPACEWEAPON; + case "OperatorAmps": + return InventorySlot.OPERATORAMP; + default: + return null; + } +}; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index ad633117..31bfa412 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -123,7 +123,10 @@ const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => { return getRandomReward(pool as IRngResult[]); }; -const creditBundles: Record = { +export const creditBundles: Record = { + "/Lotus/Types/StoreItems/CreditBundles/CreditBundleA": 50000, + "/Lotus/Types/StoreItems/CreditBundles/CreditBundleB": 25000, + "/Lotus/Types/StoreItems/CreditBundles/CreditBundleC": 175000, "/Lotus/StoreItems/Types/PickUps/Credits/1500Credits": 1500, "/Lotus/StoreItems/Types/PickUps/Credits/2000Credits": 2000, "/Lotus/StoreItems/Types/PickUps/Credits/2500Credits": 2500, @@ -139,7 +142,10 @@ const creditBundles: Record = { "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000 }; -const fusionBundles: Record = { +export const fusionBundles: Record = { + "/Lotus/Upgrades/Mods/FusionBundles/MarketTier1FusionBundle": 100, + "/Lotus/Upgrades/Mods/FusionBundles/MarketTier2FusionBundle": 400, + "/Lotus/Upgrades/Mods/FusionBundles/MarketTier3FusionBundle": 1000, "/Lotus/Upgrades/Mods/FusionBundles/CommonFusionBundle": 15, "/Lotus/Upgrades/Mods/FusionBundles/UncommonFusionBundle": 50, "/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle": 80 diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 29bdc218..c09c5544 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -91,10 +91,10 @@ const handleStoreItemAcquisition = async ( } switch (storeCategory) { default: - purchaseResponse = await addItem(accountId, internalName, quantity); + purchaseResponse = await addItem(accountId, internalName, quantity, true); break; case "Types": - purchaseResponse = await handleTypesPurchase(internalName, accountId, quantity); + purchaseResponse = await handleTypesPurchase(internalName, accountId, quantity, true); break; case "Boosters": purchaseResponse = await handleBoostersPurchase(internalName, accountId, durability); @@ -154,13 +154,14 @@ const handleSlotPurchase = async ( const handleTypesPurchase = async ( typesName: string, accountId: string, - quantity: number + quantity: number, + isStorePurchase: boolean = false ): Promise<{ InventoryChanges: IInventoryChanges }> => { const typeCategory = getStoreItemTypesCategory(typesName); logger.debug(`type category ${typeCategory}`); switch (typeCategory) { default: - return await addItem(accountId, typesName, quantity); + return await addItem(accountId, typesName, quantity, isStorePurchase); case "SlotItems": return await handleSlotPurchase(typesName, accountId); } diff --git a/src/types/inventoryTypes/commonInventoryTypes.ts b/src/types/inventoryTypes/commonInventoryTypes.ts index f7026c0c..37998602 100644 --- a/src/types/inventoryTypes/commonInventoryTypes.ts +++ b/src/types/inventoryTypes/commonInventoryTypes.ts @@ -1,5 +1,6 @@ import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { Types } from "mongoose"; +import { ICrewMembers, ICrewshipWeapon, ICustomization, IFlavourItem } from "@/src/types/inventoryTypes/inventoryTypes"; export interface IPolarity { Slot: number; @@ -113,6 +114,10 @@ export interface IEquipmentDatabase { Expiry?: IMongoDate; SkillTree?: string; ArchonCrystalUpgrades?: IArchonCrystalUpgrade[]; + Weapon?: ICrewshipWeapon; + Customization?: ICustomization; + RailjackImage?: IFlavourItem; + CrewMembers?: ICrewMembers; _id: Types.ObjectId; } diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 1367868f..8b62b7ae 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Document, Types } from "mongoose"; -import { IOid, IMongoDate } from "../commonTypes"; +import { IOid, IMongoDate } from "@/src/types/commonTypes"; import { ArtifactPolarity, IColor, @@ -25,6 +25,7 @@ export interface IInventoryDatabase | "BlessingCooldown" | "Ships" | "WeaponSkins" + | "KubrowPetEggs" > { accountOwnerId: Types.ObjectId; Created: Date; @@ -37,6 +38,7 @@ export interface IInventoryDatabase BlessingCooldown: Date; Ships: Types.ObjectId[]; WeaponSkins: IWeaponSkinDatabase[]; + KubrowPetEggs: IKubrowPetEgg[]; } export interface IInventoryResponseDocument extends IInventoryResponse, Document {} @@ -63,6 +65,7 @@ export interface ITypeCount { export const equipmentKeys = [ "Suits", + "MechSuits", "LongGuns", "Pistols", "Melee", @@ -74,7 +77,10 @@ export const equipmentKeys = [ "SpaceMelee", "Hoverboards", "OperatorAmps", - "MoaPets" + "MoaPets", + "KubrowPets", + "CrewShips", + "CrewShipHarnesses" ] as const; export type TEquipmentKey = (typeof equipmentKeys)[number]; @@ -111,6 +117,10 @@ export type TSolarMapRegion = export interface IPendingRecipeResponse extends Omit { CompletionDate: IMongoDate; } +export interface IKubrowPetEggResponse extends Omit { + ExpirationDate: IMongoDate; +} + export interface IInventoryResponse { Horses: IEquipmentDatabase[]; DrifterMelee: IEquipmentDatabase[]; @@ -181,7 +191,7 @@ export interface IInventoryResponse { TauntHistory?: ITaunt[]; StoryModeChoice: string; PeriodicMissionCompletions: IPeriodicMissionCompletionDatabase[]; - KubrowPetEggs: IKubrowPetEgg[]; + KubrowPetEggs: IKubrowPetEggResponse[]; LoreFragmentScans: ILoreFragmentScan[]; EquippedEmotes: string[]; PendingTrades: IPendingTrade[]; @@ -249,7 +259,7 @@ export interface IInventoryResponse { LastNemesisAllySpawnTime?: IMongoDate; Settings: ISettings; PersonalTechProjects: IPersonalTechProject[]; - CrewShips: ICrewShip[]; + CrewShips: IEquipmentDatabase[]; PlayerSkills: IPlayerSkills; CrewShipAmmo: IConsumable[]; CrewShipSalvagedWeaponSkins: ICrewShipSalvagedWeaponSkin[]; @@ -391,9 +401,13 @@ export enum InventorySlot { SUITS = "SuitBin", WEAPONS = "WeaponBin", SPACESUITS = "SpaceSuitBin", + SPACEWEAPON = "SpaceWeaponBin", MECHSUITS = "MechBin", PVE_LOADOUTS = "PveBonusLoadoutBin", - SENTINELS = "SentinelBin" + SENTINELS = "SentinelBin", + OPERATORAMP = "OperatorAmpBin", + CREWMEMBERS = "CrewMemberBin", + CREWSHIPSALVAGE = "CrewShipSalvageBin" } export interface ISlots { @@ -417,18 +431,6 @@ export interface ICrewShipWeapon { ItemId: IOid; } -export interface ICrewShip { - ItemType: string; - Configs: IItemConfig[]; - Weapon: ICrewshipWeapon; - Customization: ICustomization; - ItemName: string; - RailjackImage: IFlavourItem; - CrewMembers: ICrewMembers; - ItemId: IOid; - _id: Types.ObjectId; -} - export interface ICrewMembers { SLOT_A: ISlot; SLOT_B: ISlot; @@ -536,13 +538,9 @@ export interface IInvasionChainProgress { } export interface IKubrowPetEgg { - ItemType: KubrowPetEggItemType; - ExpirationDate: IMongoDate; - ItemId: IOid; -} - -export enum KubrowPetEggItemType { - LotusTypesGameKubrowPetEggsKubrowEgg = "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg" + ItemType: string; + ExpirationDate: Date; + ItemId?: IOid; } export interface IKubrowPetPrint { diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index 288bb99c..86b2c2e5 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -1,4 +1,4 @@ -import { IOid } from "./commonTypes"; +import { IOid } from "@/src/types/commonTypes"; import { ArtifactPolarity, IPolarity, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IBooster, @@ -13,8 +13,9 @@ import { ISeasonChallenge, TSolarMapRegion, TEquipmentKey, - IFusionTreasure -} from "./inventoryTypes/inventoryTypes"; + IFusionTreasure, + IKubrowPetEggResponse +} from "@/src/types/inventoryTypes/inventoryTypes"; export interface IArtifactsRequest { Upgrade: ICrewShipSalvagedWeaponSkin; @@ -58,6 +59,11 @@ export interface IMissionInventoryUpdateRequest { Hoverboards?: IEquipmentClient[]; OperatorAmps?: IEquipmentClient[]; MoaPets?: IEquipmentClient[]; + CrewShips?: IEquipmentClient[]; + CrewShipHarnesses?: IEquipmentClient[]; + MechSuits?: IEquipmentClient[]; + KubrowPets?: IEquipmentClient[]; + KubrowPetsEggs?: IKubrowPetEggResponse[]; FusionBundles?: ITypeCount[]; RawUpgrades?: IRawUpgrade[]; MiscItems?: IMiscItem[];