feat: autogenerate steel path honors vendor (#2187)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled

No more "preprocessing" needed now. Some good progress for #1225, I'd say.

Reviewed-on: #2187
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-06-18 05:49:58 -07:00 committed by Sainan
parent 3dcd2663d3
commit 6691d4e402
4 changed files with 79 additions and 677 deletions

8
package-lock.json generated
View File

@ -18,7 +18,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.67", "warframe-public-export-plus": "^0.5.68",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@ -3814,9 +3814,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.67", "version": "0.5.68",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.67.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.68.tgz",
"integrity": "sha512-LsnZD2E5PTA+5MK9kDGvM/hFDtg8sb0EwQ4hKH5ILqrSgz30a9W8785v77RSsL1AEVF8dfb/lZcSTCJq1DZHzQ==" "integrity": "sha512-KMmwCVeQ4k+EN73UZqxnM+qQdPsST8geWoJCP7US5LT6JcRxa8ptmqYXwCzaLtckBLZyVbamsxKZAxPPJckxsA=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -25,7 +25,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.67", "warframe-public-export-plus": "^0.5.68",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"

View File

@ -3,7 +3,6 @@ import { isDev } from "@/src/helpers/pathHelper";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash } from "@/src/helpers/stringHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { mixSeeds, SRng } from "@/src/services/rngService"; import { mixSeeds, SRng } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; 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 OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.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"; import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
const rawVendorManifests: IVendorManifest[] = [ const rawVendorManifests: IVendorManifest[] = [
@ -46,7 +44,6 @@ const rawVendorManifests: IVendorManifest[] = [
OstronPetVendorManifest, OstronPetVendorManifest,
SolarisDebtTokenVendorRepossessionsManifest, SolarisDebtTokenVendorRepossessionsManifest,
Temple1999VendorManifest, Temple1999VendorManifest,
TeshinHardModeVendorManifest, // uses preprocessing
ZarimanCommisionsManifestArchimedean ZarimanCommisionsManifestArchimedean
]; ];
@ -87,12 +84,16 @@ const gcd = (a: number, b: number): number => {
const getCycleDuration = (manifest: IVendor): number => { const getCycleDuration = (manifest: IVendor): number => {
let dur = 0; let dur = 0;
for (const item of manifest.items) { 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; dur = 1;
break; break;
} }
if (dur != item.durationHours) { if (dur != durationHours) {
dur = gcd(dur, item.durationHours); dur = gcd(dur, durationHours);
} }
} }
return dur * unixTimesInMs.hour; return dur * unixTimesInMs.hour;
@ -101,7 +102,7 @@ const getCycleDuration = (manifest: IVendor): number => {
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo.TypeName == typeName) { if (vendorManifest.VendorInfo.TypeName == typeName) {
return preprocessVendorManifest(vendorManifest); return vendorManifest;
} }
} }
for (const vendorInfo of generatableVendors) { for (const vendorInfo of generatableVendors) {
@ -124,7 +125,7 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => { export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo._id.$oid == oid) { if (vendorManifest.VendorInfo._id.$oid == oid) {
return preprocessVendorManifest(vendorManifest); return vendorManifest;
} }
} }
for (const vendorInfo of generatableVendors) { 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 => { const toRange = (value: IRange | number): IRange => {
if (typeof value == "number") { if (typeof value == "number") {
return { minValue: value, maxValue: value }; return { minValue: value, maxValue: value };
@ -230,6 +207,18 @@ const getCycleDurationRange = (manifest: IVendor): IRange | undefined => {
return res.maxValue != 0 ? res : 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<string, IVendorManifest> = {}; const vendorManifestCache: Record<string, IVendorManifest> = {};
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => { const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
@ -270,7 +259,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
const offersToAdd: IVendorOffer[] = []; const offersToAdd: IVendorOffer[] = [];
if (!manifest.isOneBinPerCycle) { if (!manifest.isOneBinPerCycle) {
const remainingItemCapacity: Record<string, number> = {}; // Compute vendor requirements, subtracting existing offers
const remainingItemCapacity: Record<TOfferId, number> = {};
const missingItemsPerBin: Record<number, number> = {}; const missingItemsPerBin: Record<number, number> = {};
let numOffersThatNeedToMatchABin = 0; let numOffersThatNeedToMatchABin = 0;
if (manifest.numItemsPerBin) { if (manifest.numItemsPerBin) {
@ -280,56 +270,59 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
} }
} }
for (const item of manifest.items) { for (const item of manifest.items) {
remainingItemCapacity[item.storeItem] = 1 + item.duplicates; remainingItemCapacity[getOfferId(item)] = 1 + item.duplicates;
} }
for (const offer of info.ItemManifest) { for (const offer of info.ItemManifest) {
remainingItemCapacity[offer.StoreItem] -= 1; remainingItemCapacity[getOfferId(offer)] -= 1;
const bin = parseInt(offer.Bin.substring(4)); const bin = parseInt(offer.Bin.substring(4));
if (missingItemsPerBin[bin]) { if (missingItemsPerBin[bin]) {
missingItemsPerBin[bin] -= 1; missingItemsPerBin[bin] -= 1;
numOffersThatNeedToMatchABin -= 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) { while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider item probability weightings const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++];
const item = rng.randomElement(manifest.items)!;
if ( if (
remainingItemCapacity[item.storeItem] != 0 && !item.alwaysOffered &&
remainingItemCapacity[getOfferId(item)] != 0 &&
(numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin])
) { ) {
remainingItemCapacity[item.storeItem] -= 1; remainingItemCapacity[getOfferId(item)] -= 1;
if (missingItemsPerBin[item.bin]) { if (missingItemsPerBin[item.bin]) {
missingItemsPerBin[item.bin] -= 1; missingItemsPerBin[item.bin] -= 1;
numOffersThatNeedToMatchABin -= 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 { } else {
const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. 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; const cycleStart = cycleOffset + cycleIndex * cycleDuration;
for (const rawItem of offersToAdd) { for (const rawItem of offersToAdd) {
const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration); const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
const expiry = const expiry = rawItem.alwaysOffered
cycleStart + ? 2051240400_000
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour; : cycleStart +
(rawItem.rotatedWeekly
? unixTimesInMs.week
: rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour);
const item: IItemManifest = { const item: IItemManifest = {
StoreItem: rawItem.storeItem, StoreItem: rawItem.storeItem,
ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })), ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
Bin: "BIN_" + rawItem.bin, Bin: "BIN_" + rawItem.bin,
QuantityMultiplier: rawItem.quantity, QuantityMultiplier: rawItem.quantity,
Expiry: { $date: { $numberLong: expiry.toString() } }, Expiry: { $date: { $numberLong: expiry.toString() } },
AllowMultipurchase: false, PurchaseQuantityLimit: rawItem.purchaseLimit,
RotatedWeekly: rawItem.rotatedWeekly,
AllowMultipurchase: rawItem.purchaseLimit !== 1,
Id: { Id: {
$oid: $oid:
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
@ -422,6 +420,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
}; };
if (isDev) { 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")! const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
.VendorInfo.ItemManifest; .VendorInfo.ItemManifest;
if ( if (

View File

@ -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"
}
}
}
}