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

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
UTF-8
import type { RequestHandler } from "express";
import {
    getDict,
    getItemName,
    getNormalizedString,
    getString,
    supplementalKeys,
    supplementalRecipes,
    supplementalUpgrades,
    U5Modules
} from "../../services/itemDataService.ts";
import type { IU5FingerprintUpgrade } from "../../services/itemDataService.ts";
import type { TRarity, TRelicQuality } from "warframe-public-export-plus";
import {
    ExportAbilities,
    ExportArcanes,
    ExportAvionics,
    ExportBoosters,
    ExportCustoms,
    ExportDojoRecipes,
    ExportDrones,
    ExportFactions,
    ExportFlavour,
    ExportGear,
    ExportKeys,
    ExportMissionTypes,
    ExportRailjackWeapons,
    ExportRecipes,
    ExportRegions,
    ExportRelics,
    ExportResources,
    ExportSentinels,
    ExportSyndicates,
    ExportUpgrades,
    ExportWarframes,
    ExportWeapons
} from "warframe-public-export-plus";
import allIncarnons from "../../../static/fixed_responses/allIncarnonList.json" with { type: "json" };
import supplementalDict from "../../../static/fixed_responses/supplementalDict/index.json" with { type: "json" };
import varzia from "../../constants/varzia.ts";
import suitDefaultUpgrades from "../../constants/suitDefaultUpgrades.ts";
import { pseudoRecipeToOwnedRecipeMap } from "../../services/foundryService.ts";
import { nightwaveTagToSeasonName } from "../../services/worldStateService.ts";

interface ListedItem {
    uniqueName: string;
    name: string;
    subtype?: string;
    fusionLimit?: number;
    exalted?: readonly string[];
    badReason?: "starter" | "frivolous" | "notraw";
    partType?: string;
    chainLength?: number;
    parazon?: boolean;
    alwaysAvailable?: boolean;
    maxLevelCap?: number;
    eligibleForVault?: boolean;
    parentName?: string;
    fits?: { type: string; rarity: TRarity; statAtten?: number }[];
    upgrades?: IU5FingerprintUpgrade[];
}

interface ItemLists {
    Suits: ListedItem[];
    LongGuns: ListedItem[];
    Melee: ListedItem[];
    ModularParts: ListedItem[];
    Pistols: ListedItem[];
    Sentinels: ListedItem[];
    SentinelWeapons: ListedItem[];
    SpaceGuns: ListedItem[];
    SpaceMelee: ListedItem[];
    SpaceSuits: ListedItem[];
    MechSuits: ListedItem[];
    miscitems: ListedItem[];
    Syndicates: ListedItem[];
    OperatorAmps: ListedItem[];
    QuestKeys: ListedItem[];
    KubrowPets: ListedItem[];
    EvolutionProgress: ListedItem[];
    mods: ListedItem[];
    Boosters: ListedItem[];
    VarziaOffers: ListedItem[];
    Abilities: ListedItem[];
    TechProjects: ListedItem[];
    VaultDecoRecipes: ListedItem[];
    FlavourItems: ListedItem[];
    ShipDecorations: ListedItem[];
    WeaponSkins: ListedItem[];
    MissionTypes: ListedItem[];
    Nodes: ListedItem[];
    NightwaveTags: ListedItem[];
    AdditionalDict: ListedItem[];
    //circuitGameModes: ListedItem[];
    blueprintAndItem: string;
}

const relicQualitySuffixes: Record<TRelicQuality, string> = {
    VPQ_BRONZE: "",
    VPQ_SILVER: "/Lotus/Language/Relics/VoidProjectionQuality_Silver",
    VPQ_GOLD: "/Lotus/Language/Relics/VoidProjectionQuality_Gold",
    VPQ_PLATINUM: "/Lotus/Language/Relics/VoidProjectionQuality_Platinum"
};

const toTitleCase = (str: string): string => {
    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase());
};

const getItemListsController: RequestHandler = (req, response) => {
    const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
    const res: ItemLists = {
        Suits: [],
        LongGuns: [],
        Melee: [],
        ModularParts: [],
        Pistols: [],
        Sentinels: [],
        SentinelWeapons: [],
        SpaceGuns: [],
        SpaceMelee: [],
        SpaceSuits: [],
        MechSuits: [],
        miscitems: [],
        Syndicates: [],
        OperatorAmps: [],
        QuestKeys: [],
        KubrowPets: [],
        EvolutionProgress: [],
        mods: [],
        Boosters: [],
        VarziaOffers: [],
        Abilities: [],
        TechProjects: [],
        VaultDecoRecipes: [],
        FlavourItems: [],
        ShipDecorations: [],
        WeaponSkins: [],
        MissionTypes: [],
        Nodes: [],
        NightwaveTags: [],
        AdditionalDict: [],
        /*circuitGameModes: [
            {
                uniqueName: "Survival",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang))
            },
            {
                uniqueName: "VoidFlood",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang))
            },
            {
                uniqueName: "Excavation",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang))
            },
            {
                uniqueName: "Defense",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang))
            },
            {
                uniqueName: "Exterminate",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang))
            },
            {
                uniqueName: "Assassination",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang))
            },
            {
                uniqueName: "Alchemy",
                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang))
            }
        ]*/
        blueprintAndItem: getString("/Lotus/Language/Items/BlueprintAndItem", lang)
    };
    const eligibleForVault = new Set<string>([
        ...Object.values(ExportDojoRecipes.research).flatMap(r => r.ingredients.map(i => i.ItemType)),
        ...Object.values(ExportDojoRecipes.fabrications).flatMap(f => f.ingredients.map(i => i.ItemType)),
        ...Object.values(ExportDojoRecipes.rooms).flatMap(r => r.ingredients.map(i => i.ItemType)),
        ...Object.values(ExportDojoRecipes.decos).flatMap(d => d.ingredients.map(i => i.ItemType))
    ]);
    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
        if (item.productCategory != "SpecialItems") {
            res[item.productCategory].push({
                uniqueName,
                name: getString(item.name, lang),
                exalted: item.exalted,
                maxLevelCap: item.maxLevelCap
            });
        }
        item.abilities.forEach(ability => {
            if (!res.Abilities.some(x => x.uniqueName == ability.uniqueName)) {
                res.Abilities.push({
                    uniqueName: ability.uniqueName,
                    name: getString(ability.name || uniqueName, lang)
                });
            }
        });
    }
    for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
        if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
            res[item.productCategory].push({
                uniqueName,
                name: getString(item.name, lang),
                exalted: item.exalted
            });
        }
    }
    for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
        if (item.partType) {
            if (!uniqueName.split("/")[7]?.startsWith("PvPVariant")) {
                // not a pvp variant
                if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
                    res.ModularParts.push({
                        uniqueName,
                        name: getString(item.name, lang),
                        partType: item.partType
                    });
                }
                if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
                    res.miscitems.push({
                        uniqueName: uniqueName,
                        name: getString(item.name, lang),
                        parentName: item.parentName
                    });
                }
            }
        } else if (item.totalDamage !== 0) {
            if (
                item.productCategory == "LongGuns" ||
                item.productCategory == "Pistols" ||
                item.productCategory == "Melee" ||
                item.productCategory == "SpaceGuns" ||
                item.productCategory == "SpaceMelee" ||
                item.productCategory == "SentinelWeapons" ||
                item.productCategory == "OperatorAmps"
            ) {
                const listedItem: ListedItem = {
                    uniqueName,
                    name: getString(item.name, lang),
                    maxLevelCap: item.maxLevelCap,
                    parentName: item.parentName
                };
                res[item.productCategory].push(listedItem);

                // Only allow the rifle part of a bayonet to be added via webui.
                if (item.bayonetOtherWeaponType && item.excludeFromMarket) {
                    listedItem.badReason = "notraw";
                }
            }
        } else if (!item.excludeFromCodex) {
            res.miscitems.push({
                uniqueName: uniqueName,
                name: getString(item.name, lang),
                parentName: item.parentName
            });
        }
    }
    for (const [uniqueName, item] of Object.entries(ExportResources)) {
        if (item.productCategory == "SupplyDrop") {
            continue; // skip helminth resources
        }
        let name = getNormalizedString(item.name, lang);
        if ("dissectionParts" in item) {
            name = getString("/Lotus/Language/Fish/FishDisplayName", lang).replaceAll("|FISH_NAME|", name);
            if (item.syndicateTag == "CetusSyndicate") {
                if (uniqueName.indexOf("Large") != -1) {
                    name = name.replaceAll("|FISH_SIZE|", getString("/Lotus/Language/Fish/FishSizeLargeAbbrev", lang));
                } else if (uniqueName.indexOf("Medium") != -1) {
                    name = name.replaceAll("|FISH_SIZE|", getString("/Lotus/Language/Fish/FishSizeMediumAbbrev", lang));
                } else {
                    name = name.replaceAll("|FISH_SIZE|", getString("/Lotus/Language/Fish/FishSizeSmallAbbrev", lang));
                }
            } else {
                if (uniqueName.indexOf("Large") != -1) {
                    name = name.replaceAll(
                        "|FISH_SIZE|",
                        getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryElderAbbrev", lang)
                    );
                } else if (uniqueName.indexOf("Medium") != -1) {
                    name = name.replaceAll(
                        "|FISH_SIZE|",
                        getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryMatureAbbrev", lang)
                    );
                } else {
                    name = name.replaceAll(
                        "|FISH_SIZE|",
                        getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryYoungAbbrev", lang)
                    );
                }
            }
        }
        if (item.productCategory == "ShipDecorations") {
            res.ShipDecorations.push({
                uniqueName: uniqueName,
                name: name,
                parentName: item.parentName
            });
        } else if (
            name &&
            !uniqueName.startsWith("/Lotus/Types/Game/Projections/") &&
            !uniqueName.startsWith("/Lotus/Types/Gameplay/Duviri/Resource/Fish/Duviri") && // Duviri fish seem to be present twice, but only the ones under /Lotus/Types/Items/Fish/Duviri/ can be used for the fish tank/vignette.
            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
        ) {
            res.miscitems.push({
                uniqueName: uniqueName,
                name: name,
                subtype: "Resource",
                parentName: item.parentName,
                ...(eligibleForVault.has(uniqueName) && { eligibleForVault: true })
            });
        }
    }
    for (const [uniqueName, item] of Object.entries(ExportRelics)) {
        const qualitySuffix =
            item.quality !== "VPQ_BRONZE"
                ? ` [${toTitleCase(getString(relicQualitySuffixes[item.quality], lang))}]`
                : "";

        res.miscitems.push({
            uniqueName: uniqueName,
            name:
                getString("/Lotus/Language/Relics/VoidProjectionName", lang)
                    .replaceAll("|ERA|", getString(`/Lotus/Language/Relics/Era_${item.era.toUpperCase()}`, lang))
                    .replaceAll("|CATEGORY|", item.category) + qualitySuffix
        });
    }
    for (const [uniqueName, item] of Object.entries(ExportGear)) {
        res.miscitems.push({
            uniqueName: uniqueName,
            name: getString(item.name, lang),
            subtype: "Gear",
            parentName: item.parentName
        });
    }
    const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
    for (const [uniqueName, item] of Object.entries({ ...ExportRecipes, ...supplementalRecipes })) {
        if (!item.hidden && !(uniqueName in pseudoRecipeToOwnedRecipeMap)) {
            const resultName = getItemName(item.resultType);
            if (resultName) {
                let itemName = getString(resultName, lang);
                if (item.num > 1) itemName = `${itemName} X ${item.num}`;
                res.miscitems.push({
                    uniqueName: uniqueName,
                    name: recipeNameTemplate.replace("|ITEM|", itemName)
                });
            }
        }
    }
    for (const [uniqueName, item] of Object.entries(ExportDrones)) {
        res.miscitems.push({
            uniqueName: uniqueName,
            name: getString(item.name, lang)
        });
    }
    for (const [uniqueName, item] of Object.entries(ExportRailjackWeapons)) {
        res.miscitems.push({
            uniqueName: uniqueName,
            name: getString(item.name, lang),
            parentName: item.parentName
        });
    }
    for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
        if (item.productCategory == "WeaponSkins") {
            if (
                !uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items
                !uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins
                !uniqueName.endsWith("Coat") // Frost Prime stuff
            ) {
                res.WeaponSkins.push({
                    uniqueName: uniqueName,
                    name: getString(item.name, lang),
                    alwaysAvailable: item.alwaysAvailable
                });
            }
        } else /*if (item.productCategory == "CrewShipWeaponSkins")*/ {
            if (!item.alwaysAvailable) {
                res.miscitems.push({
                    uniqueName: uniqueName,
                    name: getString(item.name, lang)
                });
            }
        }
    }

    for (const [uniqueName, upgrade] of Object.entries({ ...ExportUpgrades, ...supplementalUpgrades })) {
        const mod: ListedItem = {
            uniqueName,
            name: getString(upgrade.name, lang),
            fusionLimit: upgrade.fusionLimit
        };
        if (upgrade.isStarter) {
            mod.badReason = "starter";
        } else if (upgrade.isFrivolous) {
            mod.badReason = "frivolous";
        } else if (upgrade.upgradeEntries) {
            mod.badReason = "notraw";
        }
        if (upgrade.type == "PARAZON") {
            mod.parazon = true;
        }
        res.mods.push(mod);
    }
    {
        const uniqueItemTypes = [
            ...new Set(
                Object.values(suitDefaultUpgrades)
                    .flat()
                    .map(u => u.ItemType)
            )
        ];
        const abilityNameOverrides: Record<string, string> = {
            "/Lotus/Powersuits/AntiMatter/AntiMatterDropAbilityCard": "/Lotus/Language/Suits/DropAbilityName",
            "/Lotus/Powersuits/AntiMatter/NullStarAbilityCard": "/Lotus/Language/Suits/NullstarAbilityName",
            "/Lotus/Powersuits/Banshee/EarthQuakeAbilityCard": "/Lotus/Language/Suits/SoundquakeAbilityName",
            "/Lotus/Powersuits/Berserker/GrappleAbilityCard": "/Lotus/Language/Suits/GrappleHookAbilityName",
            "/Lotus/Powersuits/Berserker/IntimidateAbilityCard": "/Lotus/Language/Suits/BerserkerScreamAbilityName",
            "/Lotus/Powersuits/Berserker/ShieldBlastAbilityCard": "/Lotus/Language/Suits/ShieldBashAbilityName",
            "/Lotus/Powersuits/Cowgirl/RicochetArmourAbilityCard": "/Lotus/Language/Suits/RicochetArmorAbilityName",
            "/Lotus/Powersuits/Ember/FireSkinAbilityCard": "/Lotus/Language/Suits/OverheatAbilityName",
            "/Lotus/Powersuits/Excalibur/SuperJumpAbilityCard": "/Lotus/Language/Items/SuperJumpAbility",
            "/Lotus/Powersuits/Frost/IceShieldAbilityCard": "/Lotus/Language/Suits/SnowGlobeAbilityName",
            "/Lotus/Powersuits/Frost/IceSpikeAbilityCard": "/Lotus/Language/Suits/IceWaveAbilityName",
            "/Lotus/Powersuits/Frost/IcicleAbilityCard": "/Lotus/Language/Suits/FreezeAbilityName",
            "/Lotus/Powersuits/Harlequin/LightAbilityCard": "/Lotus/Language/Suits/EclipseAbilityName",
            "/Lotus/Powersuits/Harlequin/ObjectChangeAbilityCard": "/Lotus/Language/Suits/SleightOfHandAbilityName",
            "/Lotus/Powersuits/Harlequin/PrismAbilityCard": "/Lotus/Language/Suits/GrandFinaleName",
            "/Lotus/Powersuits/Jade/DaggerAbilityCard": "/Lotus/Language/Suits/PhysicDaggerAbilityName",
            "/Lotus/Powersuits/Jade/SelfBulletAttractorAbilityCard": "/Lotus/Language/Suits/AbsorbAbilityName",
            "/Lotus/Powersuits/Loki/DisarmAbilityCard": "/Lotus/Language/Suits/RadialDisarmAbilityName",
            "/Lotus/Powersuits/Loki/SwitchAbilityCard": "/Lotus/Language/Suits/SwitchTeleportAbilityName",
            "/Lotus/Powersuits/Ninja/TelelportToAbilityCard": "/Lotus/Language/Suits/TeleportToAbilityName",
            "/Lotus/Powersuits/Pirate/LiquifyAbilityCard": "/Lotus/Language/Suits/LiquefyAbilityName",
            "/Lotus/Powersuits/Rhino/RadialBlastAbilityCard": "/Lotus/Language/Suits/RhinoRoarAbilityName",
            "/Lotus/Powersuits/Tengu/DiveBombAbilityCard": "/Lotus/Language/Suits/ZephyrDiveBombAbilityName",
            "/Lotus/Powersuits/Tengu/TailWindAbilityCard": "/Lotus/Language/Suits/ZephyrTailWindAbilityName",
            "/Lotus/Powersuits/Tengu/TornadoAbilityCard": "/Lotus/Language/Suits/ZephyrTornadoAbilityName",
            "/Lotus/Powersuits/Tengu/TurbulenceAbilityCard": "/Lotus/Language/Suits/ZephyrTurbulenceAbilityName",
            "/Lotus/Powersuits/Trapper/BounceTrapAbilityCard": "/Lotus/Language/Suits/TrapperMultinadeAbilityName",
            "/Lotus/Powersuits/Volt/OverloadAbilityCard": "/Lotus/Language/Suits/OverLoadAbilityName"
        };
        for (const uniqueName of uniqueItemTypes) {
            let locStr: string;

            if (abilityNameOverrides[uniqueName]) {
                locStr = abilityNameOverrides[uniqueName];
            } else {
                const abilityBase = uniqueName.split("/").pop()!.replace(/Card$/, "");
                locStr = `/Lotus/Language/Suits/${abilityBase}Name`;
            }
            res.mods.push({
                uniqueName,
                name: getString(locStr, lang),
                fusionLimit: 3
            });
        }
    }
    for (const [uniqueName, upgrade] of Object.entries(ExportAvionics)) {
        res.mods.push({
            uniqueName,
            name: getString(upgrade.name, lang),
            fusionLimit: upgrade.fusionLimit
        });
    }
    for (const [uniqueName, arcane] of Object.entries(ExportArcanes)) {
        if (uniqueName.substring(0, 18) != "/Lotus/Types/Game/") {
            const mod: ListedItem = {
                uniqueName,
                name: getString(arcane.name, lang)
            };
            if (arcane.excludeFromCodex) {
                mod.badReason = "frivolous";
            }
            res.mods.push(mod);
        }
    }
    for (const [uniqueName, syndicate] of Object.entries(ExportSyndicates)) {
        res.Syndicates.push({
            uniqueName,
            name: getString(syndicate.name, lang)
        });
    }
    for (const [uniqueName, key] of Object.entries({ ...ExportKeys, ...supplementalKeys })) {
        if (key.chainStages) {
            res.QuestKeys.push({
                uniqueName,
                name: getString(key.name || "", lang),
                chainLength: key.chainStages.length
            });
        } else if (key.name) {
            res.miscitems.push({
                uniqueName,
                name: getString(key.name, lang),
                parentName: key.parentName
            });
        }
    }
    for (const uniqueName of allIncarnons) {
        res.EvolutionProgress.push({
            uniqueName,
            name: getString(getItemName(uniqueName) || "", lang)
        });
    }

    for (const item of Object.values(ExportBoosters)) {
        res.Boosters.push({
            uniqueName: item.typeName,
            name: getString(item.name, lang)
        });
    }

    for (const uniqueName of Object.keys(varzia.primeDualPacks)) {
        res.VarziaOffers.push({
            uniqueName,
            name: getString(getItemName(uniqueName) || uniqueName, lang)
        });
    }

    for (const [uniqueName, ability] of Object.entries(ExportAbilities)) {
        res.Abilities.push({
            uniqueName,
            name: getString(ability.name || uniqueName, lang)
        });
    }

    for (const uniqueName of Object.keys(ExportDojoRecipes.research)) {
        if (
            !["Zekti", "Vidar", "Lavan"].some(house => uniqueName.includes(house)) &&
            !uniqueName.startsWith("/Lotus/Types/Items/ShipFeatureItems/Railjack/")
        ) {
            let resultType;
            if (uniqueName in ExportRecipes) {
                resultType = ExportRecipes[uniqueName].resultType;
            } else if (uniqueName in ExportDojoRecipes.fabrications) {
                resultType = ExportDojoRecipes.fabrications[uniqueName].resultType;
            } else if (uniqueName.startsWith("/Lotus/Types/Game/")) {
                resultType = uniqueName.replace("Blueprint", "");
            } else {
                resultType = uniqueName;
            }

            let name = getString(getItemName(resultType) || resultType, lang);

            if (uniqueName in ExportRecipes) {
                const recipeNum = ExportRecipes[uniqueName].num;
                if (recipeNum > 1) {
                    name = `${name} X ${recipeNum}`;
                }
            }

            res.TechProjects.push({
                uniqueName,
                name
            });
        }
    }

    for (const uniqueName of [
        ...Object.entries(ExportDojoRecipes.decos)
            .filter(([_, data]) => data.requiredInVault)
            .map(([uniqueName]) => uniqueName),
        // not requiredInVault:
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyBronzeRecipe",
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyGoldRecipe",
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophySilverRecipe",
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyTerracottaRecipe"
        // removed in 38.6.0:
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyBronzeRecipe",
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyCrystalRecipe",
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyGoldRecipe",
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe"
    ]) {
        let name = getString(getItemName(uniqueName) || uniqueName, lang);
        if (uniqueName.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemma")) {
            const factionTag = uniqueName.includes("Corpus") ? "FC_CORPUS" : "FC_GRINEER";
            const faction = ExportFactions[factionTag].name;
            name += ` [${getString(faction || factionTag, lang)}]`;
        }
        res.VaultDecoRecipes.push({
            uniqueName,
            name
        });
    }

    for (const [uniqueName, item] of Object.entries(ExportFlavour)) {
        res.FlavourItems.push({
            uniqueName,
            name: getString(item.name, lang),
            alwaysAvailable: item.alwaysAvailable
        });
    }

    for (const [uniqueName, item] of Object.entries(ExportMissionTypes)) {
        res.MissionTypes.push({
            uniqueName,
            name: toTitleCase(getString(item.name || "", lang))
        });
    }

    for (const [uniqueName, node] of Object.entries(ExportRegions)) {
        res.Nodes.push({
            uniqueName,
            name: getString(node.name || uniqueName, lang)
        });
    }

    for (const [uniqueName, mod] of Object.entries(U5Modules)) {
        res.mods.push({
            uniqueName,
            name: uniqueName,
            fusionLimit: 0,
            fits: mod.fits,
            upgrades: mod.upgrades
        });
    }

    for (const [uniqueName, name] of Object.entries(nightwaveTagToSeasonName)) {
        res.NightwaveTags.push({
            uniqueName,
            name: getString(name, lang) || name
        });
    }

    for (const uniqueName of supplementalDict) {
        res.AdditionalDict.push({
            uniqueName,
            name: getString(uniqueName, lang)
        });
    }

    response.json(res);
};

export { getItemListsController };