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

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
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

10 个文件 +114 -58
Modified config.json.example +1 -0
@@ -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,
Modified src/services/configService.ts +1 -0
@@ -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;
Modified src/services/serversideVendorsService.ts +102 -58
@@ -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 ||
Modified static/webui/index.html +4 -0
@@ -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>
Modified static/webui/translations/de.js +1 -0
@@ -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`,
Modified static/webui/translations/en.js +1 -0
@@ -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`,
Modified static/webui/translations/es.js +1 -0
@@ -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`,
Modified static/webui/translations/fr.js +1 -0
@@ -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`,
Modified static/webui/translations/ru.js +1 -0
@@ -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`,
Modified static/webui/translations/zh.js +1 -0
@@ -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: `立即完成裂罅挑战`,