From 7c47c9f1e42cff84156faa92c1aab86e35a7c2e8 Mon Sep 17 00:00:00 2001 From: Sainan Date: Thu, 20 Jun 2024 13:29:48 +0200 Subject: [PATCH 1/3] fix: not being able to talk to Nakak in Cetus (#336) --- .../api/getVendorInfoController.ts | 7 + src/routes/api.ts | 2 + static/fixed_responses/getVendorInfo.json | 301 ++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 src/controllers/api/getVendorInfoController.ts create mode 100644 static/fixed_responses/getVendorInfo.json diff --git a/src/controllers/api/getVendorInfoController.ts b/src/controllers/api/getVendorInfoController.ts new file mode 100644 index 00000000..cd442318 --- /dev/null +++ b/src/controllers/api/getVendorInfoController.ts @@ -0,0 +1,7 @@ +import { RequestHandler } from "express"; +import getVendorInfoResponse from "@/static/fixed_responses/getVendorInfo.json"; + +export const getVendorInfoController: RequestHandler = (req, res) => { + console.assert(req.query.vendor == "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest"); + res.json(getVendorInfoResponse); +}; diff --git a/src/routes/api.ts b/src/routes/api.ts index bf7440de..8bdddd78 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -21,6 +21,7 @@ import { getGuildLogController } from "../controllers/api/getGuildLogController" import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController"; import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController"; import { getShipController } from "@/src/controllers/api/getShipController"; +import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController"; import { guildTechController } from "../controllers/api/guildTechController"; import { hostSessionController } from "@/src/controllers/api/hostSessionController"; import { hubController } from "@/src/controllers/api/hubController"; @@ -77,6 +78,7 @@ apiRouter.get("/getGuildLog.php", getGuildLogController); apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController); apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController); apiRouter.get("/getShip.php", getShipController); +apiRouter.get("/getVendorInfo.php", getVendorInfoController); apiRouter.get("/hub", hubController); apiRouter.get("/hubInstances", hubInstancesController); apiRouter.get("/inbox.php", inboxController); diff --git a/static/fixed_responses/getVendorInfo.json b/static/fixed_responses/getVendorInfo.json new file mode 100644 index 00000000..85aa7eac --- /dev/null +++ b/static/fixed_responses/getVendorInfo.json @@ -0,0 +1,301 @@ +{ + "VendorInfo": { + "_id": { + "$oid": "598a090d9a4a313746fd1f24" + }, + "TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", + "ItemManifest": [ + { + "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask", + "ItemPrices": [ + { + "ItemCount": 1, + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "63ed01ef4c37f93d0b797674" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper", + "ItemPrices": [ + { + "ItemCount": 2, + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", + "ItemCount": 10, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem", + "ItemCount": 10, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", + "ItemCount": 32, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "63ed01ef4c37f93d0b797675" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium", + "ItemPrices": [ + { + "ItemCount": 4, + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemBCutAItem", + "ItemCount": 24, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem", + "ItemCount": 18, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", + "ItemCount": 27, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_1", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "63ed01ef4c37f93d0b797676" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge", + "ItemPrices": [ + { + "ItemCount": 6, + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemACutAItem", + "ItemCount": 35, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishAPartItem", + "ItemCount": 95, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem", + "ItemCount": 60, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "63ed01ef4c37f93d0b797677" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareBlueBlueprint", + "ItemPrices": [ + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", + "ItemCount": 10, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem", + "ItemCount": 10, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "6651291214e90115b91b50a1" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareRedBlueprint", + "ItemPrices": [ + { + "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", + "ItemCount": 37, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem", + "ItemCount": 7, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "6651291214e90115b91b50a2" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/VoltMask", + "ItemPrices": [ + { + "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreBAlloyBItem", + "ItemCount": 34, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem", + "ItemCount": 17, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "6651291214e90115b91b50a3" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/MagMask", + "ItemPrices": [ + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", + "ItemCount": 16, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/ForestRodentPartItem", + "ItemCount": 5, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "6651291214e90115b91b50a4" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/ExcaliburMask", + "ItemPrices": [ + { + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/BirdOfPreyPartItem", + "ItemCount": 5, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem", + "ItemCount": 20, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "6651291214e90115b91b50a5" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/GrineerMask", + "ItemPrices": [ + { + "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", + "ItemCount": 20, + "ProductCategory": "MiscItems" + }, + { + "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", + "ItemCount": 31, + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_1", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "6651291214e90115b91b50a6" + } + } + ], + "PropertyTextHash": "6AACA376DA34B35B5C16F1B40DBC017D", + "Expiry": { + "$date": { + "$numberLong": "9999999000000" + } + } + } +} -- 2.47.2 From 97eedf8c5bdd75edcc05d6f8f5e6f7b56fc76dd2 Mon Sep 17 00:00:00 2001 From: Sainan Date: Thu, 20 Jun 2024 13:30:49 +0200 Subject: [PATCH 2/3] handle duviri resources in addItem --- src/services/inventoryService.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index b29a5241..049bb54a 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -26,7 +26,7 @@ import { logger } from "@/src/utils/logger"; import { WeaponTypeInternal, getWeaponType, getExalted } from "@/src/services/itemDataService"; import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes"; import { IEquipmentClient } from "../types/inventoryTypes/commonInventoryTypes"; -import { ExportRecipes } from "warframe-public-export-plus"; +import { ExportRecipes, ExportResources } from "warframe-public-export-plus"; export const createInventory = async ( accountOwnerId: Types.ObjectId, @@ -88,6 +88,22 @@ 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 + } + }; + } // Path-based duck typing switch (typeName.substr(1).split("/")[1]) { -- 2.47.2 From 4a7ce6afd9451f537af4e775c3447fd817729a62 Mon Sep 17 00:00:00 2001 From: Sainan Date: Thu, 20 Jun 2024 14:00:07 +0200 Subject: [PATCH 3/3] fix not properly combining inventory changes --- src/services/inventoryService.ts | 4 +- src/services/purchaseService.ts | 66 +++++++++++++++++++++++++------- src/types/purchaseTypes.ts | 20 +--------- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 049bb54a..efd291cc 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -2,7 +2,7 @@ import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import new_inventory from "@/static/fixed_responses/postTutorialInventory.json"; import { config } from "@/src/services/configService"; import { Types } from "mongoose"; -import { SlotNames } from "@/src/types/purchaseTypes"; +import { SlotNames, IInventoryChanges } from "@/src/types/purchaseTypes"; import { IChallengeProgress, IConsumable, @@ -70,7 +70,7 @@ export const addItem = async ( accountId: string, typeName: string, quantity: number = 1 -): Promise<{ InventoryChanges: object }> => { +): Promise<{ InventoryChanges: IInventoryChanges }> => { // Strict typing if (typeName in ExportRecipes) { const inventory = await getInventory(accountId); diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 0f96055c..9fb4a675 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -1,7 +1,7 @@ import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers"; import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService"; -import { IPurchaseRequest, SlotPurchase } from "@/src/types/purchaseTypes"; +import { IPurchaseRequest, SlotPurchase, IInventoryChanges, IBinChanges } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; import { ExportBundles, TRarity } from "warframe-public-export-plus"; @@ -46,12 +46,37 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI return purchaseResponse; }; +const addInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => { + for (const key in delta) { + if (!(key in InventoryChanges)) { + InventoryChanges[key] = delta[key]; + } else if (Array.isArray(delta[key])) { + const left = InventoryChanges[key] as object[]; + const right = delta[key] as object[]; + for (const item of right) { + left.push(item); + } + } else { + console.assert(key.substring(-3) == "Bin"); + const left = InventoryChanges[key] as IBinChanges; + const right = delta[key] as IBinChanges; + left.count += right.count; + left.platinum += right.platinum; + left.Slots += right.Slots; + if (right.Extra) { + left.Extra ??= 0; + left.Extra += right.Extra; + } + } + } +}; + const handleStoreItemAcquisition = async ( storeItemName: string, accountId: string, quantity: number, durability: TRarity -): Promise<{ InventoryChanges: object }> => { +): Promise<{ InventoryChanges: IInventoryChanges }> => { let purchaseResponse = { InventoryChanges: {} }; @@ -60,15 +85,17 @@ const handleStoreItemAcquisition = async ( const bundle = ExportBundles[storeItemName]; logger.debug("acquiring bundle", bundle); for (const component of bundle.components) { - purchaseResponse = { - ...purchaseResponse, - ...(await handleStoreItemAcquisition( - component.typeName, - accountId, - component.purchaseQuantity, - component.durability - )) - }; + addInventoryChanges( + purchaseResponse.InventoryChanges, + ( + await handleStoreItemAcquisition( + component.typeName, + accountId, + component.purchaseQuantity, + component.durability + ) + ).InventoryChanges + ); } } else { const storeCategory = getStoreItemCategory(storeItemName); @@ -106,7 +133,10 @@ export const slotPurchaseNameToSlotName: SlotPurchase = { // // new slot above base = extra + 1 and slots +1 // // new frame = slots -1 // // number of frames = extra - slots + 2 -const handleSlotPurchase = async (slotPurchaseNameFull: string, accountId: string) => { +const handleSlotPurchase = async ( + slotPurchaseNameFull: string, + accountId: string +): Promise<{ InventoryChanges: IInventoryChanges }> => { logger.debug(`slot name ${slotPurchaseNameFull}`); const slotPurchaseName = parseSlotPurchaseName( slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1) @@ -133,7 +163,11 @@ const handleSlotPurchase = async (slotPurchaseNameFull: string, accountId: strin }; //TODO: change to getInventory, apply changes then save at the end -const handleTypesPurchase = async (typesName: string, accountId: string, quantity: number) => { +const handleTypesPurchase = async ( + typesName: string, + accountId: string, + quantity: number +): Promise<{ InventoryChanges: IInventoryChanges }> => { const typeCategory = getStoreItemTypesCategory(typesName); logger.debug(`type category ${typeCategory}`); switch (typeCategory) { @@ -158,7 +192,11 @@ const boosterDuration: Record = { LEGENDARY: 90 * 86400 }; -const handleBoostersPurchase = async (boosterStoreName: string, accountId: string, durability: TRarity) => { +const handleBoostersPurchase = async ( + boosterStoreName: string, + accountId: string, + durability: TRarity +): Promise<{ InventoryChanges: IInventoryChanges }> => { const ItemType = boosterStoreName.replace("StoreItem", ""); if (!boosterCollection.find(x => x == ItemType)) { logger.error(`unknown booster type: ${ItemType}`); diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index db143470..0d04114e 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -1,6 +1,3 @@ -import { IFlavourItem } from "@/src/types/inventoryTypes/inventoryTypes"; -import { IEquipmentClient } from "./inventoryTypes/commonInventoryTypes"; - export interface IPurchaseRequest { PurchaseParams: IPurchaseParams; buildLabel: string; @@ -17,22 +14,7 @@ export interface IPurchaseParams { ExpectedPrice: number; } -export interface IPurchaseResponse { - InventoryChanges: { - SuitBin?: IBinChanges; - WeaponBin?: IBinChanges; - MechBin?: IBinChanges; - MechSuits?: IEquipmentClient[]; - Suits?: IEquipmentClient[]; - LongGuns?: IEquipmentClient[]; - Pistols?: IEquipmentClient[]; - Melee?: IEquipmentClient[]; - PremiumCredits?: number; - PremiumCreditsFree?: number; - RegularCredits?: number; - FlavourItems?: IFlavourItem[]; - }; -} +export type IInventoryChanges = Record; export type IBinChanges = { count: number; -- 2.47.2