返回提交历史
Modified
src/services/rngService.ts
+6
-0
Modified
src/services/serversideVendorsService.ts
+82
-33
Modified
src/types/vendorTypes.ts
+5
-5
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
代码差异
3 个文件
+93
-38
@@ -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.
@@ -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
};
@@ -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