From 5426fe0874ed8d53d3bdd33e68f7618a15334e1a Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 23 Mar 2025 01:11:08 +0100 Subject: [PATCH 1/6] feat: acquisition of peely pix + free pack for first visit --- .../api/genericUpdateController.ts | 3 +- src/services/inventoryService.ts | 23 ++- src/services/purchaseService.ts | 4 +- src/services/serversideVendorsService.ts | 2 + src/types/genericUpdate.ts | 7 + .../Nova1999ConquestShopManifest.json | 188 ++++++++++++++++++ 6 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json diff --git a/src/controllers/api/genericUpdateController.ts b/src/controllers/api/genericUpdateController.ts index 79b6ba44..e5f0b593 100644 --- a/src/controllers/api/genericUpdateController.ts +++ b/src/controllers/api/genericUpdateController.ts @@ -10,8 +10,7 @@ import { IGenericUpdate } from "@/src/types/genericUpdate"; const genericUpdateController: RequestHandler = async (request, response) => { const accountId = await getAccountIdForRequest(request); const update = getJSONfromString(String(request.body)); - await updateGeneric(update, accountId); - response.json(update); + response.json(await updateGeneric(update, accountId)); }; export { genericUpdateController }; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 0b435240..88a6156d 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -28,7 +28,7 @@ import { IUpgradeClient, ICrewShipWeaponClient } from "@/src/types/inventoryTypes/inventoryTypes"; -import { IGenericUpdate } from "../types/genericUpdate"; +import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate"; import { IMissionInventoryUpdateRequest, IThemeUpdateRequest, @@ -559,6 +559,7 @@ export const addItem = async ( switch (typeName.substr(1).split("/")[2]) { case "Mods": // Legendary Core case "CosmeticEnhancers": // Traumatic Peculiar + case "Stickers": { const changes = [ { @@ -876,7 +877,7 @@ export const updateStandingLimit = ( }; // TODO: AffiliationMods support (Nightwave). -export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise => { +export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise => { const inventory = await getInventory(accountId); // Make it an array for easier parsing. @@ -884,6 +885,19 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr data.NodeIntrosCompleted = [data.NodeIntrosCompleted]; } + const inventoryChanges: IInventoryChanges = {}; + for (const node of data.NodeIntrosCompleted) { + if (node == "KayaFirstVisitPack") { + inventoryChanges.MiscItems = [ + { + ItemType: "/Lotus/Types/Items/MiscItems/1999FixedStickersPack", + ItemCount: 1 + } + ]; + addMiscItems(inventory, inventoryChanges.MiscItems); + } + } + // Combine the two arrays into one. data.NodeIntrosCompleted = inventory.NodeIntrosCompleted.concat(data.NodeIntrosCompleted); @@ -892,6 +906,11 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr inventory.NodeIntrosCompleted = nodes; await inventory.save(); + + return { + MissionRewards: [], + InventoryChanges: inventoryChanges + }; }; export const updateTheme = async (data: IThemeUpdateRequest, accountId: string): Promise => { diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index ef7a97b7..a20019f7 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -51,10 +51,10 @@ export const handlePurchase = async ( logger.debug("purchase request", purchaseRequest); const prePurchaseInventoryChanges: IInventoryChanges = {}; - if (purchaseRequest.PurchaseParams.Source == 7) { + if (purchaseRequest.PurchaseParams.Source == 7 && purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) { const manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!); if (manifest) { - const ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson!) as { ItemId: string }) + const ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) as { ItemId: string }) .ItemId; const offer = manifest.VendorInfo.ItemManifest.find(x => x.Id.$oid == ItemId); if (!offer) { diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index cae48e6e..79e22ef3 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -19,6 +19,7 @@ import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorI import HubsPerrinSequenceWeaponVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsPerrinSequenceWeaponVendorManifest.json"; import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; +import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json"; @@ -69,6 +70,7 @@ const vendorManifests: IVendorManifest[] = [ HubsPerrinSequenceWeaponVendorManifest, HubsRailjackCrewMemberVendorManifest, MaskSalesmanManifest, + Nova1999ConquestShopManifest, OstronFishmongerVendorManifest, OstronPetVendorManifest, OstronProspectorVendorManifest, diff --git a/src/types/genericUpdate.ts b/src/types/genericUpdate.ts index fa231be9..93551b05 100644 --- a/src/types/genericUpdate.ts +++ b/src/types/genericUpdate.ts @@ -1,4 +1,11 @@ +import { IInventoryChanges } from "./purchaseTypes"; + export interface IGenericUpdate { NodeIntrosCompleted: string | string[]; // AffiliationMods: any[]; } + +export interface IUpdateNodeIntrosResponse { + MissionRewards: []; + InventoryChanges: IInventoryChanges; +} diff --git a/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json b/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json new file mode 100644 index 00000000..b2971efe --- /dev/null +++ b/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json @@ -0,0 +1,188 @@ +{ + "VendorInfo": { + "_id": { + "$oid": "67dadc30e4b6e0e5979c8d6a" + }, + "TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Nova1999ConquestShopManifest", + "ItemManifest": [ + { + "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedea", + "ItemPrices": [ + { + "ItemCount": 10, + "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "67db32b983b2ad79a9c1c18c" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFree", + "ItemPrices": [ + { + "ItemCount": 1, + "ItemType": "/Lotus/Types/Items/MiscItems/1999FreeStickersPack", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "67db32b983b2ad79a9c1c18d" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFixed", + "ItemPrices": [ + { + "ItemCount": 1, + "ItemType": "/Lotus/Types/Items/MiscItems/1999FixedStickersPack", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "67db32b983b2ad79a9c1c18e" + } + }, + { + "StoreItem": "/Lotus/Types/StoreItems/Packages/SyndicateVosforPack", + "ItemPrices": [ + { + "ItemCount": 6, + "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "67db32b983b2ad79a9c1c18f" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/StickerPictureFrame", + "ItemPrices": [ + { + "ItemCount": 10, + "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_0", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "AllowMultipurchase": true, + "Id": { + "$oid": "67db32b983b2ad79a9c1c190" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Utility/AbilityRadiationProcsCreateUniversalOrbsOnKill", + "ItemPrices": [ + { + "ItemCount": 5, + "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_1", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "PurchaseQuantityLimit": 1, + "AllowMultipurchase": false, + "Id": { + "$oid": "67db32b983b2ad79a9c1c191" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Offensive/AbilityHeatProcsGiveCritChance", + "ItemPrices": [ + { + "ItemCount": 5, + "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_1", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "PurchaseQuantityLimit": 1, + "AllowMultipurchase": false, + "Id": { + "$oid": "67db32b983b2ad79a9c1c192" + } + }, + { + "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Defensive/InvulnerabilityOnDeathOnMercyKill", + "ItemPrices": [ + { + "ItemCount": 5, + "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + "ProductCategory": "MiscItems" + } + ], + "Bin": "BIN_1", + "QuantityMultiplier": 1, + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + }, + "PurchaseQuantityLimit": 1, + "AllowMultipurchase": false, + "Id": { + "$oid": "67db32b983b2ad79a9c1c193" + } + } + ], + "PropertyTextHash": "CB7D0E807FD5E2BCD059195201D963B9", + "RequiredGoalTag": "", + "Expiry": { + "$date": { + "$numberLong": "2051240400000" + } + } + } +} \ No newline at end of file -- 2.47.2 From ad50f61ab6b40663e7456956dfd83a28543bb7ea Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 23 Mar 2025 01:16:27 +0100 Subject: [PATCH 2/6] fix not removing the temporary miscitem from inventory --- src/services/purchaseService.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index a20019f7..7e525168 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -51,12 +51,17 @@ export const handlePurchase = async ( logger.debug("purchase request", purchaseRequest); const prePurchaseInventoryChanges: IInventoryChanges = {}; - if (purchaseRequest.PurchaseParams.Source == 7 && purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) { + if (purchaseRequest.PurchaseParams.Source == 7) { const manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!); if (manifest) { - const ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) as { ItemId: string }) - .ItemId; - const offer = manifest.VendorInfo.ItemManifest.find(x => x.Id.$oid == ItemId); + let ItemId: string | undefined; + if (purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) { + ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) as { ItemId: string }) + .ItemId; + } + const offer = ItemId + ? manifest.VendorInfo.ItemManifest.find(x => x.Id.$oid == ItemId) + : manifest.VendorInfo.ItemManifest.find(x => x.StoreItem == purchaseRequest.PurchaseParams.StoreItem); if (!offer) { throw new Error(`unknown vendor offer: ${ItemId}`); } @@ -68,7 +73,7 @@ export const handlePurchase = async ( prePurchaseInventoryChanges ); } - if (!config.noVendorPurchaseLimits) { + if (!config.noVendorPurchaseLimits && ItemId) { inventory.RecentVendorPurchases ??= []; let vendorPurchases = inventory.RecentVendorPurchases.find( x => x.VendorType == manifest.VendorInfo.TypeName -- 2.47.2 From 88982efc901ef8639100d199d2cd91ad795694b1 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 23 Mar 2025 01:17:44 +0100 Subject: [PATCH 3/6] fix possibly concatting undefined --- src/services/purchaseService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 7e525168..773af41c 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -63,7 +63,7 @@ export const handlePurchase = async ( ? manifest.VendorInfo.ItemManifest.find(x => x.Id.$oid == ItemId) : manifest.VendorInfo.ItemManifest.find(x => x.StoreItem == purchaseRequest.PurchaseParams.StoreItem); if (!offer) { - throw new Error(`unknown vendor offer: ${ItemId}`); + throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`); } if (offer.ItemPrices) { handleItemPrices( -- 2.47.2 From 15e1586ae8780f5a5d4ed38f7d1729ee39d6054f Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 23 Mar 2025 01:23:08 +0100 Subject: [PATCH 4/6] don't randomise the free stickers pack --- src/services/purchaseService.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 773af41c..72e1701c 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -415,16 +415,26 @@ const handleBoosterPackPurchase = async ( "attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server." ); } - for (let i = 0; i != quantity; ++i) { - for (const weights of pack.rarityWeightsPerRoll) { - const result = getRandomWeightedRewardUc(pack.components, weights); - if (result) { - logger.debug(`booster pack rolled`, result); - purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};'; - combineInventoryChanges( - purchaseResponse.InventoryChanges, - (await addItem(inventory, result.Item, 1)).InventoryChanges - ); + if (typeName == "/Lotus/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFixed") { + for (const result of pack.components) { + purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};'; + combineInventoryChanges( + purchaseResponse.InventoryChanges, + (await addItem(inventory, result.Item, 1)).InventoryChanges + ); + } + } else { + for (let i = 0; i != quantity; ++i) { + for (const weights of pack.rarityWeightsPerRoll) { + const result = getRandomWeightedRewardUc(pack.components, weights); + if (result) { + logger.debug(`booster pack rolled`, result); + purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};'; + combineInventoryChanges( + purchaseResponse.InventoryChanges, + (await addItem(inventory, result.Item, 1)).InventoryChanges + ); + } } } } -- 2.47.2 From 9b110d528a55a58b513bdfd9c3a3d55cbf29b3bc Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 23 Mar 2025 01:33:08 +0100 Subject: [PATCH 5/6] Cap Peely Pix at 10 per ItemType, turn excess into Pix Chips --- src/services/inventoryService.ts | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 88a6156d..0d17a30d 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -559,7 +559,6 @@ export const addItem = async ( switch (typeName.substr(1).split("/")[2]) { case "Mods": // Legendary Core case "CosmeticEnhancers": // Traumatic Peculiar - case "Stickers": { const changes = [ { @@ -575,6 +574,39 @@ export const addItem = async ( }; } break; + + case "Stickers": + { + const entry = inventory.RawUpgrades.find(x => x.ItemType == typeName); + if (entry && entry.ItemCount >= 10) { + const miscItemChanges = [ + { + ItemType: "/Lotus/Types/Items/MiscItems/1999ConquestBucks", + ItemCount: 1 + } + ]; + addMiscItems(inventory, miscItemChanges); + return { + InventoryChanges: { + MiscItems: miscItemChanges + } + }; + } else { + const changes = [ + { + ItemType: typeName, + ItemCount: quantity + } + ]; + addMods(inventory, changes); + return { + InventoryChanges: { + RawUpgrades: changes + } + }; + } + } + break; } break; } -- 2.47.2 From dbd1875c09a613e10e706c8e824242c5a9dfacf4 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 23 Mar 2025 02:03:51 +0100 Subject: [PATCH 6/6] use inventory projection in updateGeneric --- src/services/inventoryService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 0d17a30d..cf48a47e 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -910,7 +910,7 @@ export const updateStandingLimit = ( // TODO: AffiliationMods support (Nightwave). export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise => { - const inventory = await getInventory(accountId); + const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems"); // Make it an array for easier parsing. if (typeof data.NodeIntrosCompleted === "string") { -- 2.47.2