From 6691d4e402eeb5c033b08e741e74e07f2c0eb63a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:49:58 -0700 Subject: [PATCH] feat: autogenerate steel path honors vendor (#2187) No more "preprocessing" needed now. Some good progress for #1225, I'd say. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2187 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 +- package.json | 2 +- src/services/serversideVendorsService.ts | 143 +++-- .../TeshinHardModeVendorManifest.json | 603 ------------------ 4 files changed, 79 insertions(+), 677 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json diff --git a/package-lock.json b/package-lock.json index ceccfce4..5d1f4a36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", - "warframe-public-export-plus": "^0.5.67", + "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -3814,9 +3814,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.67", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.67.tgz", - "integrity": "sha512-LsnZD2E5PTA+5MK9kDGvM/hFDtg8sb0EwQ4hKH5ILqrSgz30a9W8785v77RSsL1AEVF8dfb/lZcSTCJq1DZHzQ==" + "version": "0.5.68", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.68.tgz", + "integrity": "sha512-KMmwCVeQ4k+EN73UZqxnM+qQdPsST8geWoJCP7US5LT6JcRxa8ptmqYXwCzaLtckBLZyVbamsxKZAxPPJckxsA==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index eaff654a..b8d00e39 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", - "warframe-public-export-plus": "^0.5.67", + "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 295759b2..1c947197 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -3,7 +3,6 @@ import { isDev } from "@/src/helpers/pathHelper"; import { catBreadHash } from "@/src/helpers/stringHelpers"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { mixSeeds, SRng } from "@/src/services/rngService"; -import { IMongoDate } from "@/src/types/commonTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { logger } from "@/src/utils/logger"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; @@ -25,7 +24,6 @@ import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json"; -import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json"; import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; const rawVendorManifests: IVendorManifest[] = [ @@ -46,7 +44,6 @@ const rawVendorManifests: IVendorManifest[] = [ OstronPetVendorManifest, SolarisDebtTokenVendorRepossessionsManifest, Temple1999VendorManifest, - TeshinHardModeVendorManifest, // uses preprocessing ZarimanCommisionsManifestArchimedean ]; @@ -87,12 +84,16 @@ const gcd = (a: number, b: number): number => { const getCycleDuration = (manifest: IVendor): number => { let dur = 0; for (const item of manifest.items) { - if (typeof item.durationHours != "number") { + if (item.alwaysOffered) { + continue; + } + const durationHours = item.rotatedWeekly ? 168 : item.durationHours; + if (typeof durationHours != "number") { dur = 1; break; } - if (dur != item.durationHours) { - dur = gcd(dur, item.durationHours); + if (dur != durationHours) { + dur = gcd(dur, durationHours); } } return dur * unixTimesInMs.hour; @@ -101,7 +102,7 @@ const getCycleDuration = (manifest: IVendor): number => { export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { for (const vendorManifest of rawVendorManifests) { if (vendorManifest.VendorInfo.TypeName == typeName) { - return preprocessVendorManifest(vendorManifest); + return vendorManifest; } } for (const vendorInfo of generatableVendors) { @@ -124,7 +125,7 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => { for (const vendorManifest of rawVendorManifests) { if (vendorManifest.VendorInfo._id.$oid == oid) { - return preprocessVendorManifest(vendorManifest); + return vendorManifest; } } for (const vendorInfo of generatableVendors) { @@ -183,30 +184,6 @@ export const applyStandingToVendorManifest = ( }; }; -const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => { - if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) { - const manifest = structuredClone(originalManifest); - const info = manifest.VendorInfo; - refreshExpiry(info.Expiry); - for (const offer of info.ItemManifest) { - refreshExpiry(offer.Expiry); - } - return manifest; - } - return originalManifest; -}; - -const refreshExpiry = (expiry: IMongoDate): void => { - const period = parseInt(expiry.$date.$numberLong); - if (Date.now() >= period) { - const epoch = 1734307200_000; // Monday (for weekly schedules) - const iteration = Math.trunc((Date.now() - epoch) / period); - const start = epoch + iteration * period; - const end = start + period; - expiry.$date.$numberLong = end.toString(); - } -}; - const toRange = (value: IRange | number): IRange => { if (typeof value == "number") { return { minValue: value, maxValue: value }; @@ -230,6 +207,18 @@ const getCycleDurationRange = (manifest: IVendor): IRange | undefined => { return res.maxValue != 0 ? res : undefined; }; +type TOfferId = string; + +const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => { + if ("storeItem" in offer) { + // IVendorOffer + return offer.storeItem + "x" + offer.quantity; + } else { + // IItemManifest + return offer.StoreItem + "x" + offer.QuantityMultiplier; + } +}; + const vendorManifestCache: Record = {}; const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => { @@ -270,7 +259,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const offersToAdd: IVendorOffer[] = []; if (!manifest.isOneBinPerCycle) { - const remainingItemCapacity: Record = {}; + // Compute vendor requirements, subtracting existing offers + const remainingItemCapacity: Record = {}; const missingItemsPerBin: Record = {}; let numOffersThatNeedToMatchABin = 0; if (manifest.numItemsPerBin) { @@ -280,56 +270,59 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } } for (const item of manifest.items) { - remainingItemCapacity[item.storeItem] = 1 + item.duplicates; + remainingItemCapacity[getOfferId(item)] = 1 + item.duplicates; } for (const offer of info.ItemManifest) { - remainingItemCapacity[offer.StoreItem] -= 1; + remainingItemCapacity[getOfferId(offer)] -= 1; const bin = parseInt(offer.Bin.substring(4)); if (missingItemsPerBin[bin]) { missingItemsPerBin[bin] -= 1; numOffersThatNeedToMatchABin -= 1; } } - if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) { - const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); + + // Add permanent offers + let numUncountedOffers = 0; + let offset = 0; + for (const item of manifest.items) { + if (item.alwaysOffered || item.rotatedWeekly) { + ++numUncountedOffers; + const id = getOfferId(item); + if (remainingItemCapacity[id] != 0) { + remainingItemCapacity[id] -= 1; + offersToAdd.push(item); + ++offset; + } + } + } + + // Add counted offers + if (manifest.numItems) { + const useRng = manifest.numItems.minValue != manifest.numItems.maxValue; + const numItemsTarget = + numUncountedOffers + + (useRng + ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) + : manifest.numItems.minValue); + let i = 0; while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - // TODO: Consider item probability weightings - const item = rng.randomElement(manifest.items)!; + const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; if ( - remainingItemCapacity[item.storeItem] != 0 && + !item.alwaysOffered && + remainingItemCapacity[getOfferId(item)] != 0 && (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) ) { - remainingItemCapacity[item.storeItem] -= 1; + remainingItemCapacity[getOfferId(item)] -= 1; if (missingItemsPerBin[item.bin]) { missingItemsPerBin[item.bin] -= 1; numOffersThatNeedToMatchABin -= 1; } - offersToAdd.push(item); + offersToAdd.splice(offset, 0, item); + } + if (i == manifest.items.length) { + i = 0; } } - } else { - for (const item of manifest.items) { - if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) { - remainingItemCapacity[item.storeItem] -= 1; - offersToAdd.push(item); - } - } - for (const e of Object.entries(remainingItemCapacity)) { - const item = manifest.items.find(x => x.storeItem == e[0])!; - if (!item.alwaysOffered) { - while (e[1] != 0) { - e[1] -= 1; - offersToAdd.push(item); - } - } - } - for (const item of manifest.items) { - if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) { - remainingItemCapacity[item.storeItem] -= 1; - offersToAdd.push(item); - } - } - offersToAdd.reverse(); } } else { const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. @@ -342,16 +335,21 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const cycleStart = cycleOffset + cycleIndex * cycleDuration; for (const rawItem of offersToAdd) { const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration); - const expiry = - cycleStart + - rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour; + const expiry = rawItem.alwaysOffered + ? 2051240400_000 + : cycleStart + + (rawItem.rotatedWeekly + ? unixTimesInMs.week + : rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour); const item: IItemManifest = { StoreItem: rawItem.storeItem, ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })), Bin: "BIN_" + rawItem.bin, QuantityMultiplier: rawItem.quantity, Expiry: { $date: { $numberLong: expiry.toString() } }, - AllowMultipurchase: false, + PurchaseQuantityLimit: rawItem.purchaseLimit, + RotatedWeekly: rawItem.rotatedWeekly, + AllowMultipurchase: rawItem.purchaseLimit !== 1, Id: { $oid: ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + @@ -422,6 +420,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani }; if (isDev) { + if ( + getCycleDuration(ExportVendors["/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest"]) != + unixTimesInMs.week + ) { + logger.warn(`getCycleDuration self test failed`); + } + const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")! .VendorInfo.ItemManifest; if ( diff --git a/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json b/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json deleted file mode 100644 index 7934f0a3..00000000 --- a/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json +++ /dev/null @@ -1,603 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "63ed01efbdaa38891767bac9" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinArmsBlueprint", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9947" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinBodyBlueprint", - "ItemPrices": [ - { - "ItemCount": 25, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9948" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinHeadBlueprint", - "ItemPrices": [ - { - "ItemCount": 20, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9949" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinLegsBlueprint", - "ItemPrices": [ - { - "ItemCount": 25, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/FormaStanceBlueprint", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Effects/OrbsEphemera", - "ItemPrices": [ - { - "ItemCount": 3, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Effects/TatsuSkullEphemera", - "ItemPrices": [ - { - "ItemCount": 85, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawShotgunRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9950" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/UmbraFormaBlueprint", - "ItemPrices": [ - { - "ItemCount": 150, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9951" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 55, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 50000, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9952" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawModularPistolRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9953" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 3, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9954" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawModularMeleeRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9955" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/EvergreenLoginRewardFusionBundle", - "ItemPrices": [ - { - "ItemCount": 150, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9956" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9957" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponRecoilReductionMod", - "ItemPrices": [ - { - "ItemCount": 35, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9958" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/TeshinBobbleHead", - "ItemPrices": [ - { - "ItemCount": 35, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9959" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageGaussVED", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageGrendelVED", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageProteaAction", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/TeaSet", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageXakuAction", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/RivenIdentifier", - "ItemPrices": [ - { - "ItemCount": 20, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e995f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RandomSyndicateProjectionPack", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 25, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e997c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 10000, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 25, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e997d" - } - } - ], - "PropertyTextHash": "0A0F20AFA748FBEE490510DBF5A33A0D", - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - } - } -}