Compare commits
	
		
			16 Commits
		
	
	
		
			0a3f9549a9
			...
			9c55a8a4aa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9c55a8a4aa | |||
| 253ae09f24 | |||
| 703e9007b0 | |||
| 3e555b1753 | |||
| 1066b4a983 | |||
| b9a2cea862 | |||
| 0342f52359 | |||
| ea9012bd56 | |||
| 13400b6d83 | |||
| 8d57eda9d2 | |||
| 6b66cb495b | |||
| f4f7ed00d1 | |||
| 18556cb2f5 | |||
| 648af9ae18 | |||
| e16da9da44 | |||
| 4d8dbd99aa | 
@ -31,7 +31,8 @@
 | 
			
		||||
        "no-mixed-spaces-and-tabs": "error",
 | 
			
		||||
        "@typescript-eslint/require-await": "error",
 | 
			
		||||
        "import/no-named-as-default-member": "off",
 | 
			
		||||
        "import/no-cycle": "warn"
 | 
			
		||||
        "import/no-cycle": "warn",
 | 
			
		||||
        "@typescript-eslint/no-deprecated": "warn"
 | 
			
		||||
    },
 | 
			
		||||
    "parser": "@typescript-eslint/parser",
 | 
			
		||||
    "parserOptions": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								AGENTS.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								AGENTS.md
									
									
									
									
									
								
							@ -1,17 +0,0 @@
 | 
			
		||||
## In General
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
 | 
			
		||||
Use `npm i` or `npm ci` to install all dependencies.
 | 
			
		||||
 | 
			
		||||
### Testing
 | 
			
		||||
 | 
			
		||||
Use `npm run verify` to verify that your changes pass TypeScript's checks.
 | 
			
		||||
 | 
			
		||||
### Formatting
 | 
			
		||||
 | 
			
		||||
Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure.
 | 
			
		||||
 | 
			
		||||
## WebUI Specific
 | 
			
		||||
 | 
			
		||||
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context.
 | 
			
		||||
							
								
								
									
										19
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
## In General
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
 | 
			
		||||
Use `npm i` or `npm ci` to install all dependencies, including dev dependencies.
 | 
			
		||||
 | 
			
		||||
## Development Process
 | 
			
		||||
 | 
			
		||||
Auto reloading is supported for server and WebUI development. Simply use `npm run dev` or `npm run dev:bun` to start the server and edit away.
 | 
			
		||||
 | 
			
		||||
### Testing
 | 
			
		||||
 | 
			
		||||
Before submitting a PR:
 | 
			
		||||
- Use `npm run verify` to verify that the code is type-safe.
 | 
			
		||||
- Use `npm run fix` to fix formatting issues as well as be informed of any unfixable issues. Avoid introducing new warnings.
 | 
			
		||||
 | 
			
		||||
## WebUI Specific
 | 
			
		||||
 | 
			
		||||
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT provide translations generated by AI or other automated tools.
 | 
			
		||||
@ -6,14 +6,15 @@ More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://di
 | 
			
		||||
 | 
			
		||||
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
 | 
			
		||||
 | 
			
		||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
 | 
			
		||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
 | 
			
		||||
 | 
			
		||||
## config.json
 | 
			
		||||
 | 
			
		||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
 | 
			
		||||
 | 
			
		||||
- `skipTutorial` affects only newly created accounts, so you may wish to change it before logging in for the first time.
 | 
			
		||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
 | 
			
		||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
 | 
			
		||||
- `ircAddress`, `hubAddress`, and `nrsAddress` are not present by default but can be provided if these secondary servers are on a different machine.
 | 
			
		||||
- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
 | 
			
		||||
- `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.
 | 
			
		||||
 | 
			
		||||
@ -14,13 +14,18 @@ if %errorlevel% == 0 (
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	echo Updating dependencies...
 | 
			
		||||
	call npm i --omit=dev
 | 
			
		||||
 | 
			
		||||
	call npm run build
 | 
			
		||||
	node scripts/raw-precheck.js > NUL
 | 
			
		||||
	if %errorlevel% == 0 (
 | 
			
		||||
		call npm run start
 | 
			
		||||
		echo SpaceNinjaServer seems to have crashed.
 | 
			
		||||
		call npm i --omit=dev --omit=optional
 | 
			
		||||
		call npm run raw
 | 
			
		||||
	) else (
 | 
			
		||||
		call npm i --omit=dev
 | 
			
		||||
		call npm run build
 | 
			
		||||
		if %errorlevel% == 0 (
 | 
			
		||||
			call npm run start
 | 
			
		||||
		)
 | 
			
		||||
	)
 | 
			
		||||
	echo SpaceNinjaServer seems to have crashed.
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
:a
 | 
			
		||||
 | 
			
		||||
@ -14,11 +14,16 @@ if [ $? -eq 0 ]; then
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "Updating dependencies..."
 | 
			
		||||
    npm i --omit=dev
 | 
			
		||||
 | 
			
		||||
    npm run build
 | 
			
		||||
    node scripts/raw-precheck.js > /dev/null
 | 
			
		||||
    if [ $? -eq 0 ]; then
 | 
			
		||||
        npm run start
 | 
			
		||||
        echo "SpaceNinjaServer seems to have crashed."
 | 
			
		||||
        npm i --omit=dev --omit=optional
 | 
			
		||||
        npm run raw
 | 
			
		||||
    else
 | 
			
		||||
        npm i --omit=dev
 | 
			
		||||
        npm run build
 | 
			
		||||
        if [ $? -eq 0 ]; then
 | 
			
		||||
            npm run start
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
    echo "SpaceNinjaServer seems to have crashed."
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -33,6 +33,9 @@
 | 
			
		||||
        "prettier": "^3.5.3",
 | 
			
		||||
        "tree-kill": "^1.2.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=20.18.1"
 | 
			
		||||
      },
 | 
			
		||||
      "optionalDependencies": {
 | 
			
		||||
        "@types/express": "^5",
 | 
			
		||||
        "@types/morgan": "^1.9.9",
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@
 | 
			
		||||
    "dev:bun": "bun scripts/dev.cjs",
 | 
			
		||||
    "verify": "tsgo --noEmit",
 | 
			
		||||
    "verify:tsc": "tsc --noEmit",
 | 
			
		||||
    "raw": "node --experimental-transform-types src/index.ts",
 | 
			
		||||
    "raw": "node scripts/raw-precheck.js && node --experimental-transform-types src/index.ts",
 | 
			
		||||
    "raw:bun": "bun src/index.ts",
 | 
			
		||||
    "lint": "eslint --ext .ts .",
 | 
			
		||||
    "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,17 @@ args.push("--dev");
 | 
			
		||||
args.push("--secret");
 | 
			
		||||
args.push(secret);
 | 
			
		||||
 | 
			
		||||
const cangoraw = (() => {
 | 
			
		||||
    if (process.versions.bun) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
 | 
			
		||||
    if (major > 22 || (major == 22 && minor >= 7)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
let buildproc, runproc;
 | 
			
		||||
const spawnopts = { stdio: "inherit", shell: true };
 | 
			
		||||
function run(changedFile) {
 | 
			
		||||
@ -29,7 +40,7 @@ function run(changedFile) {
 | 
			
		||||
        runproc = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
 | 
			
		||||
    const thisbuildproc = spawn("npm", ["run", cangoraw ? "verify" : "build:dev"], spawnopts);
 | 
			
		||||
    const thisbuildstart = Date.now();
 | 
			
		||||
    buildproc = thisbuildproc;
 | 
			
		||||
    buildproc.on("exit", code => {
 | 
			
		||||
@ -38,8 +49,12 @@ function run(changedFile) {
 | 
			
		||||
        }
 | 
			
		||||
        buildproc = undefined;
 | 
			
		||||
        if (code === 0) {
 | 
			
		||||
            console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
 | 
			
		||||
            runproc = spawn("npm", ["run", process.versions.bun ? "raw:bun" : "start", "--", ...args], spawnopts);
 | 
			
		||||
            console.log(`${cangoraw ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
 | 
			
		||||
            runproc = spawn(
 | 
			
		||||
                "npm",
 | 
			
		||||
                ["run", cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start", "--", ...args],
 | 
			
		||||
                spawnopts
 | 
			
		||||
            );
 | 
			
		||||
            runproc.on("exit", () => {
 | 
			
		||||
                runproc = undefined;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								scripts/raw-precheck.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								scripts/raw-precheck.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
 | 
			
		||||
if (major > 22 || (major == 22 && minor >= 7)) {
 | 
			
		||||
    // ok
 | 
			
		||||
} else {
 | 
			
		||||
    console.log("Sorry, your Node version is a bit too old for this. You have 2 options:");
 | 
			
		||||
    console.log("- Update Node.js.");
 | 
			
		||||
    console.log("- Use 'npm run build && npm run start'. Optional libraries must be installed for this.");
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
}
 | 
			
		||||
@ -231,7 +231,7 @@ interface ILensInstallRequest {
 | 
			
		||||
 | 
			
		||||
// Works for ways & upgrades
 | 
			
		||||
const focusTypeToPolarity = (type: string): TFocusPolarity => {
 | 
			
		||||
    return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;
 | 
			
		||||
    return ("AP_" + type.substring(1).split("/")[3].toUpperCase()) as TFocusPolarity;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const shardValues = {
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getReflexiveAddress } from "../../services/configService.ts";
 | 
			
		||||
import { config, getReflexiveAddress } from "../../services/configService.ts";
 | 
			
		||||
 | 
			
		||||
export const hubController: RequestHandler = (req, res) => {
 | 
			
		||||
    const { myAddress } = getReflexiveAddress(req);
 | 
			
		||||
    res.json(`hub ${myAddress}:6952`);
 | 
			
		||||
    res.json(`hub ${config.hubAddress ?? getReflexiveAddress(req).myAddress}:6952`);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
			
		||||
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
			
		||||
import type { ICountedItem } from "warframe-public-export-plus";
 | 
			
		||||
import { eFaction, ExportCustoms, ExportFlavour, ExportResources } from "warframe-public-export-plus";
 | 
			
		||||
import { ExportCustoms, ExportFlavour, ExportResources } from "warframe-public-export-plus";
 | 
			
		||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    addEmailItem,
 | 
			
		||||
@ -220,7 +220,10 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
			
		||||
                    }
 | 
			
		||||
                    await createMessage(account._id, [
 | 
			
		||||
                        {
 | 
			
		||||
                            sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
 | 
			
		||||
                            sndr:
 | 
			
		||||
                                factionSidedWith == "FC_GRINEER"
 | 
			
		||||
                                    ? "/Lotus/Language/Menu/GrineerInvasionLeader"
 | 
			
		||||
                                    : "/Lotus/Language/Menu/CorpusInvasionLeader",
 | 
			
		||||
                            msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
 | 
			
		||||
                            sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
 | 
			
		||||
                            countedAtt: battlePay,
 | 
			
		||||
 | 
			
		||||
@ -95,11 +95,11 @@ const createLoginResponse = (
 | 
			
		||||
        BuildLabel: buildLabel
 | 
			
		||||
    };
 | 
			
		||||
    if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
 | 
			
		||||
        resp.NRS = [myAddress];
 | 
			
		||||
        resp.NRS = [config.nrsAddress ?? myAddress];
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
 | 
			
		||||
        // U17 and up
 | 
			
		||||
        resp.IRC = config.myIrcAddresses ?? [myAddress];
 | 
			
		||||
        resp.IRC = [config.ircAddress ?? myAddress];
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
 | 
			
		||||
        // U24 and up
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,7 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*const toTitleCase = (str: string): string => {
 | 
			
		||||
    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
 | 
			
		||||
    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase());
 | 
			
		||||
};*/
 | 
			
		||||
 | 
			
		||||
const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
@ -221,7 +221,7 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
        }
 | 
			
		||||
        if (
 | 
			
		||||
            name &&
 | 
			
		||||
            uniqueName.substr(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
			
		||||
            uniqueName.substring(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
			
		||||
            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
 | 
			
		||||
        ) {
 | 
			
		||||
            res.miscitems.push({
 | 
			
		||||
@ -313,7 +313,7 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
                uniqueName,
 | 
			
		||||
                name: getString(arcane.name, lang)
 | 
			
		||||
            };
 | 
			
		||||
            if (arcane.isFrivolous) {
 | 
			
		||||
            if (arcane.excludeFromCodex) {
 | 
			
		||||
                mod.badReason = "frivolous";
 | 
			
		||||
            }
 | 
			
		||||
            res.mods.push(mod);
 | 
			
		||||
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
import type { SlotPurchase, SlotPurchaseName } from "../types/purchaseTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const slotPurchaseNameToSlotName: SlotPurchase = {
 | 
			
		||||
    SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
 | 
			
		||||
    TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
 | 
			
		||||
    TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
 | 
			
		||||
    SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
 | 
			
		||||
    TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
 | 
			
		||||
    MechSlotItem: { name: "MechBin", purchaseQuantity: 1 },
 | 
			
		||||
    TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", purchaseQuantity: 2 },
 | 
			
		||||
    RandomModSlotItem: { name: "RandomModBin", purchaseQuantity: 3 },
 | 
			
		||||
    TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", purchaseQuantity: 2 },
 | 
			
		||||
    CrewMemberSlotItem: { name: "CrewMemberBin", purchaseQuantity: 1 }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isSlotPurchaseName = (slotPurchaseName: string): slotPurchaseName is SlotPurchaseName => {
 | 
			
		||||
    return slotPurchaseName in slotPurchaseNameToSlotName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parseSlotPurchaseName = (slotPurchaseName: string): SlotPurchaseName => {
 | 
			
		||||
    if (!isSlotPurchaseName(slotPurchaseName)) throw new Error(`invalid slot name ${slotPurchaseName}`);
 | 
			
		||||
    return slotPurchaseName;
 | 
			
		||||
};
 | 
			
		||||
@ -14,8 +14,8 @@ cacheRouter.get(/^\/origin\/[a-zA-Z0-9]+\/[0-9]+\/H\.Cache\.bin.*$/, (req, res)
 | 
			
		||||
 | 
			
		||||
cacheRouter.get(/^\/0\/.+!.+$/, async (req, res) => {
 | 
			
		||||
    try {
 | 
			
		||||
        const dir = req.path.substr(0, req.path.lastIndexOf("/"));
 | 
			
		||||
        const file = req.path.substr(dir.length + 1);
 | 
			
		||||
        const dir = req.path.substring(0, req.path.lastIndexOf("/"));
 | 
			
		||||
        const file = req.path.substring(dir.length + 1);
 | 
			
		||||
        const filePath = `static/data${dir}/${file}`;
 | 
			
		||||
 | 
			
		||||
        // Return file if we have it
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,9 @@ export interface IConfig {
 | 
			
		||||
    myAddress: string;
 | 
			
		||||
    httpPort?: number;
 | 
			
		||||
    httpsPort?: number;
 | 
			
		||||
    myIrcAddresses?: string[];
 | 
			
		||||
    ircAddress?: string;
 | 
			
		||||
    hubAddress?: string;
 | 
			
		||||
    nrsAddress?: string;
 | 
			
		||||
    administratorNames?: string[];
 | 
			
		||||
    autoCreateAccount?: boolean;
 | 
			
		||||
    skipTutorial?: boolean;
 | 
			
		||||
@ -84,6 +86,7 @@ export interface IConfig {
 | 
			
		||||
 | 
			
		||||
export const configRemovedOptionsKeys = [
 | 
			
		||||
    "NRS",
 | 
			
		||||
    "myIrcAddresses",
 | 
			
		||||
    "skipAllDialogue",
 | 
			
		||||
    "infiniteCredits",
 | 
			
		||||
    "infinitePlatinum",
 | 
			
		||||
 | 
			
		||||
@ -687,10 +687,10 @@ export const addItem = async (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Path-based duck typing
 | 
			
		||||
    switch (typeName.substr(1).split("/")[1]) {
 | 
			
		||||
    switch (typeName.substring(1).split("/")[1]) {
 | 
			
		||||
        case "Powersuits":
 | 
			
		||||
            if (typeName.endsWith("AugmentCard")) break;
 | 
			
		||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
			
		||||
            switch (typeName.substring(1).split("/")[2]) {
 | 
			
		||||
                default: {
 | 
			
		||||
                    return {
 | 
			
		||||
                        ...(await addPowerSuit(inventory, typeName, {
 | 
			
		||||
@ -725,7 +725,7 @@ export const addItem = async (
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case "Upgrades": {
 | 
			
		||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
			
		||||
            switch (typeName.substring(1).split("/")[2]) {
 | 
			
		||||
                case "Mods": // Legendary Core
 | 
			
		||||
                case "CosmeticEnhancers": // Traumatic Peculiar
 | 
			
		||||
                    {
 | 
			
		||||
@ -782,12 +782,12 @@ export const addItem = async (
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "Types":
 | 
			
		||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
			
		||||
            switch (typeName.substring(1).split("/")[2]) {
 | 
			
		||||
                case "Sentinels": {
 | 
			
		||||
                    return addSentinel(inventory, typeName, premiumPurchase);
 | 
			
		||||
                }
 | 
			
		||||
                case "Game": {
 | 
			
		||||
                    if (typeName.substr(1).split("/")[3] == "Projections") {
 | 
			
		||||
                    if (typeName.substring(1).split("/")[3] == "Projections") {
 | 
			
		||||
                        // Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
 | 
			
		||||
                        const miscItemChanges = [
 | 
			
		||||
                            {
 | 
			
		||||
@ -801,8 +801,8 @@ export const addItem = async (
 | 
			
		||||
                            MiscItems: miscItemChanges
 | 
			
		||||
                        };
 | 
			
		||||
                    } else if (
 | 
			
		||||
                        typeName.substr(1).split("/")[3] == "CatbrowPet" ||
 | 
			
		||||
                        typeName.substr(1).split("/")[3] == "KubrowPet"
 | 
			
		||||
                        typeName.substring(1).split("/")[3] == "CatbrowPet" ||
 | 
			
		||||
                        typeName.substring(1).split("/")[3] == "KubrowPet"
 | 
			
		||||
                    ) {
 | 
			
		||||
                        if (
 | 
			
		||||
                            typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
 | 
			
		||||
@ -826,7 +826,7 @@ export const addItem = async (
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case "Items": {
 | 
			
		||||
                    if (typeName.substr(1).split("/")[3] == "Emotes") {
 | 
			
		||||
                    if (typeName.substring(1).split("/")[3] == "Emotes") {
 | 
			
		||||
                        return addCustomization(inventory, typeName);
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
@ -875,8 +875,8 @@ export const addItem = async (
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case "Weapons": {
 | 
			
		||||
            if (typeName.substr(1).split("/")[4] == "MeleeTrees") break;
 | 
			
		||||
            const productCategory = typeName.substr(1).split("/")[3];
 | 
			
		||||
            if (typeName.substring(1).split("/")[4] == "MeleeTrees") break;
 | 
			
		||||
            const productCategory = typeName.substring(1).split("/")[3];
 | 
			
		||||
            switch (productCategory) {
 | 
			
		||||
                case "Pistols":
 | 
			
		||||
                case "LongGuns":
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { parseSlotPurchaseName, slotPurchaseNameToSlotName } from "../helpers/purchaseHelpers.ts";
 | 
			
		||||
import { getSubstringFromKeyword } from "../helpers/stringHelpers.ts";
 | 
			
		||||
import {
 | 
			
		||||
    addBooster,
 | 
			
		||||
@ -15,7 +14,8 @@ import type {
 | 
			
		||||
    IPurchaseRequest,
 | 
			
		||||
    IPurchaseResponse,
 | 
			
		||||
    IInventoryChanges,
 | 
			
		||||
    IPurchaseParams
 | 
			
		||||
    IPurchaseParams,
 | 
			
		||||
    SlotNames
 | 
			
		||||
} from "../types/purchaseTypes.ts";
 | 
			
		||||
import { PurchaseSource } from "../types/purchaseTypes.ts";
 | 
			
		||||
import { logger } from "../utils/logger.ts";
 | 
			
		||||
@ -489,6 +489,20 @@ export const handleStoreItemAcquisition = async (
 | 
			
		||||
    return purchaseResponse;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const slotPurchaseNameToSlotName: Record<string, { name: SlotNames; purchaseQuantity: number }> = {
 | 
			
		||||
    SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
 | 
			
		||||
    TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
 | 
			
		||||
    TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
 | 
			
		||||
    SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
 | 
			
		||||
    TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
 | 
			
		||||
    MechSlotItem: { name: "MechBin", purchaseQuantity: 1 },
 | 
			
		||||
    TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", purchaseQuantity: 2 },
 | 
			
		||||
    RandomModSlotItem: { name: "RandomModBin", purchaseQuantity: 3 },
 | 
			
		||||
    TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", purchaseQuantity: 2 },
 | 
			
		||||
    CrewMemberSlotItem: { name: "CrewMemberBin", purchaseQuantity: 1 },
 | 
			
		||||
    PvPLoadoutSlotItem: { name: "PvpBonusLoadoutBin", purchaseQuantity: 1 }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// // extra = everything above the base +2 slots (depending on slot type)
 | 
			
		||||
// // new slot above base = extra + 1 and slots +1
 | 
			
		||||
// // new frame = slots -1
 | 
			
		||||
@ -500,9 +514,8 @@ const handleSlotPurchase = (
 | 
			
		||||
    ignorePurchaseQuantity: boolean
 | 
			
		||||
): IPurchaseResponse => {
 | 
			
		||||
    logger.debug(`slot name ${slotPurchaseNameFull}`);
 | 
			
		||||
    const slotPurchaseName = parseSlotPurchaseName(
 | 
			
		||||
        slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1)
 | 
			
		||||
    );
 | 
			
		||||
    const slotPurchaseName = slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1);
 | 
			
		||||
    if (!(slotPurchaseName in slotPurchaseNameToSlotName)) throw new Error(`invalid slot name ${slotPurchaseName}`);
 | 
			
		||||
    logger.debug(`slot purchase name ${slotPurchaseName}`);
 | 
			
		||||
 | 
			
		||||
    const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name;
 | 
			
		||||
 | 
			
		||||
@ -120,18 +120,6 @@ export type IBinChanges = {
 | 
			
		||||
    Extra?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type SlotPurchaseName =
 | 
			
		||||
    | "SuitSlotItem"
 | 
			
		||||
    | "TwoSentinelSlotItem"
 | 
			
		||||
    | "TwoWeaponSlotItem"
 | 
			
		||||
    | "SpaceSuitSlotItem"
 | 
			
		||||
    | "TwoSpaceWeaponSlotItem"
 | 
			
		||||
    | "MechSlotItem"
 | 
			
		||||
    | "TwoOperatorWeaponSlotItem"
 | 
			
		||||
    | "RandomModSlotItem"
 | 
			
		||||
    | "TwoCrewShipSalvageSlotItem"
 | 
			
		||||
    | "CrewMemberSlotItem";
 | 
			
		||||
 | 
			
		||||
export const slotNames = [
 | 
			
		||||
    "SuitBin",
 | 
			
		||||
    "WeaponBin",
 | 
			
		||||
@ -148,7 +136,3 @@ export const slotNames = [
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
export type SlotNames = (typeof slotNames)[number];
 | 
			
		||||
 | 
			
		||||
export type SlotPurchase = {
 | 
			
		||||
    [P in SlotPurchaseName]: { name: SlotNames; purchaseQuantity: number };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2753,19 +2753,19 @@ async function doMaxPlexus() {
 | 
			
		||||
async function doUnlockAllScans() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllScans?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
    toast(loc("cheats_unlockSuccRelog"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function doUnlockAllShipFeatures() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllShipFeatures?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function doUnlockAllCapturaScenes() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function unlockAllMissions() {
 | 
			
		||||
@ -2777,13 +2777,13 @@ async function unlockAllMissions() {
 | 
			
		||||
async function unlockAllProfitTakerStages() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function unlockAllSimarisResearchEntries() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const importSamples = {
 | 
			
		||||
 | 
			
		||||
@ -171,11 +171,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
 | 
			
		||||
    mods_removeUnranked: `Mods ohne Rang entfernen`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
 | 
			
		||||
    cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
 | 
			
		||||
    cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>"|DISPLAYNAME|"</code> zu <code>administratorNames</code> in der config.json hinzu.`,
 | 
			
		||||
    cheats_server: `Server`,
 | 
			
		||||
    cheats_skipTutorial: `Tutorial überspringen`,
 | 
			
		||||
    cheats_skipAllDialogue: `Alle Dialoge überspringen`,
 | 
			
		||||
    cheats_unlockAllScans: `Alle Scans freischalten`,
 | 
			
		||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
			
		||||
    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_infiniteCredits: `Unendlich Credits`,
 | 
			
		||||
@ -212,7 +213,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro hat volles Inventar`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
 | 
			
		||||
    cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
 | 
			
		||||
@ -170,11 +170,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `Add Missing Unranked Mods`,
 | 
			
		||||
    mods_removeUnranked: `Remove Unranked Mods`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Add Missing Max Rank Mods`,
 | 
			
		||||
    cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`,
 | 
			
		||||
    cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>"|DISPLAYNAME|"</code> to <code>administratorNames</code> in the config.json.`,
 | 
			
		||||
    cheats_server: `Server`,
 | 
			
		||||
    cheats_skipTutorial: `Skip Tutorial`,
 | 
			
		||||
    cheats_skipAllDialogue: `Skip All Dialogue`,
 | 
			
		||||
    cheats_unlockAllScans: `Unlock All Scans`,
 | 
			
		||||
    cheats_unlockSuccRelog: `Success. Please that you'll need to relog for the client to refresh this.`,
 | 
			
		||||
    cheats_unlockAllMissions: `Unlock All Missions`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
 | 
			
		||||
    cheats_infiniteCredits: `Infinite Credits`,
 | 
			
		||||
@ -211,7 +212,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro Fully Stocked`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_unlockSucc: `Successfully unlocked.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, 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`,
 | 
			
		||||
 | 
			
		||||
@ -79,8 +79,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `Trucos`,
 | 
			
		||||
    navbar_import: `Importar`,
 | 
			
		||||
    inventory_addItems: `Agregar objetos`,
 | 
			
		||||
    inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
 | 
			
		||||
    inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
 | 
			
		||||
    inventory_addItemByItemType: `Sin refinar`,
 | 
			
		||||
    inventory_addItemByItemType_warning: `Usa esta función bajo tu propio riesgo. Podría dañar tu inventario, y tendrías que eliminar los objetos manualmente si algo sale mal.`,
 | 
			
		||||
    inventory_suits: `Warframes`,
 | 
			
		||||
    inventory_longGuns: `Armas primarias`,
 | 
			
		||||
    inventory_pistols: `Armas secundarias`,
 | 
			
		||||
@ -171,11 +171,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `Agregar mods sin rango faltantes`,
 | 
			
		||||
    mods_removeUnranked: `Quitar mods sin rango`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Agregar mods de rango máximo faltantes`,
 | 
			
		||||
    cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`,
 | 
			
		||||
    cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>"|DISPLAYNAME|"</code> a <code>administratorNames</code> en el archivo config.json.`,
 | 
			
		||||
    cheats_server: `Servidor`,
 | 
			
		||||
    cheats_skipTutorial: `Omitir tutorial`,
 | 
			
		||||
    cheats_skipAllDialogue: `Omitir todos los diálogos`,
 | 
			
		||||
    cheats_unlockAllScans: `Desbloquear todos los escaneos`,
 | 
			
		||||
    cheats_unlockSuccRelog: `Éxito. Ten en cuenta que deberás volver a iniciar sesión para que el cliente se actualice.`,
 | 
			
		||||
    cheats_unlockAllMissions: `Desbloquear todas las misiones`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Éxito. Ten en cuenta que deberás entrar a un dojo, repetidor o volver a iniciar sesión para que el cliente actualice el mapa estelar.`,
 | 
			
		||||
    cheats_infiniteCredits: `Créditos infinitos`,
 | 
			
		||||
@ -212,7 +213,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro con stock completo`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Desbloquea todas las etapas del Roba-ganancias`,
 | 
			
		||||
    cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
 | 
			
		||||
    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_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`,
 | 
			
		||||
@ -241,7 +242,7 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
 | 
			
		||||
    cheats_changeButton: `Cambiar`,
 | 
			
		||||
    cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `Finaliza Invasión en una mision`,
 | 
			
		||||
 | 
			
		||||
    worldState: `Estado del mundo`,
 | 
			
		||||
    worldState_creditBoost: `Potenciador de Créditos`,
 | 
			
		||||
 | 
			
		||||
@ -171,11 +171,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
 | 
			
		||||
    mods_removeUnranked: `Retirer les mods sans rang`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
 | 
			
		||||
    cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
 | 
			
		||||
    cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>"|DISPLAYNAME|"</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
 | 
			
		||||
    cheats_server: `Serveur`,
 | 
			
		||||
    cheats_skipTutorial: `Passer le tutoriel`,
 | 
			
		||||
    cheats_skipAllDialogue: `Passer les dialogues`,
 | 
			
		||||
    cheats_unlockAllScans: `Débloquer tous les scans`,
 | 
			
		||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
			
		||||
    cheats_unlockAllMissions: `Débloquer toutes les missions`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Succès. Une actualisation de l'inventaire est nécessaire.`,
 | 
			
		||||
    cheats_infiniteCredits: `Crédits infinis`,
 | 
			
		||||
@ -212,7 +213,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_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, 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`,
 | 
			
		||||
 | 
			
		||||
@ -171,11 +171,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `Добавить недостающие моды без ранга`,
 | 
			
		||||
    mods_removeUnranked: `Удалить моды без ранга`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Добавить недостающие моды макс. ранга`,
 | 
			
		||||
    cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
 | 
			
		||||
    cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>"|DISPLAYNAME|"</code> в <code>administratorNames</code> в config.json.`,
 | 
			
		||||
    cheats_server: `Сервер`,
 | 
			
		||||
    cheats_skipTutorial: `Пропустить обучение`,
 | 
			
		||||
    cheats_skipAllDialogue: `Пропустить все диалоги`,
 | 
			
		||||
    cheats_unlockAllScans: `Разблокировать все сканирования`,
 | 
			
		||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
			
		||||
    cheats_unlockAllMissions: `Разблокировать все миссии`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Успех. Пожалуйста, обратите внимание, что вам нужно будет войти в Додзё/Реле или перезайти, чтобы клиент обновил звездную карту.`,
 | 
			
		||||
    cheats_infiniteCredits: `Бесконечные Кредиты`,
 | 
			
		||||
@ -212,7 +213,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Баро полностью укомплектован`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
 | 
			
		||||
    cheats_unlockSucc: `Успешно разблокировано.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
 | 
			
		||||
 | 
			
		||||
@ -171,11 +171,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `Добавити недостаючі модифікатори без рівня`,
 | 
			
		||||
    mods_removeUnranked: `Видалити модифікатори без рівня`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Добавити недостаючі модифікатори макс. рівня`,
 | 
			
		||||
    cheats_administratorRequirement: `Ви повинні бути адміністратором для використання цієї функції. Щоб стати адміністратором, додайте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
 | 
			
		||||
    cheats_administratorRequirement: `Ви повинні бути адміністратором для використання цієї функції. Щоб стати адміністратором, додайте <code>"|DISPLAYNAME|"</code> в <code>administratorNames</code> в config.json.`,
 | 
			
		||||
    cheats_server: `Сервер`,
 | 
			
		||||
    cheats_skipTutorial: `Пропустити навчання`,
 | 
			
		||||
    cheats_skipAllDialogue: `Пропустити всі діалоги`,
 | 
			
		||||
    cheats_unlockAllScans: `Розблокувати всі сканування`,
 | 
			
		||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
			
		||||
    cheats_unlockAllMissions: `Розблокувати всі місії`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `Успіх. Будь ласка, зверніть увагу, що вам потрібно буде увійти в Доджьо/Реле або перезайти, щоб клієнт оновив Зоряну мапу.`,
 | 
			
		||||
    cheats_infiniteCredits: `Бескінечні Кредити`,
 | 
			
		||||
@ -212,7 +213,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Баро повністю укомплектований`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
 | 
			
		||||
    cheats_unlockSucc: `Успішно розблоковано.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
 | 
			
		||||
 | 
			
		||||
@ -171,11 +171,12 @@ dict = {
 | 
			
		||||
    mods_addMissingUnrankedMods: `添加所有缺失的Mods`,
 | 
			
		||||
    mods_removeUnranked: `删除所有未升级的Mods`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`,
 | 
			
		||||
    cheats_administratorRequirement: `您必须是管理员才能使用此功能.要成为管理员,请在 config.json 中将 <code>|DISPLAYNAME|</code> 添加到 <code>administratorNames</code>`,
 | 
			
		||||
    cheats_administratorRequirement: `您必须是管理员才能使用此功能.要成为管理员,请在 config.json 中将 <code>"|DISPLAYNAME|"</code> 添加到 <code>administratorNames</code>`,
 | 
			
		||||
    cheats_server: `服务器`,
 | 
			
		||||
    cheats_skipTutorial: `跳过教程`,
 | 
			
		||||
    cheats_skipAllDialogue: `跳过所有对话`,
 | 
			
		||||
    cheats_unlockAllScans: `解锁所有扫描`,
 | 
			
		||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
			
		||||
    cheats_unlockAllMissions: `解锁所有星图`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`,
 | 
			
		||||
    cheats_infiniteCredits: `无限现金`,
 | 
			
		||||
@ -212,7 +213,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
 | 
			
		||||
    cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
 | 
			
		||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user