XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

chore: slightly generalise auto-generation of vendor manifests (#1611)

was gonna use this for the iron wake vendor manifest but the order is all wrong so in that way preprocessing remains a more preferable approach Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1611 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

9472f855
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

3 个文件 +93 -38
Modified src/services/rngService.ts +6 -0
@@ -109,6 +109,12 @@ export class CRng {
109 109 randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
110 110 return getRewardAtPercentage(pool, this.random());
111 111 }
112
113 churnSeed(its: number): void {
114 while (its--) {
115 this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
116 }
117 }
112 118 }
113 119
114 120 // Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
Modified src/services/serversideVendorsService.ts +82 -33
@@ -3,9 +3,15 @@ import path from "path";
3 3 import { repoDir } from "@/src/helpers/pathHelper";
4 4 import { CRng, mixSeeds } from "@/src/services/rngService";
5 5 import { IMongoDate } from "@/src/types/commonTypes";
6 import { IItemManifestPreprocessed, IRawVendorManifest, IVendorManifestPreprocessed } from "@/src/types/vendorTypes";
6 import {
7 IItemManifestPreprocessed,
8 IRawVendorManifest,
9 IVendorInfo,
10 IVendorManifestPreprocessed
11 } from "@/src/types/vendorTypes";
7 12 import { JSONParse } from "json-with-bigint";
8 13 import { ExportVendors } from "warframe-public-export-plus";
14 import { unixTimesInMs } from "../constants/timeConstants";
9 15
10 16 const getVendorManifestJson = (name: string): IRawVendorManifest => {
11 17 return JSONParse(fs.readFileSync(path.join(repoDir, `static/fixed_responses/getVendorInfo/${name}.json`), "utf-8"));
@@ -44,14 +50,37 @@ const rawVendorManifests: IRawVendorManifest[] = [
44 50 getVendorManifestJson("ZarimanCommisionsManifestArchimedean")
45 51 ];
46 52
53 interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
54 cycleDuration?: number;
55 }
56
57 const generatableVendors: IGeneratableVendorInfo[] = [
58 {
59 _id: { $oid: "67dadc30e4b6e0e5979c8d84" },
60 TypeName: "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest",
61 PropertyTextHash: "77093DD05A8561A022DEC9A4B9BB4A56",
62 RandomSeedType: "VRST_WEAPON",
63 RequiredGoalTag: "",
64 WeaponUpgradeValueAttenuationExponent: 2.25,
65 cycleDuration: 4 * unixTimesInMs.day
66 }
67 // {
68 // _id: { $oid: "5dbb4c41e966f7886c3ce939" },
69 // TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
70 // PropertyTextHash: "62B64A8065B7C0FA345895D4BC234621"
71 // }
72 ];
73
47 74 export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPreprocessed | undefined => {
48 75 for (const vendorManifest of rawVendorManifests) {
49 76 if (vendorManifest.VendorInfo.TypeName == typeName) {
50 77 return preprocessVendorManifest(vendorManifest);
51 78 }
52 79 }
53 if (typeName == "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest") {
54 return generateCodaWeaponVendorManifest();
80 for (const vendorInfo of generatableVendors) {
81 if (vendorInfo.TypeName == typeName) {
82 return generateVendorManifest(vendorInfo);
83 }
55 84 }
56 85 return undefined;
57 86 };
@@ -62,8 +91,10 @@ export const getVendorManifestByOid = (oid: string): IVendorManifestPreprocessed
62 91 return preprocessVendorManifest(vendorManifest);
63 92 }
64 93 }
65 if (oid == "67dadc30e4b6e0e5979c8d84") {
66 return generateCodaWeaponVendorManifest();
94 for (const vendorInfo of generatableVendors) {
95 if (vendorInfo._id.$oid == oid) {
96 return generateVendorManifest(vendorInfo);
97 }
67 98 }
68 99 return undefined;
69 100 };
@@ -103,41 +134,59 @@ const refreshExpiry = (expiry: IMongoDate): number => {
103 134 return 0;
104 135 };
105 136
106 const generateCodaWeaponVendorManifest = (): IVendorManifestPreprocessed => {
107 const EPOCH = 1740960000 * 1000;
108 const DUR = 4 * 86400 * 1000;
109 const cycle = Math.trunc((Date.now() - EPOCH) / DUR);
110 const cycleStart = EPOCH + cycle * DUR;
111 const cycleEnd = cycleStart + DUR;
112 const binThisCycle = cycle % 2; // isOneBinPerCycle
137 const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => {
138 const EPOCH = 1740960000 * 1000; // Monday; aligns with coda weapons 8 day cycle.
139 const manifest = ExportVendors[vendorInfo.TypeName];
140 let binThisCycle;
141 if (manifest.isOneBinPerCycle) {
142 const cycleDuration = vendorInfo.cycleDuration!; // manifest.items[0].durationHours! * 3600_000;
143 const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
144 binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
145 }
113 146 const items: IItemManifestPreprocessed[] = [];
114 const manifest = ExportVendors["/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest"];
115 const rng = new CRng(cycle);
116 for (const rawItem of manifest.items) {
117 if (rawItem.bin != binThisCycle) {
147 let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
148 for (let i = 0; i != manifest.items.length; ++i) {
149 const rawItem = manifest.items[i];
150 if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
118 151 continue;
119 152 }
120 items.push({
121 StoreItem: rawItem.storeItem,
122 ItemPrices: rawItem.itemPrices!.map(item => ({ ...item, ProductCategory: "MiscItems" })),
123 Bin: "BIN_" + rawItem.bin,
124 QuantityMultiplier: 1,
125 Expiry: { $date: { $numberLong: cycleEnd.toString() } },
126 AllowMultipurchase: false,
127 LocTagRandSeed: (BigInt(rng.randomInt(0, 0xffffffff)) << 32n) | BigInt(rng.randomInt(0, 0xffffffff)),
128 Id: { $oid: "67e9da12793a120d" + rng.randomInt(0, 0xffffffff).toString(16).padStart(8, "0") }
129 });
153 const cycleDuration = vendorInfo.cycleDuration!; // rawItem.durationHours! * 3600_000;
154 const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
155 const cycleStart = EPOCH + cycleIndex * cycleDuration;
156 const cycleEnd = cycleStart + cycleDuration;
157 if (soonestOfferExpiry > cycleEnd) {
158 soonestOfferExpiry = cycleEnd;
159 }
160 const rng = new CRng(cycleIndex);
161 rng.churnSeed(i);
162 /*for (let j = -1; j != rawItem.duplicates; ++j)*/ {
163 const item: IItemManifestPreprocessed = {
164 StoreItem: rawItem.storeItem,
165 ItemPrices: rawItem.itemPrices!.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
166 Bin: "BIN_" + rawItem.bin,
167 QuantityMultiplier: 1,
168 Expiry: { $date: { $numberLong: cycleEnd.toString() } },
169 AllowMultipurchase: false,
170 Id: {
171 $oid:
172 i.toString(16).padStart(8, "0") +
173 vendorInfo._id.$oid.substring(8, 16) +
174 rng.randomInt(0, 0xffffffff).toString(16).padStart(8, "0")
175 }
176 };
177 if (vendorInfo.RandomSeedType) {
178 item.LocTagRandSeed =
179 (BigInt(rng.randomInt(0, 0xffffffff)) << 32n) | BigInt(rng.randomInt(0, 0xffffffff));
180 }
181 items.push(item);
182 }
130 183 }
184 delete vendorInfo.cycleDuration;
131 185 return {
132 186 VendorInfo: {
133 _id: { $oid: "67dadc30e4b6e0e5979c8d84" },
134 TypeName: "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest",
187 ...vendorInfo,
135 188 ItemManifest: items,
136 PropertyTextHash: "77093DD05A8561A022DEC9A4B9BB4A56",
137 RandomSeedType: "VRST_WEAPON",
138 RequiredGoalTag: "",
139 WeaponUpgradeValueAttenuationExponent: 2.25,
140 Expiry: { $date: { $numberLong: cycleEnd.toString() } }
189 Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
141 190 }
142 191 };
143 192 };
Modified src/types/vendorTypes.ts +5 -5
@@ -1,16 +1,16 @@
1 1 import { IMongoDate, IOid } from "./commonTypes";
2 2
3 interface IItemPrice {
3 export interface IItemPrice {
4 4 ItemType: string | string[]; // If string[], preprocessing will use RNG to pick one for the current period.
5 5 ItemCount: number;
6 6 ProductCategory: string;
7 7 }
8 8
9 interface IItemPricePreprocessed extends Omit<IItemPrice, "ItemType"> {
9 export interface IItemPricePreprocessed extends Omit<IItemPrice, "ItemType"> {
10 10 ItemType: string;
11 11 }
12 12
13 interface IItemManifest {
13 export interface IItemManifest {
14 14 StoreItem: string;
15 15 ItemPrices?: IItemPrice[];
16 16 Bin: string;
@@ -27,7 +27,7 @@ export interface IItemManifestPreprocessed extends Omit<IItemManifest, "ItemPric
27 27 ItemPrices?: IItemPricePreprocessed[];
28 28 }
29 29
30 interface IVendorInfo {
30 export interface IVendorInfo {
31 31 _id: IOid;
32 32 TypeName: string;
33 33 ItemManifest: IItemManifest[];
@@ -38,7 +38,7 @@ interface IVendorInfo {
38 38 Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
39 39 }
40 40
41 interface IVendorInfoPreprocessed extends Omit<IVendorInfo, "ItemManifest"> {
41 export interface IVendorInfoPreprocessed extends Omit<IVendorInfo, "ItemManifest"> {
42 42 ItemManifest: IItemManifestPreprocessed[];
43 43 }
44 44