forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			654652b889
			...
			d0127f609c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0127f609c | |||
| 2b054d1728 | |||
| 5ac73528a0 | |||
| 678ad0c4a1 | |||
| 4bdb759463 | |||
| 71be8a2868 | |||
| f3072e84c9 | |||
| b2749765a3 | 
@ -20,6 +20,7 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
 | 
			
		||||
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
 | 
			
		||||
- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral.
 | 
			
		||||
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
 | 
			
		||||
  - `RadioLegionIntermission14Syndicate` for Nora's Mix: Dreams of the Dead
 | 
			
		||||
  - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
 | 
			
		||||
  - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
 | 
			
		||||
  - `RadioLegionIntermission11Syndicate` for Nora's Mix Vol. 7
 | 
			
		||||
 | 
			
		||||
@ -41,8 +41,7 @@ function run(changedFile) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const thisbuildproc = spawn(
 | 
			
		||||
        process.versions.bun ? "bun" : "npm",
 | 
			
		||||
        ["run", cangoraw ? "verify" : "build:dev"],
 | 
			
		||||
        [process.versions.bun ? "bun" : "npm", "run", cangoraw ? "verify" : "build:dev"].join(" "),
 | 
			
		||||
        spawnopts
 | 
			
		||||
    );
 | 
			
		||||
    const thisbuildstart = Date.now();
 | 
			
		||||
@ -55,8 +54,13 @@ function run(changedFile) {
 | 
			
		||||
        if (code === 0) {
 | 
			
		||||
            console.log(`${cangoraw ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
 | 
			
		||||
            runproc = spawn(
 | 
			
		||||
                process.versions.bun ? "bun" : "npm",
 | 
			
		||||
                ["run", cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start", "--", ...args],
 | 
			
		||||
                [
 | 
			
		||||
                    process.versions.bun ? "bun" : "npm",
 | 
			
		||||
                    "run",
 | 
			
		||||
                    cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start",
 | 
			
		||||
                    "--",
 | 
			
		||||
                    ...args
 | 
			
		||||
                ].join(" "),
 | 
			
		||||
                spawnopts
 | 
			
		||||
            );
 | 
			
		||||
            runproc.on("exit", () => {
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ const app = express();
 | 
			
		||||
 | 
			
		||||
app.use((req, _res, next) => {
 | 
			
		||||
    // 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
 | 
			
		||||
    // The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
 | 
			
		||||
    // The client patch is expected to decrypt it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
 | 
			
		||||
    if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
 | 
			
		||||
        req.headers["content-encoding"] = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { parseString } from "../../helpers/general.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { giveKeyChainItem } from "../../services/questService.ts";
 | 
			
		||||
import type { IKeyChainRequest } from "../../types/requestTypes.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								src/controllers/api/reverseQuestProgressController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/controllers/api/reverseQuestProgressController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { resetQuestKeyToStage } from "../../services/questService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import type { IKeyChainRequest } from "../../types/requestTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const reverseQuestProgressController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    resetQuestKeyToStage(inventory, keyChainInfo);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
@ -120,6 +120,7 @@ import { rerollRandomModController } from "../controllers/api/rerollRandomModCon
 | 
			
		||||
import { researchMushroomController } from "../controllers/api/researchMushroomController.ts";
 | 
			
		||||
import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts";
 | 
			
		||||
import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts";
 | 
			
		||||
import { reverseQuestProgressController } from "../controllers/api/reverseQuestProgressController.ts";
 | 
			
		||||
import { saveDialogueController } from "../controllers/api/saveDialogueController.ts";
 | 
			
		||||
import { saveLoadoutController } from "../controllers/api/saveLoadoutController.ts";
 | 
			
		||||
import { saveSettingsController } from "../controllers/api/saveSettingsController.ts";
 | 
			
		||||
@ -323,6 +324,7 @@ apiRouter.post("/renamePet.php", renamePetController);
 | 
			
		||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
 | 
			
		||||
apiRouter.post("/researchMushroom.php", researchMushroomController);
 | 
			
		||||
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
 | 
			
		||||
apiRouter.post("/reverseQuestProgress.php", reverseQuestProgressController);
 | 
			
		||||
apiRouter.post("/saveDialogue.php", saveDialogueController);
 | 
			
		||||
apiRouter.post("/saveLoadout.php", saveLoadoutController);
 | 
			
		||||
apiRouter.post("/saveSettings.php", saveSettingsController);
 | 
			
		||||
 | 
			
		||||
@ -41,8 +41,8 @@ export const createAccount = async (accountData: IDatabaseAccountRequiredFields)
 | 
			
		||||
        await account.save();
 | 
			
		||||
        const loadoutId = await createLoadout(account._id);
 | 
			
		||||
        const shipId = await createShip(account._id);
 | 
			
		||||
        await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId });
 | 
			
		||||
        await createPersonalRooms(account._id, shipId);
 | 
			
		||||
        await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId });
 | 
			
		||||
        await createStats(account._id.toString());
 | 
			
		||||
        return account.toJSON();
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
@ -64,17 +64,6 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
 | 
			
		||||
        personalRoomsOwnerId: accountId,
 | 
			
		||||
        activeShipId: shipId
 | 
			
		||||
    });
 | 
			
		||||
    if (config.skipTutorial) {
 | 
			
		||||
        // unlocked during Vor's Prize and The Teacher quests
 | 
			
		||||
        const defaultFeatures = [
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/SocialMenuFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/FoundryFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/ModsFeatureItem"
 | 
			
		||||
        ];
 | 
			
		||||
        personalRooms.Ship.Features.push(...defaultFeatures);
 | 
			
		||||
    }
 | 
			
		||||
    await personalRooms.save();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2031,7 +2031,7 @@ function getRandomMissionDrops(
 | 
			
		||||
                                        .replace("SteelPath", "Steel")
 | 
			
		||||
                                        .replace(
 | 
			
		||||
                                            "/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob",
 | 
			
		||||
                                            "Lotus/Types/Game/MissionDecks/NokkoColonyRewards/NokkoColonyRewards"
 | 
			
		||||
                                            "/Lotus/Types/Game/MissionDecks/NokkoColonyRewards/NokkoColonyRewards"
 | 
			
		||||
                                        ),
 | 
			
		||||
                                    masteryReq: 0,
 | 
			
		||||
                                    minEnemyLevel: 30,
 | 
			
		||||
 | 
			
		||||
@ -2,16 +2,25 @@ import type { IKeyChainRequest } from "../types/requestTypes.ts";
 | 
			
		||||
import { isEmptyObject } from "../helpers/general.ts";
 | 
			
		||||
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
 | 
			
		||||
import { createMessage } from "./inboxService.ts";
 | 
			
		||||
import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "./inventoryService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    addEquipment,
 | 
			
		||||
    addItem,
 | 
			
		||||
    addItems,
 | 
			
		||||
    addKeyChainItems,
 | 
			
		||||
    addPowerSuit,
 | 
			
		||||
    setupKahlSyndicate
 | 
			
		||||
} from "./inventoryService.ts";
 | 
			
		||||
import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "./itemDataService.ts";
 | 
			
		||||
import type { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { logger } from "../utils/logger.ts";
 | 
			
		||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
			
		||||
import { ExportKeys, ExportRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
 | 
			
		||||
import questCompletionItems from "../../static/fixed_responses/questCompletionRewards.json" with { type: "json" };
 | 
			
		||||
import type { ITypeCount } from "../types/commonTypes.ts";
 | 
			
		||||
import { addString } from "../helpers/stringHelpers.ts";
 | 
			
		||||
import { unlockShipFeature } from "./personalRoomsService.ts";
 | 
			
		||||
import { EquipmentFeatures } from "../types/equipmentTypes.ts";
 | 
			
		||||
 | 
			
		||||
export interface IUpdateQuestRequest {
 | 
			
		||||
    QuestKeys: IQuestKeyClient[];
 | 
			
		||||
@ -92,6 +101,30 @@ export const updateQuestStage = (
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const resetQuestKeyToStage = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    { KeyChain, ChainStage }: IKeyChainRequest
 | 
			
		||||
): void => {
 | 
			
		||||
    const quest = inventory.QuestKeys.find(quest => quest.ItemType === KeyChain);
 | 
			
		||||
 | 
			
		||||
    if (!quest) {
 | 
			
		||||
        throw new Error(`Quest ${KeyChain} not found in QuestKeys`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    quest.Progress ??= [];
 | 
			
		||||
 | 
			
		||||
    quest.Progress.splice(1 + ChainStage); // remove stages past the target
 | 
			
		||||
 | 
			
		||||
    const questStage = quest.Progress[ChainStage];
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    if (questStage) {
 | 
			
		||||
        const run = quest.Progress[0].c;
 | 
			
		||||
        if (run >= 0) {
 | 
			
		||||
            questStage.c = run - 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addQuestKey = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    questKey: IQuestKeyDatabase
 | 
			
		||||
@ -169,6 +202,7 @@ export const completeQuest = async (
 | 
			
		||||
            stage.c = run;
 | 
			
		||||
            await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i }, sendMessages);
 | 
			
		||||
            await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
 | 
			
		||||
            await installShipFeatures(inventory, { KeyChain: questKey, ChainStage: i });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -305,6 +339,9 @@ const handleQuestCompletion = async (
 | 
			
		||||
    logger.debug(`quest completion items`, questCompletionItems);
 | 
			
		||||
    if (questCompletionItems) {
 | 
			
		||||
        await addItems(inventory, questCompletionItems, inventoryChanges);
 | 
			
		||||
        for (const item of questCompletionItems) {
 | 
			
		||||
            await removeRequiredItems(inventory, item.ItemType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -354,7 +391,12 @@ export const giveKeyChainMessage = async (
 | 
			
		||||
        await createMessage(inventory.accountOwnerId, [keyChainMessage]);
 | 
			
		||||
    } else {
 | 
			
		||||
        if (keyChainMessage.countedAtt?.length) await addItems(inventory, keyChainMessage.countedAtt);
 | 
			
		||||
        if (keyChainMessage.att?.length) await addItems(inventory, keyChainMessage.att);
 | 
			
		||||
        if (keyChainMessage.att?.length) {
 | 
			
		||||
            await addItems(inventory, keyChainMessage.att);
 | 
			
		||||
            for (const reward of keyChainMessage.att) {
 | 
			
		||||
                await removeRequiredItems(inventory, reward);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateQuestStage(inventory, keyChainInfo, { m: true });
 | 
			
		||||
@ -379,6 +421,7 @@ export const giveKeyChainMissionReward = async (
 | 
			
		||||
 | 
			
		||||
                for (const reward of missionRewards) {
 | 
			
		||||
                    await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
 | 
			
		||||
                    await removeRequiredItems(inventory, fromStoreItem(reward.StoreItem));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                updateQuestStage(inventory, keyChainInfo, { c: run });
 | 
			
		||||
@ -392,6 +435,7 @@ export const giveKeyChainMissionReward = async (
 | 
			
		||||
                        await addItem(inventory, fromStoreItem(reward.itemType), reward.amount);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        await addItem(inventory, fromStoreItem(reward.itemType));
 | 
			
		||||
                        await removeRequiredItems(inventory, fromStoreItem(reward.itemType));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -420,3 +464,337 @@ export const giveKeyChainStageTriggered = async (
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const installShipFeatures = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    keyChainInfo: IKeyChainRequest
 | 
			
		||||
): Promise<void> => {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
 | 
			
		||||
    const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain);
 | 
			
		||||
    if (chainStages && questKey) {
 | 
			
		||||
        if (keyChainInfo.ChainStage - 1 >= 0) {
 | 
			
		||||
            const prevStage = chainStages[keyChainInfo.ChainStage - 1];
 | 
			
		||||
            for (const item of prevStage.itemsToGiveWhenTriggered) {
 | 
			
		||||
                if (item.startsWith("/Lotus/StoreItems/Types/Items/ShipFeatureItems/")) {
 | 
			
		||||
                    logger.debug(`installing ship feature ${fromStoreItem(item)}`);
 | 
			
		||||
                    await unlockShipFeature(inventory, fromStoreItem(item));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (prevStage.key) {
 | 
			
		||||
                const fixedLevelRewards = getLevelKeyRewards(prevStage.key);
 | 
			
		||||
                if (fixedLevelRewards.levelKeyRewards?.items) {
 | 
			
		||||
                    for (const item of fixedLevelRewards.levelKeyRewards.items) {
 | 
			
		||||
                        if (item.startsWith("/Lotus/StoreItems/Types/Items/ShipFeatureItems/")) {
 | 
			
		||||
                            logger.debug(`installing ship feature ${fromStoreItem(item)}`);
 | 
			
		||||
                            await unlockShipFeature(inventory, fromStoreItem(item));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (fixedLevelRewards.levelKeyRewards2) {
 | 
			
		||||
                    for (const item of fixedLevelRewards.levelKeyRewards2) {
 | 
			
		||||
                        if (
 | 
			
		||||
                            item.rewardType == "RT_STORE_ITEM" &&
 | 
			
		||||
                            item.itemType.startsWith("/Lotus/StoreItems/Types/Items/ShipFeatureItems/")
 | 
			
		||||
                        ) {
 | 
			
		||||
                            logger.debug(`installing ship feature ${fromStoreItem(item.itemType)}`);
 | 
			
		||||
                            await unlockShipFeature(inventory, fromStoreItem(item.itemType));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument, typeName: string): Promise<void> => {
 | 
			
		||||
    switch (typeName) {
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Keys/LimboQuest/LimboHelmetKeyBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Keys/LimboQuest/LimboSystemsKeyBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Keys/LimboQuest/LimboChassisKeyBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/BrawlerBlueprint": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Recipes/Components/InfestedIrradiatedBaitBallBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    await addItem(inventory, recipe.resultType, -1, false, undefined, undefined, true);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
 | 
			
		||||
                    await addItems(inventory, [
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType:
 | 
			
		||||
                                "/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingWingsBlueprint",
 | 
			
		||||
                            ItemCount: -1
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType:
 | 
			
		||||
                                "/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingChassisBlueprint",
 | 
			
		||||
                            ItemCount: -1
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType:
 | 
			
		||||
                                "/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingSystemsBlueprint",
 | 
			
		||||
                            ItemCount: -1
 | 
			
		||||
                        }
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Recipes/Components/VorBoltRemoverBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/ChromaBlueprint": {
 | 
			
		||||
            await addItems(inventory, [
 | 
			
		||||
                {
 | 
			
		||||
                    ItemType: "/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconABlueprint",
 | 
			
		||||
                    ItemCount: -1
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    ItemType: "/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconBBlueprint",
 | 
			
		||||
                    ItemCount: -1
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    ItemType: "/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconCBlueprint",
 | 
			
		||||
                    ItemCount: -1
 | 
			
		||||
                }
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/OctaviaBlueprint": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Keys/BardQuest/BardQuestSequencerBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
 | 
			
		||||
                    await addItems(inventory, [
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType: "/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartA",
 | 
			
		||||
                            ItemCount: -1
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType: "/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartB",
 | 
			
		||||
                            ItemCount: -1
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType: "/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartC",
 | 
			
		||||
                            ItemCount: -1
 | 
			
		||||
                        }
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Game/CrewShip/Ships/RailJack": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Recipes/Railjack/RailjackCephalonBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
 | 
			
		||||
                    await unlockShipFeature(inventory, recipe.resultType);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Items/ShipDecos/MummyQuestVessel": {
 | 
			
		||||
            const gearItem = inventory.Consumables.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Keys/MummyQuest/MummyArtifact01GearItem"
 | 
			
		||||
            );
 | 
			
		||||
            if (gearItem && gearItem.ItemCount > 0) {
 | 
			
		||||
                await addItem(inventory, gearItem.ItemType, gearItem.ItemCount * -1);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Types/Recipes/WarframeRecipes/ConcreteFrameBlueprint": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Gameplay/EntratiLab/Quest/GargoyleRecipeItem"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    await addItem(inventory, recipe.resultType);
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case "/Lotus/Upgrades/Skins/Umbra/UmbraAltHelmet": {
 | 
			
		||||
            const recipeItem = inventory.Recipes.find(
 | 
			
		||||
                i => i.ItemType == "/Lotus/Types/Recipes/WarframeRecipes/ExcaliburUmbraBlueprint"
 | 
			
		||||
            );
 | 
			
		||||
            if (recipeItem && recipeItem.ItemCount > 0) {
 | 
			
		||||
                const recipe = ExportRecipes[recipeItem.ItemType];
 | 
			
		||||
                if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
 | 
			
		||||
                    const umbraModA = (
 | 
			
		||||
                        await addItem(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            "/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
 | 
			
		||||
                            1,
 | 
			
		||||
                            false,
 | 
			
		||||
                            undefined,
 | 
			
		||||
                            `{"lvl":5}`
 | 
			
		||||
                        )
 | 
			
		||||
                    ).Upgrades![0];
 | 
			
		||||
                    const umbraModB = (
 | 
			
		||||
                        await addItem(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            "/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
 | 
			
		||||
                            1,
 | 
			
		||||
                            false,
 | 
			
		||||
                            undefined,
 | 
			
		||||
                            `{"lvl":5}`
 | 
			
		||||
                        )
 | 
			
		||||
                    ).Upgrades![0];
 | 
			
		||||
                    const umbraModC = (
 | 
			
		||||
                        await addItem(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            "/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
 | 
			
		||||
                            1,
 | 
			
		||||
                            false,
 | 
			
		||||
                            undefined,
 | 
			
		||||
                            `{"lvl":5}`
 | 
			
		||||
                        )
 | 
			
		||||
                    ).Upgrades![0];
 | 
			
		||||
                    const sacrificeModA = (
 | 
			
		||||
                        await addItem(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            "/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
 | 
			
		||||
                            1,
 | 
			
		||||
                            false,
 | 
			
		||||
                            undefined,
 | 
			
		||||
                            `{"lvl":5}`
 | 
			
		||||
                        )
 | 
			
		||||
                    ).Upgrades![0];
 | 
			
		||||
                    const sacrificeModB = (
 | 
			
		||||
                        await addItem(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            "/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
 | 
			
		||||
                            1,
 | 
			
		||||
                            false,
 | 
			
		||||
                            undefined,
 | 
			
		||||
                            `{"lvl":5}`
 | 
			
		||||
                        )
 | 
			
		||||
                    ).Upgrades![0];
 | 
			
		||||
 | 
			
		||||
                    await addPowerSuit(inventory, "/Lotus/Powersuits/Excalibur/ExcaliburUmbra", {
 | 
			
		||||
                        Configs: [
 | 
			
		||||
                            {
 | 
			
		||||
                                Upgrades: [
 | 
			
		||||
                                    "",
 | 
			
		||||
                                    "",
 | 
			
		||||
                                    "",
 | 
			
		||||
                                    "",
 | 
			
		||||
                                    "",
 | 
			
		||||
                                    umbraModA.ItemId.$oid,
 | 
			
		||||
                                    umbraModB.ItemId.$oid,
 | 
			
		||||
                                    umbraModC.ItemId.$oid
 | 
			
		||||
                                ]
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        XP: 900_000,
 | 
			
		||||
                        Features: EquipmentFeatures.DOUBLE_CAPACITY
 | 
			
		||||
                    });
 | 
			
		||||
                    inventory.XPInfo.push({
 | 
			
		||||
                        ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
 | 
			
		||||
                        XP: 900_000
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    addEquipment(inventory, "Melee", "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana", {
 | 
			
		||||
                        Configs: [
 | 
			
		||||
                            {
 | 
			
		||||
                                Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid]
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        XP: 450_000,
 | 
			
		||||
                        Features: EquipmentFeatures.DOUBLE_CAPACITY
 | 
			
		||||
                    });
 | 
			
		||||
                    inventory.XPInfo.push({
 | 
			
		||||
                        ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
 | 
			
		||||
                        XP: 450_000
 | 
			
		||||
                    });
 | 
			
		||||
                    if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3777,7 +3777,10 @@ export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string
 | 
			
		||||
            valid_values: Object.keys(nightwaveTagToSeason)
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) {
 | 
			
		||||
    if (!buildLabel || version_compare(buildLabel, "2025.10.14.16.10") >= 0) {
 | 
			
		||||
        return "RadioLegionIntermission14Syndicate";
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2025.05.20.10.18") >= 0) {
 | 
			
		||||
        return "RadioLegionIntermission13Syndicate";
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2025.02.05.11.19") >= 0) {
 | 
			
		||||
@ -3787,6 +3790,7 @@ export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const nightwaveTagToSeason: Record<string, number> = {
 | 
			
		||||
    RadioLegionIntermission14Syndicate: 16, // Nora's Mix: Dreams of the Dead
 | 
			
		||||
    RadioLegionIntermission13Syndicate: 15, // Nora's Mix Vol. 9
 | 
			
		||||
    RadioLegionIntermission12Syndicate: 14, // Nora's Mix Vol. 8
 | 
			
		||||
    RadioLegionIntermission11Syndicate: 13, // Nora's Mix Vol. 7
 | 
			
		||||
 | 
			
		||||
@ -1474,6 +1474,7 @@
 | 
			
		||||
                                    <label class="form-label" for="worldState.nightwaveOverride" data-loc="worldState_nightwaveOverride"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.nightwaveOverride" data-default="">
 | 
			
		||||
                                        <option value="" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="RadioLegionIntermission14Syndicate" data-loc="worldState_RadioLegionIntermission14Syndicate"></option>
 | 
			
		||||
                                        <option value="RadioLegionIntermission13Syndicate" data-loc="worldState_RadioLegionIntermission13Syndicate"></option>
 | 
			
		||||
                                        <option value="RadioLegionIntermission12Syndicate" data-loc="worldState_RadioLegionIntermission12Syndicate"></option>
 | 
			
		||||
                                        <option value="RadioLegionIntermission11Syndicate" data-loc="worldState_RadioLegionIntermission11Syndicate"></option>
 | 
			
		||||
 | 
			
		||||
@ -1542,14 +1542,17 @@ function updateInventory() {
 | 
			
		||||
 | 
			
		||||
                if (item) {
 | 
			
		||||
                    document.getElementById("detailedView-loading").classList.add("d-none");
 | 
			
		||||
 | 
			
		||||
                    const itemName = itemMap[item.ItemType]?.name ?? item.ItemType;
 | 
			
		||||
                    if (item.ItemName) {
 | 
			
		||||
                        $("#detailedView-title").text(item.ItemName);
 | 
			
		||||
                        $("#detailedView-route .text-body-secondary").text(
 | 
			
		||||
                            itemMap[item.ItemType]?.name ?? item.ItemType
 | 
			
		||||
                        );
 | 
			
		||||
                        const pipeIndex = item.ItemName.indexOf("|");
 | 
			
		||||
                        if (pipeIndex != -1) {
 | 
			
		||||
                            $("#detailedView-title").text(item.ItemName.substr(1 + pipeIndex) + " " + itemName);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            $("#detailedView-title").text(item.ItemName);
 | 
			
		||||
                            $("#detailedView-route .text-body-secondary").text(itemName);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $("#detailedView-title").text(itemMap[item.ItemType]?.name ?? item.ItemType);
 | 
			
		||||
                        $("#detailedView-title").text(itemName);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (category == "Suits") {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// German translation by Animan8000
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers im Spielchat, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
 | 
			
		||||
    general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. durch Besuch eines Dojo/Relais oder durch erneutes Anmelden.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `Hinweis: Möglicherweise musst du ein Menü neu öffnen, damit die Änderungen sichtbar werden.`,
 | 
			
		||||
    general_addButton: `Hinzufügen`,
 | 
			
		||||
    general_setButton: `Festlegen`,
 | 
			
		||||
@ -51,7 +51,7 @@ dict = {
 | 
			
		||||
    code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden.`,
 | 
			
		||||
    code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`,
 | 
			
		||||
    code_succImport: `Erfolgreich importiert.`,
 | 
			
		||||
    code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`,
 | 
			
		||||
    code_succRelog: `Fertig. Bitte beachte, dass du dich neu anmelden musst, um Änderungen im Spiel zu sehen.`,
 | 
			
		||||
    code_nothingToDo: `Fertig. Es gab nichts zu tun.`,
 | 
			
		||||
    code_gild: `Veredeln`,
 | 
			
		||||
    code_moa: `Moa`,
 | 
			
		||||
@ -135,7 +135,7 @@ dict = {
 | 
			
		||||
    inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
 | 
			
		||||
    inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
 | 
			
		||||
    inventory_maxPlexus: `Plexus auf Max. Rang`,
 | 
			
		||||
    inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
 | 
			
		||||
    inventory_removeIsNew: `Entferne Ausrufezeichen bei neuem Equipment`,
 | 
			
		||||
 | 
			
		||||
    quests_list: `Quests`,
 | 
			
		||||
    quests_completeAll: `Alle Quests abschließen`,
 | 
			
		||||
@ -200,9 +200,9 @@ dict = {
 | 
			
		||||
    cheats_skipTutorial: `Tutorial überspringen`,
 | 
			
		||||
    cheats_skipAllDialogue: `Alle Dialoge überspringen`,
 | 
			
		||||
    cheats_unlockAllScans: `Alle Scans freischalten`,
 | 
			
		||||
    cheats_unlockSuccRelog: `Erfolgreich. Bitte beachte, dass du dich neu einloggen musst, damit der Client dies aktualisiert.`,
 | 
			
		||||
    cheats_unlockSuccRelog: `Erfolgreich. Bitte beachte, dass du dich neu anmelden musst, damit der Client dies aktualisiert.`,
 | 
			
		||||
    cheats_unlockAllMissions: `Alle Missionen freischalten`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu anmelden musst, damit die Sternenkarte aktualisiert wird.`,
 | 
			
		||||
    cheats_infiniteCredits: `Unendlich Credits`,
 | 
			
		||||
    cheats_infinitePlatinum: `Unendlich Platinum`,
 | 
			
		||||
    cheats_infiniteEndo: `Unendlich Endo`,
 | 
			
		||||
@ -214,7 +214,7 @@ dict = {
 | 
			
		||||
    cheats_dontSubtractPurchaseItemCost: `Gegenstände beim Kauf nicht verbrauchen`,
 | 
			
		||||
    cheats_dontSubtractPurchaseStandingCost: `Ansehen beim Kauf nicht verbrauchen`,
 | 
			
		||||
    cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
 | 
			
		||||
    cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
 | 
			
		||||
    cheats_dontSubtractConsumables: `Verbrauchsgüter (Ausrüstung) nicht verbrauchen`,
 | 
			
		||||
    cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
 | 
			
		||||
    cheats_unlockAllSkins: `Alle Skins freischalten`,
 | 
			
		||||
    cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`,
 | 
			
		||||
@ -234,7 +234,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro hat volles Inventar`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Erfolgreich. Bitte beachte, dass du dein Inventar neu synchronisieren musst, z. B. mit dem /sync Befehl des Bootstrappers im Spielchat, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Erfolgreich. Bitte beachte, dass du dein Inventar neu synchronisieren musst, z. B. durch Besuch eines Dojo/Relais oder durch erneutes Anmelden.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
@ -324,6 +324,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `Trauer`,
 | 
			
		||||
    worldState_fear: `Angst`,
 | 
			
		||||
    worldState_nightwaveOverride: `Nightwave-Überschreibung`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `Noras Mix: Träume der Toten`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `Noras Mix - Vol. 9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `Noras Mix - Vol. 8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `Noras Mix - Vol. 7`,
 | 
			
		||||
@ -385,10 +386,10 @@ dict = {
 | 
			
		||||
    upgrade_AvatarAbilityRange: `+7.5% Fähigkeitsreichweite`,
 | 
			
		||||
    upgrade_AvatarAbilityEfficiency: `+5% Fähigkeitseffizienz`,
 | 
			
		||||
    upgrade_AvatarEnergyRegen: `+0.5 Energieregeneration pro Sekunde`,
 | 
			
		||||
    upgrade_AvatarEnemyRadar: `+5m Feindradar`,
 | 
			
		||||
    upgrade_AvatarEnemyRadar: `+5m Gegnerradar`,
 | 
			
		||||
    upgrade_AvatarLootRadar: `+7m Beuteradar`,
 | 
			
		||||
    upgrade_WeaponAmmoMax: `+15% Max. Munition`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `-3% Rüstung bei Feinden`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `-3% Rüstung für Gegner`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `+100% Magazinfüllung für Primär- und Sekundärwaffen bei Gnadenstoß`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `+100% Gesundheitskugel Chance bei Gnadenstoß`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `+50% Energiekugel Chance bei Gnadenstoß`,
 | 
			
		||||
@ -398,7 +399,7 @@ dict = {
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `+60% Parkourgeschwindigkeit für 15s nach Gnadenstoß`,
 | 
			
		||||
    upgrade_AvatarTimeLimitIncrease: `+8s extra Zeit beim Hacken`,
 | 
			
		||||
    upgrade_ElectrifyOnHack: `Setze beim Hacken Gegner innerhalb von 20m unter Strom`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Feinde innerhalb von 15m vor Furcht für 8s kauern`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Gegner innerhalb von 15m vor Furcht für 8s kauern`,
 | 
			
		||||
    upgrade_OnHackLockers: `Schließe nach dem Hacken 5 Spinde innerhalb von 20m auf`,
 | 
			
		||||
    upgrade_OnExecutionBlind: `Blende bei einem Gnadenstoß Gegner innerhalb von 18m`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `Nächste Fähigkeit erhält +50% Fähigkeitsstärke nach Gnadenstoß`,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
			
		||||
    general_addButton: `Add`,
 | 
			
		||||
    general_setButton: `Set`,
 | 
			
		||||
@ -233,7 +233,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro Fully Stocked`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
 | 
			
		||||
@ -323,6 +323,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `Sorrow`,
 | 
			
		||||
    worldState_fear: `Fear`,
 | 
			
		||||
    worldState_nightwaveOverride: `Nightwave Override`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `Nora's Mix: Dreams of the Dead`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `Nora's Mix Vol. 9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `Nora's Mix Vol. 8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `Nora's Mix Vol. 7`,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// Spanish translation by hxedcl, Slayer55555
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `Nota: Puede que necesites reabrir cualquier menú en el que te encuentres para que los cambios se reflejen.`,
 | 
			
		||||
    general_addButton: `Agregar`,
 | 
			
		||||
    general_setButton: `Establecer`,
 | 
			
		||||
@ -234,7 +234,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro con stock completo`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Desbloquea todas las etapas del Roba-ganancias`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Éxito. Ten en cuenta que deberás volver a sincronizar tu inventario. Para hacerlo, puedes usar el comando /sync en el Bootstrapper, visitar un dojo o repetidor, o volver a iniciar sesión.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`,
 | 
			
		||||
@ -324,6 +324,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `Tristeza`,
 | 
			
		||||
    worldState_fear: `Miedo`,
 | 
			
		||||
    worldState_nightwaveOverride: `Volúmen de Onda Nocturna`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// French translation by Vitruvio
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Note : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`,
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `Note : Rouvrir un menu est nécessaire pour voir les changements.`,
 | 
			
		||||
    general_addButton: `Ajouter`,
 | 
			
		||||
    general_setButton: `Définir`,
 | 
			
		||||
@ -234,7 +234,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Stock de Baro au max`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Succès. Une resynchronisation est nécessaire en tapant "/sync" dans le tchat, en visitant un relais ou en se reconnectant.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
 | 
			
		||||
@ -324,6 +324,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `hagrin`,
 | 
			
		||||
    worldState_fear: `Peur`,
 | 
			
		||||
    worldState_nightwaveOverride: `Saison d'Ondes Nocturnes`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// Russian translation by AMelonInsideLemon, LoseFace
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду загрузчика /sync в чате игры, посетив Додзё/Реле или перезагрузив игру.`,
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`,
 | 
			
		||||
    general_addButton: `Добавить`,
 | 
			
		||||
    general_setButton: `Установить`,
 | 
			
		||||
@ -234,7 +234,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Баро полностью укомплектован`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды загрузчика /sync в чате игры, посетив Додзё/Реле или повторно войдя в игру.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
 | 
			
		||||
@ -324,6 +324,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `Печаль`,
 | 
			
		||||
    worldState_fear: `Страх`,
 | 
			
		||||
    worldState_nightwaveOverride: `Сезон Ночной волны`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `Микс Норы, Диск 9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `Микс Норы, Диск 8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `Микс Норы, Диск 7`,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// Ukrainian translation by LoseFace
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або перезавантаживши гру.`,
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`,
 | 
			
		||||
    general_addButton: `Добавити`,
 | 
			
		||||
    general_setButton: `Встановити`,
 | 
			
		||||
@ -234,7 +234,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Баро повністю укомплектований`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
 | 
			
		||||
@ -324,6 +324,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `Журба`,
 | 
			
		||||
    worldState_fear: `Страх`,
 | 
			
		||||
    worldState_nightwaveOverride: `Сезон Нічної хвилі`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `Вибірка Нори 9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `Вибірка Нори 8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `Вибірка Нори 7`,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, qingchun
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
			
		||||
    general_addButton: `添加`,
 | 
			
		||||
    general_setButton: `设置`,
 | 
			
		||||
@ -234,7 +234,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
 | 
			
		||||
@ -324,6 +324,7 @@ dict = {
 | 
			
		||||
    worldState_sorrow: `悲伤`,
 | 
			
		||||
    worldState_fear: `恐惧`,
 | 
			
		||||
    worldState_nightwaveOverride: `午夜电波系列`,
 | 
			
		||||
    worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
 | 
			
		||||
    worldState_RadioLegionIntermission13Syndicate: `诺拉的混选VOL.9`,
 | 
			
		||||
    worldState_RadioLegionIntermission12Syndicate: `诺拉的混选VOL.8`,
 | 
			
		||||
    worldState_RadioLegionIntermission11Syndicate: `诺拉的混选VOL.7`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user