返回提交历史
Modified
config.json.example
+1
-0
Modified
src/services/configService.ts
+1
-0
Modified
src/services/serversideVendorsService.ts
+102
-58
Modified
static/webui/index.html
+4
-0
Modified
static/webui/translations/de.js
+1
-0
Modified
static/webui/translations/en.js
+1
-0
Modified
static/webui/translations/es.js
+1
-0
Modified
static/webui/translations/fr.js
+1
-0
Modified
static/webui/translations/ru.js
+1
-0
Modified
static/webui/translations/zh.js
+1
-0
XFEstudio/XFESpaceNinjaServer
feat: fullyStockedVendors cheat (#2246)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2246 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
84f08131
代码差异
10 个文件
+114
-58
@@ -41,6 +41,7 @@
41
41
"noVendorPurchaseLimits": false,
42
42
"noDeathMarks": false,
43
43
"noKimCooldowns": false,
44
"fullyStockedVendors": false,
44
45
"syndicateMissionsRepeatable": false,
45
46
"unlockAllProfitTakerStages": false,
46
47
"instantFinishRivenChallenge": false,
@@ -48,6 +48,7 @@ export interface IConfig {
48
48
noVendorPurchaseLimits?: boolean;
49
49
noDeathMarks?: boolean;
50
50
noKimCooldowns?: boolean;
51
fullyStockedVendors?: boolean;
51
52
syndicateMissionsRepeatable?: boolean;
52
53
unlockAllProfitTakerStages?: boolean;
53
54
instantFinishRivenChallenge?: boolean;
@@ -6,6 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
6
6
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
7
7
import { logger } from "@/src/utils/logger";
8
8
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
9
import { config } from "./configService";
9
10
10
11
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
11
12
cycleOffset?: number;
@@ -59,20 +60,23 @@ const getCycleDuration = (manifest: IVendor): number => {
59
60
return dur * unixTimesInMs.hour;
60
61
};
61
62
62
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
63
export const getVendorManifestByTypeName = (typeName: string, fullStock?: boolean): IVendorManifest | undefined => {
63
64
for (const vendorInfo of generatableVendors) {
64
65
if (vendorInfo.TypeName == typeName) {
65
return generateVendorManifest(vendorInfo);
66
return generateVendorManifest(vendorInfo, fullStock ?? config.fullyStockedVendors);
66
67
}
67
68
}
68
69
if (typeName in ExportVendors) {
69
70
const manifest = ExportVendors[typeName];
70
return generateVendorManifest({
71
_id: { $oid: getVendorOid(typeName) },
72
TypeName: typeName,
73
RandomSeedType: manifest.randomSeedType,
74
cycleDuration: getCycleDuration(manifest)
75
});
71
return generateVendorManifest(
72
{
73
_id: { $oid: getVendorOid(typeName) },
74
TypeName: typeName,
75
RandomSeedType: manifest.randomSeedType,
76
cycleDuration: getCycleDuration(manifest)
77
},
78
fullStock ?? config.fullyStockedVendors
79
);
76
80
}
77
81
return undefined;
78
82
};
@@ -80,18 +84,21 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
80
84
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
81
85
for (const vendorInfo of generatableVendors) {
82
86
if (vendorInfo._id.$oid == oid) {
83
return generateVendorManifest(vendorInfo);
87
return generateVendorManifest(vendorInfo, config.fullyStockedVendors);
84
88
}
85
89
}
86
90
for (const [typeName, manifest] of Object.entries(ExportVendors)) {
87
91
const typeNameOid = getVendorOid(typeName);
88
92
if (typeNameOid == oid) {
89
return generateVendorManifest({
90
_id: { $oid: typeNameOid },
91
TypeName: typeName,
92
RandomSeedType: manifest.randomSeedType,
93
cycleDuration: getCycleDuration(manifest)
94
});
93
return generateVendorManifest(
94
{
95
_id: { $oid: typeNameOid },
96
TypeName: typeName,
97
RandomSeedType: manifest.randomSeedType,
98
cycleDuration: getCycleDuration(manifest)
99
},
100
config.fullyStockedVendors
101
);
95
102
}
96
103
}
97
104
return undefined;
@@ -169,9 +176,26 @@ const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => {
169
176
}
170
177
};
171
178
179
let vendorManifestsUsingFullStock = false;
172
180
const vendorManifestCache: Record<string, IVendorManifest> = {};
173
181
174
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
182
const clearVendorCache = (): void => {
183
for (const k of Object.keys(vendorManifestCache)) {
184
delete vendorManifestCache[k];
185
}
186
};
187
188
const generateVendorManifest = (
189
vendorInfo: IGeneratableVendorInfo,
190
fullStock: boolean | undefined
191
): IVendorManifest => {
192
fullStock ??= config.fullyStockedVendors;
193
fullStock ??= false;
194
if (vendorManifestsUsingFullStock != fullStock) {
195
vendorManifestsUsingFullStock = fullStock;
196
clearVendorCache();
197
}
198
175
199
if (!(vendorInfo.TypeName in vendorManifestCache)) {
176
200
// eslint-disable-next-line @typescript-eslint/no-unused-vars
177
201
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
@@ -208,7 +232,20 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
208
232
const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration);
209
233
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
210
234
const offersToAdd: IVendorOffer[] = [];
211
if (!manifest.isOneBinPerCycle) {
235
if (manifest.isOneBinPerCycle) {
236
if (fullStock) {
237
for (const rawItem of manifest.items) {
238
offersToAdd.push(rawItem);
239
}
240
} else {
241
const binThisCycle = cycleIndex % 2; // Note: May want to check the actual number of bins, but this is only used for coda weapons right now.
242
for (const rawItem of manifest.items) {
243
if (rawItem.bin == binThisCycle) {
244
offersToAdd.push(rawItem);
245
}
246
}
247
}
248
} else {
212
249
// Compute vendor requirements, subtracting existing offers
213
250
const remainingItemCapacity: Record<TOfferId, number> = {};
214
251
const missingItemsPerBin: Record<number, number> = {};
@@ -254,12 +291,14 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
254
291
manifest.numItems &&
255
292
(manifest.numItems.minValue != manifest.numItems.maxValue ||
256
293
manifest.numItems.minValue != numCountedOffers);
257
const numItemsTarget = manifest.numItems
258
? numUncountedOffers +
259
(useRng
260
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
261
: manifest.numItems.minValue)
262
: manifest.items.length;
294
const numItemsTarget = fullStock
295
? numUncountedOffers + numCountedOffers
296
: manifest.numItems
297
? numUncountedOffers +
298
(useRng
299
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
300
: manifest.numItems.minValue)
301
: manifest.items.length;
263
302
let i = 0;
264
303
const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit<
265
304
IVendorOffer,
@@ -282,13 +321,6 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
282
321
i = 0;
283
322
}
284
323
}
285
} else {
286
const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
287
for (const rawItem of manifest.items) {
288
if (rawItem.bin == binThisCycle) {
289
offersToAdd.push(rawItem);
290
}
291
}
292
324
}
293
325
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
294
326
for (const rawItem of offersToAdd) {
@@ -387,34 +419,44 @@ if (args.dev) {
387
419
logger.warn(`getCycleDuration self test failed`);
388
420
}
389
421
390
const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
391
.VendorInfo.ItemManifest;
392
if (
393
ads.length != 5 ||
394
ads[0].Bin != "BIN_4" ||
395
ads[1].Bin != "BIN_3" ||
396
ads[2].Bin != "BIN_2" ||
397
ads[3].Bin != "BIN_1" ||
398
ads[4].Bin != "BIN_0"
399
) {
400
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
401
}
422
for (let i = 0; i != 2; ++i) {
423
const fullStock = !!i;
402
424
403
const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")!
404
.VendorInfo.ItemManifest;
405
if (
406
pall.length != 5 ||
407
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
408
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
409
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
410
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
411
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
412
) {
413
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
425
const ads = getVendorManifestByTypeName(
426
"/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
427
fullStock
428
)!.VendorInfo.ItemManifest;
429
if (
430
ads.length != 5 ||
431
ads[0].Bin != "BIN_4" ||
432
ads[1].Bin != "BIN_3" ||
433
ads[2].Bin != "BIN_2" ||
434
ads[3].Bin != "BIN_1" ||
435
ads[4].Bin != "BIN_0"
436
) {
437
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
438
}
439
440
const pall = getVendorManifestByTypeName(
441
"/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
442
fullStock
443
)!.VendorInfo.ItemManifest;
444
if (
445
pall.length != 5 ||
446
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
447
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
448
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
449
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
450
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
451
) {
452
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
453
}
414
454
}
415
455
416
const cms = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest")!
417
.VendorInfo.ItemManifest;
456
const cms = getVendorManifestByTypeName(
457
"/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest",
458
false
459
)!.VendorInfo.ItemManifest;
418
460
if (
419
461
cms.length != 9 ||
420
462
cms[0].Bin != "BIN_2" ||
@@ -426,13 +468,15 @@ if (args.dev) {
426
468
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`);
427
469
}
428
470
429
const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")!
430
.VendorInfo.ItemManifest;
471
const temple = getVendorManifestByTypeName(
472
"/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
473
false
474
)!.VendorInfo.ItemManifest;
431
475
if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) {
432
476
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`);
433
477
}
434
478
435
const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest")!
479
const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", false)!
436
480
.VendorInfo.ItemManifest;
437
481
if (
438
482
nakak.length != 10 ||
@@ -700,6 +700,10 @@
700
700
<input class="form-check-input" type="checkbox" id="noKimCooldowns" />
701
701
<label class="form-check-label" for="noKimCooldowns" data-loc="cheats_noKimCooldowns"></label>
702
702
</div>
703
<div class="form-check">
704
<input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
705
<label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
706
</div>
703
707
<div class="form-check">
704
708
<input class="form-check-input" type="checkbox" id="syndicateMissionsRepeatable" />
705
709
<label class="form-check-label" for="syndicateMissionsRepeatable" data-loc="cheats_syndicateMissionsRepeatable"></label>
@@ -158,6 +158,7 @@ dict = {
158
158
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
159
159
cheats_noDeathMarks: `Keine Todesmarkierungen`,
160
160
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
161
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
161
162
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
162
163
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
163
164
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
@@ -157,6 +157,7 @@ dict = {
157
157
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
158
158
cheats_noDeathMarks: `No Death Marks`,
159
159
cheats_noKimCooldowns: `No KIM Cooldowns`,
160
cheats_fullyStockedVendors: `Fully Stocked Vendors`,
160
161
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
161
162
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
162
163
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
@@ -158,6 +158,7 @@ dict = {
158
158
cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`,
159
159
cheats_noDeathMarks: `Sin marcas de muerte`,
160
160
cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`,
161
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
161
162
cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
162
163
cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`,
163
164
cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,
@@ -158,6 +158,7 @@ dict = {
158
158
cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
159
159
cheats_noDeathMarks: `Aucune marque d'assassin`,
160
160
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
161
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
161
162
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
162
163
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
163
164
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
@@ -158,6 +158,7 @@ dict = {
158
158
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
159
159
cheats_noDeathMarks: `Без меток сметри`,
160
160
cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
161
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
161
162
cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`,
162
163
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
163
164
cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`,
@@ -158,6 +158,7 @@ dict = {
158
158
cheats_noVendorPurchaseLimits: `商城或商人无购买限制`,
159
159
cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`,
160
160
cheats_noKimCooldowns: `无 KIM 冷却时间`,
161
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
161
162
cheats_syndicateMissionsRepeatable: `集团任务可重复`,
162
163
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
163
164
cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,