chore: slightly generalise auto-generation of vendor manifests #1611
@ -109,6 +109,12 @@ export class CRng {
|
|||||||
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
||||||
return getRewardAtPercentage(pool, this.random());
|
return getRewardAtPercentage(pool, this.random());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
churnSeed(its: number): void {
|
||||||
|
while (its--) {
|
||||||
|
this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
|
// Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
|
||||||
|
@ -3,9 +3,15 @@ import path from "path";
|
|||||||
import { repoDir } from "@/src/helpers/pathHelper";
|
import { repoDir } from "@/src/helpers/pathHelper";
|
||||||
import { CRng, mixSeeds } from "@/src/services/rngService";
|
import { CRng, mixSeeds } from "@/src/services/rngService";
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { IItemManifestPreprocessed, IRawVendorManifest, IVendorManifestPreprocessed } from "@/src/types/vendorTypes";
|
import {
|
||||||
|
IItemManifestPreprocessed,
|
||||||
|
IRawVendorManifest,
|
||||||
|
IVendorInfo,
|
||||||
|
IVendorManifestPreprocessed
|
||||||
|
} from "@/src/types/vendorTypes";
|
||||||
import { JSONParse } from "json-with-bigint";
|
import { JSONParse } from "json-with-bigint";
|
||||||
import { ExportVendors } from "warframe-public-export-plus";
|
import { ExportVendors } from "warframe-public-export-plus";
|
||||||
|
import { unixTimesInMs } from "../constants/timeConstants";
|
||||||
|
|
||||||
const getVendorManifestJson = (name: string): IRawVendorManifest => {
|
const getVendorManifestJson = (name: string): IRawVendorManifest => {
|
||||||
return JSONParse(fs.readFileSync(path.join(repoDir, `static/fixed_responses/getVendorInfo/${name}.json`), "utf-8"));
|
return JSONParse(fs.readFileSync(path.join(repoDir, `static/fixed_responses/getVendorInfo/${name}.json`), "utf-8"));
|
||||||
@ -44,14 +50,37 @@ const rawVendorManifests: IRawVendorManifest[] = [
|
|||||||
getVendorManifestJson("ZarimanCommisionsManifestArchimedean")
|
getVendorManifestJson("ZarimanCommisionsManifestArchimedean")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
|
||||||
|
cycleDuration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatableVendors: IGeneratableVendorInfo[] = [
|
||||||
|
{
|
||||||
|
_id: { $oid: "67dadc30e4b6e0e5979c8d84" },
|
||||||
|
TypeName: "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest",
|
||||||
|
PropertyTextHash: "77093DD05A8561A022DEC9A4B9BB4A56",
|
||||||
|
RandomSeedType: "VRST_WEAPON",
|
||||||
|
RequiredGoalTag: "",
|
||||||
|
WeaponUpgradeValueAttenuationExponent: 2.25,
|
||||||
|
cycleDuration: 4 * unixTimesInMs.day
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
|
||||||
|
// TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
|
||||||
|
// PropertyTextHash: "62B64A8065B7C0FA345895D4BC234621"
|
||||||
|
// }
|
||||||
|
];
|
||||||
|
|
||||||
export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPreprocessed | undefined => {
|
export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPreprocessed | 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 preprocessVendorManifest(vendorManifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeName == "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest") {
|
for (const vendorInfo of generatableVendors) {
|
||||||
return generateCodaWeaponVendorManifest();
|
if (vendorInfo.TypeName == typeName) {
|
||||||
|
return generateVendorManifest(vendorInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
@ -62,8 +91,10 @@ export const getVendorManifestByOid = (oid: string): IVendorManifestPreprocessed
|
|||||||
return preprocessVendorManifest(vendorManifest);
|
return preprocessVendorManifest(vendorManifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oid == "67dadc30e4b6e0e5979c8d84") {
|
for (const vendorInfo of generatableVendors) {
|
||||||
return generateCodaWeaponVendorManifest();
|
if (vendorInfo._id.$oid == oid) {
|
||||||
|
return generateVendorManifest(vendorInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
@ -103,41 +134,59 @@ const refreshExpiry = (expiry: IMongoDate): number => {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateCodaWeaponVendorManifest = (): IVendorManifestPreprocessed => {
|
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => {
|
||||||
const EPOCH = 1740960000 * 1000;
|
const EPOCH = 1740960000 * 1000; // Monday; aligns with coda weapons 8 day cycle.
|
||||||
const DUR = 4 * 86400 * 1000;
|
const manifest = ExportVendors[vendorInfo.TypeName];
|
||||||
const cycle = Math.trunc((Date.now() - EPOCH) / DUR);
|
let binThisCycle;
|
||||||
const cycleStart = EPOCH + cycle * DUR;
|
if (manifest.isOneBinPerCycle) {
|
||||||
const cycleEnd = cycleStart + DUR;
|
const cycleDuration = vendorInfo.cycleDuration!; // manifest.items[0].durationHours! * 3600_000;
|
||||||
const binThisCycle = cycle % 2; // isOneBinPerCycle
|
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
|
||||||
|
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
|
||||||
|
}
|
||||||
const items: IItemManifestPreprocessed[] = [];
|
const items: IItemManifestPreprocessed[] = [];
|
||||||
const manifest = ExportVendors["/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest"];
|
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
|
||||||
const rng = new CRng(cycle);
|
for (let i = 0; i != manifest.items.length; ++i) {
|
||||||
for (const rawItem of manifest.items) {
|
const rawItem = manifest.items[i];
|
||||||
if (rawItem.bin != binThisCycle) {
|
if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
items.push({
|
const cycleDuration = vendorInfo.cycleDuration!; // rawItem.durationHours! * 3600_000;
|
||||||
|
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
|
||||||
|
const cycleStart = EPOCH + cycleIndex * cycleDuration;
|
||||||
|
const cycleEnd = cycleStart + cycleDuration;
|
||||||
|
if (soonestOfferExpiry > cycleEnd) {
|
||||||
|
soonestOfferExpiry = cycleEnd;
|
||||||
|
}
|
||||||
|
const rng = new CRng(cycleIndex);
|
||||||
|
rng.churnSeed(i);
|
||||||
|
/*for (let j = -1; j != rawItem.duplicates; ++j)*/ {
|
||||||
|
const item: IItemManifestPreprocessed = {
|
||||||
StoreItem: rawItem.storeItem,
|
StoreItem: rawItem.storeItem,
|
||||||
ItemPrices: rawItem.itemPrices!.map(item => ({ ...item, ProductCategory: "MiscItems" })),
|
ItemPrices: rawItem.itemPrices!.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
|
||||||
Bin: "BIN_" + rawItem.bin,
|
Bin: "BIN_" + rawItem.bin,
|
||||||
QuantityMultiplier: 1,
|
QuantityMultiplier: 1,
|
||||||
Expiry: { $date: { $numberLong: cycleEnd.toString() } },
|
Expiry: { $date: { $numberLong: cycleEnd.toString() } },
|
||||||
AllowMultipurchase: false,
|
AllowMultipurchase: false,
|
||||||
LocTagRandSeed: (BigInt(rng.randomInt(0, 0xffffffff)) << 32n) | BigInt(rng.randomInt(0, 0xffffffff)),
|
Id: {
|
||||||
Id: { $oid: "67e9da12793a120d" + rng.randomInt(0, 0xffffffff).toString(16).padStart(8, "0") }
|
$oid:
|
||||||
});
|
i.toString(16).padStart(8, "0") +
|
||||||
|
vendorInfo._id.$oid.substring(8, 16) +
|
||||||
|
rng.randomInt(0, 0xffffffff).toString(16).padStart(8, "0")
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if (vendorInfo.RandomSeedType) {
|
||||||
|
item.LocTagRandSeed =
|
||||||
|
(BigInt(rng.randomInt(0, 0xffffffff)) << 32n) | BigInt(rng.randomInt(0, 0xffffffff));
|
||||||
|
}
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete vendorInfo.cycleDuration;
|
||||||
return {
|
return {
|
||||||
VendorInfo: {
|
VendorInfo: {
|
||||||
_id: { $oid: "67dadc30e4b6e0e5979c8d84" },
|
...vendorInfo,
|
||||||
TypeName: "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest",
|
|
||||||
ItemManifest: items,
|
ItemManifest: items,
|
||||||
PropertyTextHash: "77093DD05A8561A022DEC9A4B9BB4A56",
|
Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
|
||||||
RandomSeedType: "VRST_WEAPON",
|
|
||||||
RequiredGoalTag: "",
|
|
||||||
WeaponUpgradeValueAttenuationExponent: 2.25,
|
|
||||||
Expiry: { $date: { $numberLong: cycleEnd.toString() } }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { IMongoDate, IOid } from "./commonTypes";
|
import { IMongoDate, IOid } from "./commonTypes";
|
||||||
|
|
||||||
interface IItemPrice {
|
export interface IItemPrice {
|
||||||
ItemType: string | string[]; // If string[], preprocessing will use RNG to pick one for the current period.
|
ItemType: string | string[]; // If string[], preprocessing will use RNG to pick one for the current period.
|
||||||
ItemCount: number;
|
ItemCount: number;
|
||||||
ProductCategory: string;
|
ProductCategory: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IItemPricePreprocessed extends Omit<IItemPrice, "ItemType"> {
|
export interface IItemPricePreprocessed extends Omit<IItemPrice, "ItemType"> {
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IItemManifest {
|
export interface IItemManifest {
|
||||||
StoreItem: string;
|
StoreItem: string;
|
||||||
ItemPrices?: IItemPrice[];
|
ItemPrices?: IItemPrice[];
|
||||||
Bin: string;
|
Bin: string;
|
||||||
@ -27,7 +27,7 @@ export interface IItemManifestPreprocessed extends Omit<IItemManifest, "ItemPric
|
|||||||
ItemPrices?: IItemPricePreprocessed[];
|
ItemPrices?: IItemPricePreprocessed[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVendorInfo {
|
export interface IVendorInfo {
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
TypeName: string;
|
TypeName: string;
|
||||||
ItemManifest: IItemManifest[];
|
ItemManifest: IItemManifest[];
|
||||||
@ -38,7 +38,7 @@ interface IVendorInfo {
|
|||||||
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
|
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVendorInfoPreprocessed extends Omit<IVendorInfo, "ItemManifest"> {
|
export interface IVendorInfoPreprocessed extends Omit<IVendorInfo, "ItemManifest"> {
|
||||||
ItemManifest: IItemManifestPreprocessed[];
|
ItemManifest: IItemManifestPreprocessed[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user