feat: fullyStockedVendors cheat (#2246)
Reviewed-on: OpenWF/SpaceNinjaServer#2246 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									7ca7147b78
								
							
						
					
					
						commit
						84f081312b
					
				@ -41,6 +41,7 @@
 | 
			
		||||
  "noVendorPurchaseLimits": false,
 | 
			
		||||
  "noDeathMarks": false,
 | 
			
		||||
  "noKimCooldowns": false,
 | 
			
		||||
  "fullyStockedVendors": false,
 | 
			
		||||
  "syndicateMissionsRepeatable": false,
 | 
			
		||||
  "unlockAllProfitTakerStages": false,
 | 
			
		||||
  "instantFinishRivenChallenge": false,
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ export interface IConfig {
 | 
			
		||||
    noVendorPurchaseLimits?: boolean;
 | 
			
		||||
    noDeathMarks?: boolean;
 | 
			
		||||
    noKimCooldowns?: boolean;
 | 
			
		||||
    fullyStockedVendors?: boolean;
 | 
			
		||||
    syndicateMissionsRepeatable?: boolean;
 | 
			
		||||
    unlockAllProfitTakerStages?: boolean;
 | 
			
		||||
    instantFinishRivenChallenge?: boolean;
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
 | 
			
		||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
 | 
			
		||||
import { config } from "./configService";
 | 
			
		||||
 | 
			
		||||
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
 | 
			
		||||
    cycleOffset?: number;
 | 
			
		||||
@ -59,20 +60,23 @@ const getCycleDuration = (manifest: IVendor): number => {
 | 
			
		||||
    return dur * unixTimesInMs.hour;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
 | 
			
		||||
export const getVendorManifestByTypeName = (typeName: string, fullStock?: boolean): IVendorManifest | undefined => {
 | 
			
		||||
    for (const vendorInfo of generatableVendors) {
 | 
			
		||||
        if (vendorInfo.TypeName == typeName) {
 | 
			
		||||
            return generateVendorManifest(vendorInfo);
 | 
			
		||||
            return generateVendorManifest(vendorInfo, fullStock ?? config.fullyStockedVendors);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in ExportVendors) {
 | 
			
		||||
        const manifest = ExportVendors[typeName];
 | 
			
		||||
        return generateVendorManifest({
 | 
			
		||||
        return generateVendorManifest(
 | 
			
		||||
            {
 | 
			
		||||
                _id: { $oid: getVendorOid(typeName) },
 | 
			
		||||
                TypeName: typeName,
 | 
			
		||||
                RandomSeedType: manifest.randomSeedType,
 | 
			
		||||
                cycleDuration: getCycleDuration(manifest)
 | 
			
		||||
        });
 | 
			
		||||
            },
 | 
			
		||||
            fullStock ?? config.fullyStockedVendors
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
};
 | 
			
		||||
@ -80,18 +84,21 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
 | 
			
		||||
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
 | 
			
		||||
    for (const vendorInfo of generatableVendors) {
 | 
			
		||||
        if (vendorInfo._id.$oid == oid) {
 | 
			
		||||
            return generateVendorManifest(vendorInfo);
 | 
			
		||||
            return generateVendorManifest(vendorInfo, config.fullyStockedVendors);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    for (const [typeName, manifest] of Object.entries(ExportVendors)) {
 | 
			
		||||
        const typeNameOid = getVendorOid(typeName);
 | 
			
		||||
        if (typeNameOid == oid) {
 | 
			
		||||
            return generateVendorManifest({
 | 
			
		||||
            return generateVendorManifest(
 | 
			
		||||
                {
 | 
			
		||||
                    _id: { $oid: typeNameOid },
 | 
			
		||||
                    TypeName: typeName,
 | 
			
		||||
                    RandomSeedType: manifest.randomSeedType,
 | 
			
		||||
                    cycleDuration: getCycleDuration(manifest)
 | 
			
		||||
            });
 | 
			
		||||
                },
 | 
			
		||||
                config.fullyStockedVendors
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
@ -169,9 +176,26 @@ const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let vendorManifestsUsingFullStock = false;
 | 
			
		||||
const vendorManifestCache: Record<string, IVendorManifest> = {};
 | 
			
		||||
 | 
			
		||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
 | 
			
		||||
const clearVendorCache = (): void => {
 | 
			
		||||
    for (const k of Object.keys(vendorManifestCache)) {
 | 
			
		||||
        delete vendorManifestCache[k];
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const generateVendorManifest = (
 | 
			
		||||
    vendorInfo: IGeneratableVendorInfo,
 | 
			
		||||
    fullStock: boolean | undefined
 | 
			
		||||
): IVendorManifest => {
 | 
			
		||||
    fullStock ??= config.fullyStockedVendors;
 | 
			
		||||
    fullStock ??= false;
 | 
			
		||||
    if (vendorManifestsUsingFullStock != fullStock) {
 | 
			
		||||
        vendorManifestsUsingFullStock = fullStock;
 | 
			
		||||
        clearVendorCache();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!(vendorInfo.TypeName in vendorManifestCache)) {
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
 | 
			
		||||
@ -208,7 +232,20 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
			
		||||
        const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration);
 | 
			
		||||
        const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
 | 
			
		||||
        const offersToAdd: IVendorOffer[] = [];
 | 
			
		||||
        if (!manifest.isOneBinPerCycle) {
 | 
			
		||||
        if (manifest.isOneBinPerCycle) {
 | 
			
		||||
            if (fullStock) {
 | 
			
		||||
                for (const rawItem of manifest.items) {
 | 
			
		||||
                    offersToAdd.push(rawItem);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                const binThisCycle = cycleIndex % 2; // Note: May want to check the actual number of bins, but this is only used for coda weapons right now.
 | 
			
		||||
                for (const rawItem of manifest.items) {
 | 
			
		||||
                    if (rawItem.bin == binThisCycle) {
 | 
			
		||||
                        offersToAdd.push(rawItem);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Compute vendor requirements, subtracting existing offers
 | 
			
		||||
            const remainingItemCapacity: Record<TOfferId, number> = {};
 | 
			
		||||
            const missingItemsPerBin: Record<number, number> = {};
 | 
			
		||||
@ -254,7 +291,9 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
			
		||||
                manifest.numItems &&
 | 
			
		||||
                (manifest.numItems.minValue != manifest.numItems.maxValue ||
 | 
			
		||||
                    manifest.numItems.minValue != numCountedOffers);
 | 
			
		||||
            const numItemsTarget = manifest.numItems
 | 
			
		||||
            const numItemsTarget = fullStock
 | 
			
		||||
                ? numUncountedOffers + numCountedOffers
 | 
			
		||||
                : manifest.numItems
 | 
			
		||||
                  ? numUncountedOffers +
 | 
			
		||||
                    (useRng
 | 
			
		||||
                        ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
 | 
			
		||||
@ -282,13 +321,6 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
			
		||||
                    i = 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
 | 
			
		||||
            for (const rawItem of manifest.items) {
 | 
			
		||||
                if (rawItem.bin == binThisCycle) {
 | 
			
		||||
                    offersToAdd.push(rawItem);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const cycleStart = cycleOffset + cycleIndex * cycleDuration;
 | 
			
		||||
        for (const rawItem of offersToAdd) {
 | 
			
		||||
@ -387,8 +419,13 @@ if (args.dev) {
 | 
			
		||||
        logger.warn(`getCycleDuration self test failed`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
 | 
			
		||||
        .VendorInfo.ItemManifest;
 | 
			
		||||
    for (let i = 0; i != 2; ++i) {
 | 
			
		||||
        const fullStock = !!i;
 | 
			
		||||
 | 
			
		||||
        const ads = getVendorManifestByTypeName(
 | 
			
		||||
            "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
 | 
			
		||||
            fullStock
 | 
			
		||||
        )!.VendorInfo.ItemManifest;
 | 
			
		||||
        if (
 | 
			
		||||
            ads.length != 5 ||
 | 
			
		||||
            ads[0].Bin != "BIN_4" ||
 | 
			
		||||
@ -400,8 +437,10 @@ if (args.dev) {
 | 
			
		||||
            logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")!
 | 
			
		||||
        .VendorInfo.ItemManifest;
 | 
			
		||||
        const pall = getVendorManifestByTypeName(
 | 
			
		||||
            "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
 | 
			
		||||
            fullStock
 | 
			
		||||
        )!.VendorInfo.ItemManifest;
 | 
			
		||||
        if (
 | 
			
		||||
            pall.length != 5 ||
 | 
			
		||||
            pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
 | 
			
		||||
@ -412,9 +451,12 @@ if (args.dev) {
 | 
			
		||||
        ) {
 | 
			
		||||
            logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const cms = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest")!
 | 
			
		||||
        .VendorInfo.ItemManifest;
 | 
			
		||||
    const cms = getVendorManifestByTypeName(
 | 
			
		||||
        "/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest",
 | 
			
		||||
        false
 | 
			
		||||
    )!.VendorInfo.ItemManifest;
 | 
			
		||||
    if (
 | 
			
		||||
        cms.length != 9 ||
 | 
			
		||||
        cms[0].Bin != "BIN_2" ||
 | 
			
		||||
@ -426,13 +468,15 @@ if (args.dev) {
 | 
			
		||||
        logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")!
 | 
			
		||||
        .VendorInfo.ItemManifest;
 | 
			
		||||
    const temple = getVendorManifestByTypeName(
 | 
			
		||||
        "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
 | 
			
		||||
        false
 | 
			
		||||
    )!.VendorInfo.ItemManifest;
 | 
			
		||||
    if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) {
 | 
			
		||||
        logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest")!
 | 
			
		||||
    const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", false)!
 | 
			
		||||
        .VendorInfo.ItemManifest;
 | 
			
		||||
    if (
 | 
			
		||||
        nakak.length != 10 ||
 | 
			
		||||
 | 
			
		||||
@ -700,6 +700,10 @@
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="noKimCooldowns" />
 | 
			
		||||
                                        <label class="form-check-label" for="noKimCooldowns" data-loc="cheats_noKimCooldowns"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
 | 
			
		||||
                                        <label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="syndicateMissionsRepeatable" />
 | 
			
		||||
                                        <label class="form-check-label" for="syndicateMissionsRepeatable" data-loc="cheats_syndicateMissionsRepeatable"></label>
 | 
			
		||||
 | 
			
		||||
@ -158,6 +158,7 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
 | 
			
		||||
    cheats_noDeathMarks: `Keine Todesmarkierungen`,
 | 
			
		||||
    cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
 | 
			
		||||
    cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
 | 
			
		||||
 | 
			
		||||
@ -157,6 +157,7 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
 | 
			
		||||
    cheats_noDeathMarks: `No Death Marks`,
 | 
			
		||||
    cheats_noKimCooldowns: `No KIM Cooldowns`,
 | 
			
		||||
    cheats_fullyStockedVendors: `Fully Stocked Vendors`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
 | 
			
		||||
 | 
			
		||||
@ -158,6 +158,7 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`,
 | 
			
		||||
    cheats_noDeathMarks: `Sin marcas de muerte`,
 | 
			
		||||
    cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`,
 | 
			
		||||
    cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,
 | 
			
		||||
 | 
			
		||||
@ -158,6 +158,7 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
 | 
			
		||||
    cheats_noDeathMarks: `Aucune marque d'assassin`,
 | 
			
		||||
    cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
 | 
			
		||||
    cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
 | 
			
		||||
 | 
			
		||||
@ -158,6 +158,7 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
 | 
			
		||||
    cheats_noDeathMarks: `Без меток сметри`,
 | 
			
		||||
    cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
 | 
			
		||||
    cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`,
 | 
			
		||||
 | 
			
		||||
@ -158,6 +158,7 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `商城或商人无购买限制`,
 | 
			
		||||
    cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`,
 | 
			
		||||
    cheats_noKimCooldowns: `无 KIM 冷却时间`,
 | 
			
		||||
    cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `集团任务可重复`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user