Compare commits
	
		
			38 Commits
		
	
	
		
			9ef852190c
			...
			11c085fcd7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					11c085fcd7 | ||
| 
						 | 
					bbbc554939 | ||
| 
						 | 
					7ea9460b11 | ||
| 
						 | 
					83b44e8e13 | ||
| 
						 | 
					542694b576 | ||
| 
						 | 
					94df06490f | ||
| 
						 | 
					29afa68065 | ||
| 
						 | 
					008b45df30 | ||
| 
						 | 
					65fbd9c5fc | ||
| 
						 | 
					4f0c9e9695 | ||
| 
						 | 
					259bfd5800 | ||
| 
						 | 
					4d03246e16 | ||
| 9662da00de | |||
| 662d824369 | |||
| a0bac12e95 | |||
| e98cb2ec24 | |||
| b5c6c3e485 | |||
| fa65ba3f25 | |||
| 0c54c064eb | |||
| b4e789bf0d | |||
| 9add016d7b | |||
| a2171c80a5 | |||
| 5a2fa2c2c3 | |||
| 4b2b184b8f | |||
| dc401de1e9 | |||
| 1439fdc083 | |||
| 6771a129f5 | |||
| f13de810e5 | |||
| c52f7dcedc | |||
| 0bf142ed50 | |||
| 0d791ad145 | |||
| 5396eefe75 | |||
| a9a197b005 | |||
| 1ade801e7c | |||
| c9cc1fa089 | |||
| 287acab892 | |||
| 30398021b3 | |||
| 15578b04d2 | 
@ -3,3 +3,6 @@
 | 
			
		||||
Dockerfile*
 | 
			
		||||
.*
 | 
			
		||||
docker-data/
 | 
			
		||||
node_modules/
 | 
			
		||||
static/data/
 | 
			
		||||
logs/
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -12,14 +12,13 @@ jobs:
 | 
			
		||||
            - name: Setup Node.js environment
 | 
			
		||||
              uses: actions/setup-node@v4.0.2
 | 
			
		||||
              with:
 | 
			
		||||
                  node-version: ">=20.6.0"
 | 
			
		||||
                  node-version: ">=20.18.1"
 | 
			
		||||
            - run: npm ci
 | 
			
		||||
            - run: cp config-vanilla.json config.json
 | 
			
		||||
            - run: npm run verify
 | 
			
		||||
            - run: npm run lint:ci
 | 
			
		||||
            - run: npm run prettier
 | 
			
		||||
            - run: npm run update-translations
 | 
			
		||||
            - run: npm run fix-imports
 | 
			
		||||
            - name: Fail if there are uncommitted changes
 | 
			
		||||
              run: |
 | 
			
		||||
                  if [[ -n "$(git status --porcelain)" ]]; then
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
  "typescript.preferences.preferTypeOnlyAutoImports": true
 | 
			
		||||
}
 | 
			
		||||
@ -5,8 +5,7 @@ RUN apk add --no-cache bash jq
 | 
			
		||||
COPY . /app
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
RUN npm i --omit=dev
 | 
			
		||||
RUN npm run build
 | 
			
		||||
RUN npm i --omit=dev --omit=optional
 | 
			
		||||
RUN date '+%d %B %Y' > BUILD_DATE
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
 | 
			
		||||
 | 
			
		||||
@ -2,24 +2,27 @@
 | 
			
		||||
 | 
			
		||||
echo Updating SpaceNinjaServer...
 | 
			
		||||
git fetch --prune
 | 
			
		||||
git stash
 | 
			
		||||
git checkout -f origin/main
 | 
			
		||||
if %errorlevel% == 0 (
 | 
			
		||||
	git stash
 | 
			
		||||
	git checkout -f origin/main
 | 
			
		||||
 | 
			
		||||
if exist static\data\0\ (
 | 
			
		||||
	if exist static\data\0\ (
 | 
			
		||||
		echo Updating stripped assets...
 | 
			
		||||
		cd static\data\0\
 | 
			
		||||
		git pull
 | 
			
		||||
		cd ..\..\..\
 | 
			
		||||
)
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
echo Updating dependencies...
 | 
			
		||||
call npm i --omit=dev
 | 
			
		||||
	echo Updating dependencies...
 | 
			
		||||
	call npm i --omit=dev
 | 
			
		||||
 | 
			
		||||
call npm run build
 | 
			
		||||
if %errorlevel% == 0 (
 | 
			
		||||
	call npm run build
 | 
			
		||||
	if %errorlevel% == 0 (
 | 
			
		||||
		call npm run start
 | 
			
		||||
		echo SpaceNinjaServer seems to have crashed.
 | 
			
		||||
	)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
:a
 | 
			
		||||
pause > nul
 | 
			
		||||
goto a
 | 
			
		||||
 | 
			
		||||
@ -2,22 +2,23 @@
 | 
			
		||||
 | 
			
		||||
echo "Updating SpaceNinjaServer..."
 | 
			
		||||
git fetch --prune
 | 
			
		||||
git stash
 | 
			
		||||
git checkout -f origin/main
 | 
			
		||||
if [ $? -eq 0 ]; then
 | 
			
		||||
    git stash
 | 
			
		||||
    git checkout -f origin/main
 | 
			
		||||
 | 
			
		||||
if [ -d "static/data/0/" ]; then
 | 
			
		||||
    if [ -d "static/data/0/" ]; then
 | 
			
		||||
        echo "Updating stripped assets..."
 | 
			
		||||
        cd static/data/0/
 | 
			
		||||
        git pull
 | 
			
		||||
        cd ../../../
 | 
			
		||||
fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
echo "Updating dependencies..."
 | 
			
		||||
npm i --omit=dev
 | 
			
		||||
    echo "Updating dependencies..."
 | 
			
		||||
    npm i --omit=dev
 | 
			
		||||
 | 
			
		||||
npm run build
 | 
			
		||||
if [ $? -eq 0 ]; then
 | 
			
		||||
    npm run build
 | 
			
		||||
    if [ $? -eq 0 ]; then
 | 
			
		||||
        npm run start
 | 
			
		||||
        echo "SpaceNinjaServer seems to have crashed."
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@
 | 
			
		||||
  "administratorNames": [],
 | 
			
		||||
  "autoCreateAccount": true,
 | 
			
		||||
  "skipTutorial": false,
 | 
			
		||||
  "skipAllDialogue": false,
 | 
			
		||||
  "unlockAllScans": false,
 | 
			
		||||
  "unlockAllShipFeatures": false,
 | 
			
		||||
  "unlockAllShipDecorations": false,
 | 
			
		||||
@ -19,9 +18,6 @@
 | 
			
		||||
  "unlockAllSkins": false,
 | 
			
		||||
  "unlockAllCapturaScenes": false,
 | 
			
		||||
  "fullyStockedVendors": false,
 | 
			
		||||
  "baroAlwaysAvailable": false,
 | 
			
		||||
  "baroFullyStocked": false,
 | 
			
		||||
  "unlockAllProfitTakerStages": false,
 | 
			
		||||
  "skipClanKeyCrafting": false,
 | 
			
		||||
  "noDojoRoomBuildStage": false,
 | 
			
		||||
  "noDecoBuildStage": false,
 | 
			
		||||
@ -29,9 +25,7 @@
 | 
			
		||||
  "noDojoResearchCosts": false,
 | 
			
		||||
  "noDojoResearchTime": false,
 | 
			
		||||
  "fastClanAscension": false,
 | 
			
		||||
  "missionsCanGiveAllRelics": false,
 | 
			
		||||
  "unlockAllSimarisResearchEntries": false,
 | 
			
		||||
  "disableDailyTribute": false,
 | 
			
		||||
  "spoofMasteryRank": -1,
 | 
			
		||||
  "relicRewardItemCountMultiplier": 1,
 | 
			
		||||
  "nightwaveStandingMultiplier": 1,
 | 
			
		||||
@ -45,6 +39,10 @@
 | 
			
		||||
    "affinityBoost": false,
 | 
			
		||||
    "resourceBoost": false,
 | 
			
		||||
    "tennoLiveRelay": false,
 | 
			
		||||
    "baroTennoConRelay": false,
 | 
			
		||||
    "baroAlwaysAvailable": false,
 | 
			
		||||
    "baroFullyStocked": false,
 | 
			
		||||
    "varziaFullyStocked": false,
 | 
			
		||||
    "wolfHunt": false,
 | 
			
		||||
    "orphixVenom": false,
 | 
			
		||||
    "longShadow": false,
 | 
			
		||||
@ -71,10 +69,9 @@
 | 
			
		||||
    "duviriOverride": "",
 | 
			
		||||
    "nightwaveOverride": "",
 | 
			
		||||
    "allTheFissures": "",
 | 
			
		||||
    "circuitGameModes": null,
 | 
			
		||||
    "darvoStockMultiplier": 1,
 | 
			
		||||
    "varziaOverride": "",
 | 
			
		||||
    "varziaFullyStocked": false
 | 
			
		||||
    "circuitGameModes": null,
 | 
			
		||||
    "darvoStockMultiplier": 1
 | 
			
		||||
  },
 | 
			
		||||
  "dev": {
 | 
			
		||||
    "keepVendorsExpired": false
 | 
			
		||||
 | 
			
		||||
@ -5,4 +5,4 @@ if [ ! -f conf/config.json ]; then
 | 
			
		||||
	jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
exec npm run start -- --configPath conf/config.json
 | 
			
		||||
exec npm run raw -- --configPath conf/config.json
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										41
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										41
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -9,11 +9,6 @@
 | 
			
		||||
      "version": "0.1.0",
 | 
			
		||||
      "license": "GNU",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/express": "^5",
 | 
			
		||||
        "@types/morgan": "^1.9.9",
 | 
			
		||||
        "@types/websocket": "^1.0.10",
 | 
			
		||||
        "@types/ws": "^8.18.1",
 | 
			
		||||
        "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
			
		||||
        "chokidar": "^4.0.3",
 | 
			
		||||
        "crc-32": "^1.2.2",
 | 
			
		||||
        "express": "^5",
 | 
			
		||||
@ -21,7 +16,6 @@
 | 
			
		||||
        "mongoose": "^8.11.0",
 | 
			
		||||
        "morgan": "^1.10.0",
 | 
			
		||||
        "ncp": "^2.0.0",
 | 
			
		||||
        "typescript": "^5.7",
 | 
			
		||||
        "undici": "^7.10.0",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.83",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
@ -38,6 +32,14 @@
 | 
			
		||||
        "eslint-plugin-prettier": "^5.2.5",
 | 
			
		||||
        "prettier": "^3.5.3",
 | 
			
		||||
        "tree-kill": "^1.2.2"
 | 
			
		||||
      },
 | 
			
		||||
      "optionalDependencies": {
 | 
			
		||||
        "@types/express": "^5",
 | 
			
		||||
        "@types/morgan": "^1.9.9",
 | 
			
		||||
        "@types/websocket": "^1.0.10",
 | 
			
		||||
        "@types/ws": "^8.18.1",
 | 
			
		||||
        "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
			
		||||
        "typescript": "^5.7"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@colors/colors": {
 | 
			
		||||
@ -349,6 +351,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
 | 
			
		||||
      "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/connect": "*",
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
@ -359,6 +362,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
 | 
			
		||||
      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
@ -368,6 +372,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/body-parser": "*",
 | 
			
		||||
        "@types/express-serve-static-core": "^5.0.0",
 | 
			
		||||
@ -379,6 +384,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz",
 | 
			
		||||
      "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*",
 | 
			
		||||
        "@types/qs": "*",
 | 
			
		||||
@ -390,7 +396,8 @@
 | 
			
		||||
      "version": "2.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/json5": {
 | 
			
		||||
      "version": "0.0.29",
 | 
			
		||||
@ -403,13 +410,15 @@
 | 
			
		||||
      "version": "1.3.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
 | 
			
		||||
      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/morgan": {
 | 
			
		||||
      "version": "1.9.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz",
 | 
			
		||||
      "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
@ -419,6 +428,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "undici-types": "~7.10.0"
 | 
			
		||||
      }
 | 
			
		||||
@ -427,19 +437,22 @@
 | 
			
		||||
      "version": "6.14.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
 | 
			
		||||
      "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/range-parser": {
 | 
			
		||||
      "version": "1.2.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
 | 
			
		||||
      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/send": {
 | 
			
		||||
      "version": "0.17.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
 | 
			
		||||
      "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mime": "^1",
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
@ -450,6 +463,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
 | 
			
		||||
      "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/http-errors": "*",
 | 
			
		||||
        "@types/node": "*",
 | 
			
		||||
@ -473,6 +487,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz",
 | 
			
		||||
      "integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
@ -491,6 +506,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
@ -735,6 +751,7 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250826.1.tgz",
 | 
			
		||||
      "integrity": "sha512-+NuzOfk/lu6pLYSCio+R7uzJ9pfOasc1fshxVmLp6wgcB8yuUYYvBaT7CoHapUnNBYZXkJ9u0UOECnq3dbzgSQ==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "tsgo": "bin/tsgo.js"
 | 
			
		||||
      },
 | 
			
		||||
@ -5397,6 +5414,7 @@
 | 
			
		||||
      "version": "5.9.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
 | 
			
		||||
      "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "tsc": "bin/tsc",
 | 
			
		||||
@ -5438,7 +5456,8 @@
 | 
			
		||||
      "version": "7.10.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
 | 
			
		||||
      "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/unpipe": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								package.json
									
									
									
									
									
								
							@ -22,17 +22,11 @@
 | 
			
		||||
    "lint:fix": "eslint --fix --ext .ts .",
 | 
			
		||||
    "prettier": "prettier --write .",
 | 
			
		||||
    "update-translations": "cd scripts && node update-translations.cjs",
 | 
			
		||||
    "fix-imports": "cd scripts && node fix-imports.cjs",
 | 
			
		||||
    "fix": "npm run update-translations && npm run fix-imports && npm run prettier"
 | 
			
		||||
    "fix": "npm run update-translations && npm run lint:fix"
 | 
			
		||||
  },
 | 
			
		||||
  "license": "GNU",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@types/express": "^5",
 | 
			
		||||
    "@types/morgan": "^1.9.9",
 | 
			
		||||
    "@types/websocket": "^1.0.10",
 | 
			
		||||
    "@types/ws": "^8.18.1",
 | 
			
		||||
    "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
			
		||||
    "chokidar": "^4.0.3",
 | 
			
		||||
    "crc-32": "^1.2.2",
 | 
			
		||||
    "express": "^5",
 | 
			
		||||
@ -40,7 +34,6 @@
 | 
			
		||||
    "mongoose": "^8.11.0",
 | 
			
		||||
    "morgan": "^1.10.0",
 | 
			
		||||
    "ncp": "^2.0.0",
 | 
			
		||||
    "typescript": "^5.7",
 | 
			
		||||
    "undici": "^7.10.0",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.83",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
@ -48,6 +41,14 @@
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
    "ws": "^8.18.2"
 | 
			
		||||
  },
 | 
			
		||||
  "optionalDependencies": {
 | 
			
		||||
    "@types/express": "^5",
 | 
			
		||||
    "@types/morgan": "^1.9.9",
 | 
			
		||||
    "@types/websocket": "^1.0.10",
 | 
			
		||||
    "@types/ws": "^8.18.1",
 | 
			
		||||
    "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
			
		||||
    "typescript": "^5.7"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^8.28.0",
 | 
			
		||||
@ -57,5 +58,8 @@
 | 
			
		||||
    "eslint-plugin-prettier": "^5.2.5",
 | 
			
		||||
    "prettier": "^3.5.3",
 | 
			
		||||
    "tree-kill": "^1.2.2"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">=20.18.1"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,75 +0,0 @@
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
 | 
			
		||||
const root = path.join(process.cwd(), "..");
 | 
			
		||||
 | 
			
		||||
function listFiles(dir) {
 | 
			
		||||
    const entries = fs.readdirSync(dir, { withFileTypes: true });
 | 
			
		||||
    let results = [];
 | 
			
		||||
    for (const entry of entries) {
 | 
			
		||||
        const fullPath = path.join(dir, entry.name);
 | 
			
		||||
        if (entry.isDirectory()) {
 | 
			
		||||
            results = results.concat(listFiles(fullPath));
 | 
			
		||||
        } else {
 | 
			
		||||
            results.push(fullPath);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return results;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const files = listFiles(path.join(root, "src"));
 | 
			
		||||
 | 
			
		||||
for (const file of files) {
 | 
			
		||||
    let content;
 | 
			
		||||
    try {
 | 
			
		||||
        content = fs.readFileSync(file, "utf8");
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        continue;
 | 
			
		||||
    }
 | 
			
		||||
    const dir = path.dirname(file);
 | 
			
		||||
    const fixedContent = content.replaceAll(/from "([^"]+)";/g, (sub, importPath) => {
 | 
			
		||||
        if (importPath.startsWith("@/") || importPath.startsWith(".")) {
 | 
			
		||||
            const base = importPath.startsWith("@/")
 | 
			
		||||
                ? path.join(root, importPath.slice(2))
 | 
			
		||||
                : path.resolve(dir, importPath);
 | 
			
		||||
            let target = base;
 | 
			
		||||
 | 
			
		||||
            if (fs.existsSync(target)) {
 | 
			
		||||
                const stat = fs.statSync(target);
 | 
			
		||||
                if (stat.isDirectory()) {
 | 
			
		||||
                    if (fs.existsSync(path.join(target, "index.ts"))) {
 | 
			
		||||
                        target = path.join(target, "index.ts");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return sub;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    const ext = path.extname(target);
 | 
			
		||||
                    if (!ext) {
 | 
			
		||||
                        target += ".ts";
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else if (fs.existsSync(target + ".ts")) {
 | 
			
		||||
                target += ".ts";
 | 
			
		||||
            } else if (fs.existsSync(path.join(target, "index.ts"))) {
 | 
			
		||||
                target = path.join(target, "index.ts");
 | 
			
		||||
            } else {
 | 
			
		||||
                return sub;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let relative = path.relative(dir, target).replace(/\\/g, "/");
 | 
			
		||||
            if (!path.extname(relative)) {
 | 
			
		||||
                relative += ".ts";
 | 
			
		||||
            }
 | 
			
		||||
            if (!relative.startsWith(".")) {
 | 
			
		||||
                relative = "./" + relative;
 | 
			
		||||
            }
 | 
			
		||||
            console.log(`${importPath} -> ${relative}`);
 | 
			
		||||
            return sub.split(importPath).join(relative);
 | 
			
		||||
        }
 | 
			
		||||
        return sub;
 | 
			
		||||
    });
 | 
			
		||||
    if (content != fixedContent) {
 | 
			
		||||
        fs.writeFileSync(file, fixedContent, "utf8");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -31,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
			
		||||
            const strings = extractStrings(line);
 | 
			
		||||
            if (Object.keys(strings).length > 0) {
 | 
			
		||||
                Object.entries(strings).forEach(([key, value]) => {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED]")) {
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`${targetStrings[key]}\`,\n`);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,6 @@ import { payRouter } from "./routes/pay.ts";
 | 
			
		||||
import { statsRouter } from "./routes/stats.ts";
 | 
			
		||||
import { webuiRouter } from "./routes/webui.ts";
 | 
			
		||||
 | 
			
		||||
import { worldStateController } from "./controllers/dynamic/worldStateController.ts";
 | 
			
		||||
 | 
			
		||||
const app = express();
 | 
			
		||||
 | 
			
		||||
app.use((req, _res, next) => {
 | 
			
		||||
@ -47,9 +45,6 @@ app.use("/pay", payRouter);
 | 
			
		||||
app.use("/stats", statsRouter);
 | 
			
		||||
app.use("/", webuiRouter);
 | 
			
		||||
 | 
			
		||||
// U39.1+ gets worldState from that location
 | 
			
		||||
app.get("/worldState.php", worldStateController);
 | 
			
		||||
 | 
			
		||||
app.use(unknownEndpointHandler);
 | 
			
		||||
app.use(errorHandler);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,8 +4,9 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
import { getRecipe } from "../../services/itemDataService.ts";
 | 
			
		||||
import type { IOid, IOidWithLegacySupport } from "../../types/commonTypes.ts";
 | 
			
		||||
import type { IOidWithLegacySupport } from "../../types/commonTypes.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import type { TAccountDocument } from "../../services/loginService.ts";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    getInventory,
 | 
			
		||||
@ -21,23 +22,32 @@ import {
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
import type { IPendingRecipeDatabase } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { toOid2 } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import { fromOid, toOid2 } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
 | 
			
		||||
import type { IRecipe } from "warframe-public-export-plus";
 | 
			
		||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
 | 
			
		||||
import { EquipmentFeatures, Status } from "../../types/equipmentTypes.ts";
 | 
			
		||||
 | 
			
		||||
interface IClaimCompletedRecipeRequest {
 | 
			
		||||
    RecipeIds: IOid[];
 | 
			
		||||
    RecipeIds: IOidWithLegacySupport[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IClaimCompletedRecipeResponse {
 | 
			
		||||
    InventoryChanges: IInventoryChanges;
 | 
			
		||||
    BrandedSuits?: IOidWithLegacySupport[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(account._id.toString());
 | 
			
		||||
    const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
 | 
			
		||||
    const resp: IClaimCompletedRecipeResponse = {
 | 
			
		||||
        InventoryChanges: {}
 | 
			
		||||
    };
 | 
			
		||||
    for (const recipeId of claimCompletedRecipeRequest.RecipeIds) {
 | 
			
		||||
        const pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId));
 | 
			
		||||
        if (!pendingRecipe) {
 | 
			
		||||
        throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
 | 
			
		||||
            throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //check recipe is indeed ready to be completed
 | 
			
		||||
@ -57,17 +67,30 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
            await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
            res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
 | 
			
		||||
    } else {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush);
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json(resp);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const claimCompletedRecipe = async (
 | 
			
		||||
    account: TAccountDocument,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    recipe: IRecipe,
 | 
			
		||||
    pendingRecipe: IPendingRecipeDatabase,
 | 
			
		||||
    resp: IClaimCompletedRecipeResponse,
 | 
			
		||||
    rush: any
 | 
			
		||||
): Promise<void> => {
 | 
			
		||||
    logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
			
		||||
 | 
			
		||||
        let BrandedSuits: undefined | IOidWithLegacySupport[];
 | 
			
		||||
    if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
			
		||||
        inventory.PendingSpectreLoadouts ??= [];
 | 
			
		||||
        inventory.SpectreLoadouts ??= [];
 | 
			
		||||
 | 
			
		||||
            const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
 | 
			
		||||
                x => x.ItemType == recipe.resultType
 | 
			
		||||
            );
 | 
			
		||||
        const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
 | 
			
		||||
        if (pendingLoadoutIndex != -1) {
 | 
			
		||||
            const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
 | 
			
		||||
            if (loadoutIndex != -1) {
 | 
			
		||||
@ -85,10 +108,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
            inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
 | 
			
		||||
            1
 | 
			
		||||
        );
 | 
			
		||||
            BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
 | 
			
		||||
        resp.BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        let InventoryChanges: IInventoryChanges = {};
 | 
			
		||||
    if (recipe.consumeOnUse) {
 | 
			
		||||
        addRecipes(inventory, [
 | 
			
		||||
            {
 | 
			
		||||
@ -97,20 +119,16 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
        if (req.query.rush) {
 | 
			
		||||
 | 
			
		||||
    if (rush) {
 | 
			
		||||
        const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
 | 
			
		||||
        const start = end - recipe.buildTime;
 | 
			
		||||
        const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
 | 
			
		||||
        const progress = secondsElapsed / recipe.buildTime;
 | 
			
		||||
        logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
 | 
			
		||||
        const cost =
 | 
			
		||||
                progress > 0.5
 | 
			
		||||
                    ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)))
 | 
			
		||||
                    : recipe.skipBuildTimePrice;
 | 
			
		||||
            InventoryChanges = {
 | 
			
		||||
                ...InventoryChanges,
 | 
			
		||||
                ...updateCurrency(inventory, cost, true)
 | 
			
		||||
            };
 | 
			
		||||
            progress > 0.5 ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5))) : recipe.skipBuildTimePrice;
 | 
			
		||||
        combineInventoryChanges(resp.InventoryChanges, updateCurrency(inventory, cost, true));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
 | 
			
		||||
@ -128,7 +146,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
        pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
 | 
			
		||||
    } else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
 | 
			
		||||
        const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
 | 
			
		||||
            addKubrowPetPrint(inventory, pet, InventoryChanges);
 | 
			
		||||
        addKubrowPetPrint(inventory, pet, resp.InventoryChanges);
 | 
			
		||||
    } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
 | 
			
		||||
        if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
 | 
			
		||||
            // Quite the special case here...
 | 
			
		||||
@ -185,8 +203,8 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
                    `{"lvl":5}`
 | 
			
		||||
                )
 | 
			
		||||
            ).Upgrades![0];
 | 
			
		||||
                InventoryChanges.Upgrades ??= [];
 | 
			
		||||
                InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
 | 
			
		||||
            resp.InventoryChanges.Upgrades ??= [];
 | 
			
		||||
            resp.InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
 | 
			
		||||
 | 
			
		||||
            await addPowerSuit(
 | 
			
		||||
                inventory,
 | 
			
		||||
@ -209,7 +227,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
                    XP: 900_000,
 | 
			
		||||
                    Features: EquipmentFeatures.DOUBLE_CAPACITY
 | 
			
		||||
                },
 | 
			
		||||
                    InventoryChanges
 | 
			
		||||
                resp.InventoryChanges
 | 
			
		||||
            );
 | 
			
		||||
            inventory.XPInfo.push({
 | 
			
		||||
                ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
 | 
			
		||||
@ -227,34 +245,31 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
                    XP: 450_000,
 | 
			
		||||
                    Features: EquipmentFeatures.DOUBLE_CAPACITY
 | 
			
		||||
                },
 | 
			
		||||
                    InventoryChanges
 | 
			
		||||
                resp.InventoryChanges
 | 
			
		||||
            );
 | 
			
		||||
            inventory.XPInfo.push({
 | 
			
		||||
                ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
 | 
			
		||||
                XP: 450_000
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
                InventoryChanges = {
 | 
			
		||||
                    ...InventoryChanges,
 | 
			
		||||
                    ...(await addItem(
 | 
			
		||||
            combineInventoryChanges(
 | 
			
		||||
                resp.InventoryChanges,
 | 
			
		||||
                await addItem(
 | 
			
		||||
                    inventory,
 | 
			
		||||
                    recipe.resultType,
 | 
			
		||||
                    recipe.num,
 | 
			
		||||
                    false,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    pendingRecipe.TargetFingerprint
 | 
			
		||||
                    ))
 | 
			
		||||
                };
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (
 | 
			
		||||
        inventory.claimingBlueprintRefundsIngredients &&
 | 
			
		||||
        recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
 | 
			
		||||
    ) {
 | 
			
		||||
            await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
 | 
			
		||||
        }
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        res.json({ InventoryChanges, BrandedSuits });
 | 
			
		||||
        await refundRecipeIngredients(inventory, resp.InventoryChanges, recipe, pendingRecipe);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,7 @@ export const crewShipFusionController: RequestHandler = async (req, res) => {
 | 
			
		||||
            const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]);
 | 
			
		||||
            buffA.Value = Math.trunc(newFval * 0x3fffffff);
 | 
			
		||||
        }
 | 
			
		||||
        if (inferiorFingerprint.SubroutineIndex) {
 | 
			
		||||
        if (inferiorFingerprint.SubroutineIndex !== undefined) {
 | 
			
		||||
            const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
 | 
			
		||||
            if (!useSuperiorSubroutine) {
 | 
			
		||||
                fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;
 | 
			
		||||
 | 
			
		||||
@ -306,7 +306,7 @@ export const getInventoryResponse = async (
 | 
			
		||||
        inventoryResponse.PrimeTokens = 999999999;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.skipAllDialogue) {
 | 
			
		||||
    if (inventory.skipAllDialogue) {
 | 
			
		||||
        inventoryResponse.TauntHistory = [
 | 
			
		||||
            {
 | 
			
		||||
                node: "TreasureTutorial",
 | 
			
		||||
@ -486,42 +486,9 @@ export const getInventoryResponse = async (
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllProfitTakerStages) {
 | 
			
		||||
        inventoryResponse.CompletedJobChains ??= [];
 | 
			
		||||
        const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
 | 
			
		||||
        if (EudicoHeists) {
 | 
			
		||||
            EudicoHeists.Jobs = allEudicoHeistJobs;
 | 
			
		||||
        } else {
 | 
			
		||||
            inventoryResponse.CompletedJobChains.push({
 | 
			
		||||
                LocationTag: "EudicoHeists",
 | 
			
		||||
                Jobs: allEudicoHeistJobs
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllSimarisResearchEntries) {
 | 
			
		||||
        inventoryResponse.LibraryPersonalTarget = undefined;
 | 
			
		||||
        inventoryResponse.LibraryPersonalProgress = [
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research1Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research2Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research3Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research4Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research5Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research6Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research7Target"
 | 
			
		||||
        ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return inventoryResponse;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const allEudicoHeistJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const getExpRequiredForMr = (rank: number): number => {
 | 
			
		||||
    if (rank <= 30) {
 | 
			
		||||
        return 2500 * rank * rank;
 | 
			
		||||
 | 
			
		||||
@ -140,7 +140,11 @@ const createLoginResponse = (
 | 
			
		||||
        resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
 | 
			
		||||
        if (version_compare(buildLabel, "2025.08.26.09.49") >= 0) {
 | 
			
		||||
            resp.platformCDNs = [`${myUrlBase}/dynamic/`];
 | 
			
		||||
        } else {
 | 
			
		||||
            resp.platformCDNs = [`${myUrlBase}/`];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return resp;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ import {
 | 
			
		||||
    setAccountGotLoginRewardToday
 | 
			
		||||
} from "../../services/loginRewardService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
			
		||||
@ -17,20 +16,9 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
 | 
			
		||||
    const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
 | 
			
		||||
 | 
			
		||||
    if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
 | 
			
		||||
        res.json({
 | 
			
		||||
            DailyTributeInfo: {
 | 
			
		||||
                IsMilestoneDay: isMilestoneDay,
 | 
			
		||||
                IsChooseRewardSet: isLoginRewardAChoice(account),
 | 
			
		||||
                LoginDays: account.LoginDays,
 | 
			
		||||
                NextMilestoneReward: "",
 | 
			
		||||
                NextMilestoneDay: nextMilestoneDay
 | 
			
		||||
            }
 | 
			
		||||
        } satisfies ILoginRewardsReponse);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (today != account.LastLoginRewardDate) {
 | 
			
		||||
        const inventory = await getInventory(account._id.toString());
 | 
			
		||||
        if (!inventory.disableDailyTribute) {
 | 
			
		||||
            const randomRewards = getRandomLoginRewards(account, inventory);
 | 
			
		||||
            const response: ILoginRewardsReponse = {
 | 
			
		||||
                DailyTributeInfo: {
 | 
			
		||||
@ -54,4 +42,16 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
			
		||||
                sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
			
		||||
            }
 | 
			
		||||
            res.json(response);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    res.json({
 | 
			
		||||
        DailyTributeInfo: {
 | 
			
		||||
            IsMilestoneDay: isMilestoneDay,
 | 
			
		||||
            IsChooseRewardSet: isLoginRewardAChoice(account),
 | 
			
		||||
            LoginDays: account.LoginDays,
 | 
			
		||||
            NextMilestoneReward: "",
 | 
			
		||||
            NextMilestoneDay: nextMilestoneDay
 | 
			
		||||
        }
 | 
			
		||||
    } satisfies ILoginRewardsReponse);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -95,6 +95,16 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
			
		||||
        ConquestCompletedMissionsCount
 | 
			
		||||
    } = await addMissionRewards(account, inventory, missionReport, firstCompletion);
 | 
			
		||||
 | 
			
		||||
    const extraMissionRewards = inventory.extraMissionRewards ?? 0;
 | 
			
		||||
    if (extraMissionRewards >= 1) {
 | 
			
		||||
        for (let i = 0; i < extraMissionRewards; i++) {
 | 
			
		||||
            const rngMissionReport = missionReport;
 | 
			
		||||
            rngMissionReport.RewardInfo!.rewardSeed = generateRewardSeed();
 | 
			
		||||
            logger.debug("extra mission rewards with new seed, this might mismatch the mission report.");
 | 
			
		||||
            await addMissionRewards(account, inventory, rngMissionReport, firstCompletion);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (missionReport.EndOfMatchUpload) {
 | 
			
		||||
        inventory.RewardSeed = generateRewardSeed();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,9 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
        const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
			
		||||
        const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
			
		||||
 | 
			
		||||
        const fusionMultiplier = (await getInventory(account._id.toString(), "nemesisWeaponFusionMultiplier"))
 | 
			
		||||
            .nemesisWeaponFusionMultiplier;
 | 
			
		||||
 | 
			
		||||
        // Update destination damage type if desired
 | 
			
		||||
        if (body.UseSourceDmgType) {
 | 
			
		||||
            destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
 | 
			
		||||
@ -55,7 +58,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
        // Upgrade destination damage value
 | 
			
		||||
        const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
 | 
			
		||||
        const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
 | 
			
		||||
        let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
 | 
			
		||||
        let newDamage = Math.max(destDamage, sourceDamage) * 1.1 * (fusionMultiplier ?? 1);
 | 
			
		||||
        if (newDamage >= 0.5794998) {
 | 
			
		||||
            newDamage = 0.6;
 | 
			
		||||
        }
 | 
			
		||||
@ -101,9 +104,16 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
            "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
 | 
			
		||||
        );
 | 
			
		||||
        const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
 | 
			
		||||
        const alwaysCorrectCheat = (await getInventory(account._id.toString(), "nemesisAlwaysCorrect"))
 | 
			
		||||
            .nemesisAlwaysCorrect;
 | 
			
		||||
        if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
 | 
			
		||||
            const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
 | 
			
		||||
            const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
 | 
			
		||||
 | 
			
		||||
            if (alwaysCorrectCheat) {
 | 
			
		||||
                guess[0] = guess[1] = guess[2] = passcode;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
			
		||||
            const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
			
		||||
            const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
			
		||||
@ -149,7 +159,10 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                inventory.Nemesis!.HenchmenKilled += antivirusGain;
 | 
			
		||||
                const antivirusGainMultiplier = (
 | 
			
		||||
                    await getInventory(account._id.toString(), "nemesisAntivirusGainMultiplier")
 | 
			
		||||
                ).nemesisAntivirusGainMultiplier;
 | 
			
		||||
                inventory.Nemesis!.HenchmenKilled += antivirusGain * (antivirusGainMultiplier ?? 1);
 | 
			
		||||
                if (inventory.Nemesis!.HenchmenKilled >= 100) {
 | 
			
		||||
                    inventory.Nemesis!.HenchmenKilled = 100;
 | 
			
		||||
 | 
			
		||||
@ -195,7 +208,9 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
 | 
			
		||||
            // Evaluate guess
 | 
			
		||||
            const correct =
 | 
			
		||||
                body.guess == GUESS_WILDCARD || getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess;
 | 
			
		||||
                body.guess == GUESS_WILDCARD ||
 | 
			
		||||
                getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess ||
 | 
			
		||||
                alwaysCorrectCheat;
 | 
			
		||||
 | 
			
		||||
            // Update entry
 | 
			
		||||
            const guess = decodeNemesisGuess(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								src/controllers/api/upgradeOperatorController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/controllers/api/upgradeOperatorController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const upgradeOperatorController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(
 | 
			
		||||
        accountId,
 | 
			
		||||
        "OperatorCustomizationSlotPurchases PremiumCredits PremiumCreditsFree"
 | 
			
		||||
    );
 | 
			
		||||
    inventory.OperatorCustomizationSlotPurchases ??= 0;
 | 
			
		||||
    inventory.OperatorCustomizationSlotPurchases += 1;
 | 
			
		||||
    const inventoryChanges = updateCurrency(inventory, 10, true);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({ InventoryChanges: inventoryChanges });
 | 
			
		||||
};
 | 
			
		||||
@ -64,9 +64,9 @@ interface ItemLists {
 | 
			
		||||
 | 
			
		||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
			
		||||
    VPQ_BRONZE: "",
 | 
			
		||||
    VPQ_SILVER: " [Flawless]",
 | 
			
		||||
    VPQ_GOLD: " [Radiant]",
 | 
			
		||||
    VPQ_PLATINUM: " [Exceptional]"
 | 
			
		||||
    VPQ_SILVER: " [Exceptional]",
 | 
			
		||||
    VPQ_GOLD: " [Flawless]",
 | 
			
		||||
    VPQ_PLATINUM: " [Radiant]"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*const toTitleCase = (str: string): string => {
 | 
			
		||||
 | 
			
		||||
@ -7,12 +7,12 @@ export const setAccountCheatController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const payload = req.body as ISetAccountCheatRequest;
 | 
			
		||||
    const inventory = await getInventory(accountId, payload.key);
 | 
			
		||||
    inventory[payload.key] = payload.value;
 | 
			
		||||
    inventory[payload.key] = payload.value as never;
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ISetAccountCheatRequest {
 | 
			
		||||
    key: keyof IAccountCheats;
 | 
			
		||||
    value: boolean;
 | 
			
		||||
    value: boolean | number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
const allEudicoHeistJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const unlockAllProfitTakerStagesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "CompletedJobChains");
 | 
			
		||||
    inventory.CompletedJobChains ??= [];
 | 
			
		||||
    const chain = inventory.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
 | 
			
		||||
    if (chain) {
 | 
			
		||||
        chain.Jobs = allEudicoHeistJobs;
 | 
			
		||||
    } else {
 | 
			
		||||
        inventory.CompletedJobChains.push({ LocationTag: "EudicoHeists", Jobs: allEudicoHeistJobs });
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "LibraryPersonalTarget LibraryPersonalProgress");
 | 
			
		||||
    inventory.LibraryPersonalTarget = undefined;
 | 
			
		||||
    inventory.LibraryPersonalProgress = [
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research1Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research2Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research3Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research4Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research5Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research6Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research7Target"
 | 
			
		||||
    ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
@ -9,6 +9,7 @@ import { addMiscItems, combineInventoryChanges } from "../services/inventoryServ
 | 
			
		||||
import { handleStoreItemAcquisition } from "../services/purchaseService.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
 | 
			
		||||
import { config } from "../services/configService.ts";
 | 
			
		||||
import { log } from "winston";
 | 
			
		||||
 | 
			
		||||
export const crackRelic = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
@ -17,11 +18,11 @@ export const crackRelic = async (
 | 
			
		||||
): Promise<IRngResult> => {
 | 
			
		||||
    const relic = ExportRelics[participant.VoidProjection];
 | 
			
		||||
    let weights = refinementToWeights[relic.quality];
 | 
			
		||||
    if (relic.quality == "VPQ_SILVER" && config.exceptionalRelicsAlwaysGiveBronzeReward) {
 | 
			
		||||
    if (relic.quality == "VPQ_SILVER" && inventory.exceptionalRelicsAlwaysGiveBronzeReward) {
 | 
			
		||||
        weights = { COMMON: 1, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
 | 
			
		||||
    } else if (relic.quality == "VPQ_GOLD" && config.flawlessRelicsAlwaysGiveSilverReward) {
 | 
			
		||||
    } else if (relic.quality == "VPQ_GOLD" && inventory.flawlessRelicsAlwaysGiveSilverReward) {
 | 
			
		||||
        weights = { COMMON: 0, UNCOMMON: 1, RARE: 0, LEGENDARY: 0 };
 | 
			
		||||
    } else if (relic.quality == "VPQ_PLATINUM" && config.radiantRelicsAlwaysGiveGoldReward) {
 | 
			
		||||
    } else if (relic.quality == "VPQ_PLATINUM" && inventory.radiantRelicsAlwaysGiveGoldReward) {
 | 
			
		||||
        weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
 | 
			
		||||
    }
 | 
			
		||||
    logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
 | 
			
		||||
@ -54,6 +55,26 @@ export const crackRelic = async (
 | 
			
		||||
        (await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (inventory.crackRelicForPlatinum) {
 | 
			
		||||
        let platinumReward = 0;
 | 
			
		||||
        switch (reward.rarity) {
 | 
			
		||||
            case "COMMON":
 | 
			
		||||
                platinumReward = inventory.relicPlatinumCommon ?? 2;
 | 
			
		||||
                break;
 | 
			
		||||
            case "UNCOMMON":
 | 
			
		||||
                platinumReward = inventory.relicPlatinumUncommon ?? 5;
 | 
			
		||||
                break;
 | 
			
		||||
            case "RARE":
 | 
			
		||||
                platinumReward = inventory.relicPlatinumRare ?? 12;
 | 
			
		||||
                break;
 | 
			
		||||
            case "LEGENDARY":
 | 
			
		||||
                logger.warn(`got a legendary reward for a relic!`);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        logger.debug(`adding ${platinumReward} platinum to inventory for a ${reward.rarity} reward`);
 | 
			
		||||
        inventory.PremiumCredits += platinumReward;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return reward;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1428,12 +1428,14 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        accountOwnerId: Schema.Types.ObjectId,
 | 
			
		||||
 | 
			
		||||
        // SNS account cheats
 | 
			
		||||
        skipAllDialogue: Boolean,
 | 
			
		||||
        dontSubtractPurchaseCreditCost: Boolean,
 | 
			
		||||
        dontSubtractPurchasePlatinumCost: Boolean,
 | 
			
		||||
        dontSubtractPurchaseItemCost: Boolean,
 | 
			
		||||
        dontSubtractPurchaseStandingCost: Boolean,
 | 
			
		||||
        dontSubtractVoidTraces: Boolean,
 | 
			
		||||
        dontSubtractConsumables: Boolean,
 | 
			
		||||
        finishInvasionsInOneMission: Boolean,
 | 
			
		||||
        infiniteCredits: Boolean,
 | 
			
		||||
        infinitePlatinum: Boolean,
 | 
			
		||||
        infiniteEndo: Boolean,
 | 
			
		||||
@ -1455,6 +1457,27 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        claimingBlueprintRefundsIngredients: Boolean,
 | 
			
		||||
        instantResourceExtractorDrones: Boolean,
 | 
			
		||||
        noResourceExtractorDronesDamage: Boolean,
 | 
			
		||||
        missionsCanGiveAllRelics: Boolean,
 | 
			
		||||
        exceptionalRelicsAlwaysGiveBronzeReward: Boolean,
 | 
			
		||||
        flawlessRelicsAlwaysGiveSilverReward: Boolean,
 | 
			
		||||
        radiantRelicsAlwaysGiveGoldReward: Boolean,
 | 
			
		||||
        disableDailyTribute: Boolean,
 | 
			
		||||
        gainNoNegativeSyndicateStanding: Boolean,
 | 
			
		||||
        nemesisAlwaysCorrect: Boolean,
 | 
			
		||||
        nemesisHenchmenKillsMulptiplierGrineer: Number,
 | 
			
		||||
        nemesisHenchmenKillsMulptiplierCorpus: Number,
 | 
			
		||||
        nemesisAntivirusGainMultiplier: Number,
 | 
			
		||||
        nemesisHintProgressMultiplierGrineer: Number,
 | 
			
		||||
        nemesisHintProgressMultiplierCorpus: Number,
 | 
			
		||||
        nemesisWeaponFusionMultiplier: Number,
 | 
			
		||||
        nemesisExtraWeapon: Number,
 | 
			
		||||
        extraMissionRewards: Number,
 | 
			
		||||
        playerSkillGainsMultiplierSpace: Number,
 | 
			
		||||
        playerSkillGainsMultiplierDrifter: Number,
 | 
			
		||||
        extraRelicRewards: Number,
 | 
			
		||||
        relicPlatinumCommon: Number,
 | 
			
		||||
        relicPlatinumUncommon: Number,
 | 
			
		||||
        relicPlatinumRare: Number,
 | 
			
		||||
 | 
			
		||||
        SubscribedToEmails: { type: Number, default: 0 },
 | 
			
		||||
        SubscribedToEmailsPersonalized: { type: Number, default: 0 },
 | 
			
		||||
@ -1564,6 +1587,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        OperatorLoadOuts: [operatorConfigSchema],
 | 
			
		||||
        //Drifter
 | 
			
		||||
        AdultOperatorLoadOuts: [operatorConfigSchema],
 | 
			
		||||
        OperatorCustomizationSlotPurchases: Number,
 | 
			
		||||
        // Kahl
 | 
			
		||||
        KahlLoadOuts: [operatorConfigSchema],
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -162,6 +162,7 @@ import { updateQuestController } from "../controllers/api/updateQuestController.
 | 
			
		||||
import { updateSessionGetController, updateSessionPostController } from "../controllers/api/updateSessionController.ts";
 | 
			
		||||
import { updateSongChallengeController } from "../controllers/api/updateSongChallengeController.ts";
 | 
			
		||||
import { updateThemeController } from "../controllers/api/updateThemeController.ts";
 | 
			
		||||
import { upgradeOperatorController } from "../controllers/api/upgradeOperatorController.ts";
 | 
			
		||||
import { upgradesController } from "../controllers/api/upgradesController.ts";
 | 
			
		||||
import { valenceSwapController } from "../controllers/api/valenceSwapController.ts";
 | 
			
		||||
import { wishlistController } from "../controllers/api/wishlistController.ts";
 | 
			
		||||
@ -229,6 +230,7 @@ apiRouter.get("/startLibraryPersonalTarget.php", startLibraryPersonalTargetContr
 | 
			
		||||
apiRouter.get("/surveys.php", surveysController);
 | 
			
		||||
apiRouter.get("/trading.php", tradingController);
 | 
			
		||||
apiRouter.get("/updateSession.php", updateSessionGetController);
 | 
			
		||||
apiRouter.get("/upgradeOperator.php", upgradeOperatorController);
 | 
			
		||||
 | 
			
		||||
// post
 | 
			
		||||
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,8 @@ import { addMissingMaxRankModsController } from "../controllers/custom/addMissin
 | 
			
		||||
import { webuiFileChangeDetectedController } from "../controllers/custom/webuiFileChangeDetectedController.ts";
 | 
			
		||||
import { completeAllMissionsController } from "../controllers/custom/completeAllMissionsController.ts";
 | 
			
		||||
import { addMissingHelminthBlueprintsController } from "../controllers/custom/addMissingHelminthBlueprintsController.ts";
 | 
			
		||||
import { unlockAllProfitTakerStagesController } from "../controllers/custom/unlockAllProfitTakerStagesController.ts";
 | 
			
		||||
import { unlockAllSimarisResearchEntriesController } from "../controllers/custom/unlockAllSimarisResearchEntriesController.ts";
 | 
			
		||||
 | 
			
		||||
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
 | 
			
		||||
import { createAccountController } from "../controllers/custom/createAccountController.ts";
 | 
			
		||||
@ -48,6 +50,8 @@ customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
 | 
			
		||||
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
 | 
			
		||||
customRouter.get("/completeAllMissions", completeAllMissionsController);
 | 
			
		||||
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
 | 
			
		||||
customRouter.get("/unlockAllProfitTakerStages", unlockAllProfitTakerStagesController);
 | 
			
		||||
customRouter.get("/unlockAllSimarisResearchEntries", unlockAllSimarisResearchEntriesController);
 | 
			
		||||
 | 
			
		||||
customRouter.post("/abilityOverride", abilityOverrideController);
 | 
			
		||||
customRouter.post("/createAccount", createAccountController);
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import { repoDir } from "../helpers/pathHelper.ts";
 | 
			
		||||
import { args } from "../helpers/commandLineArguments.ts";
 | 
			
		||||
import { Inbox } from "../models/inboxModel.ts";
 | 
			
		||||
 | 
			
		||||
export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
export interface IConfig {
 | 
			
		||||
    mongodbUrl: string;
 | 
			
		||||
    logger: {
 | 
			
		||||
        files: boolean;
 | 
			
		||||
@ -18,7 +18,6 @@ export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
    administratorNames?: string[];
 | 
			
		||||
    autoCreateAccount?: boolean;
 | 
			
		||||
    skipTutorial?: boolean;
 | 
			
		||||
    skipAllDialogue?: boolean;
 | 
			
		||||
    unlockAllScans?: boolean;
 | 
			
		||||
    unlockAllShipFeatures?: boolean;
 | 
			
		||||
    unlockAllShipDecorations?: boolean;
 | 
			
		||||
@ -27,9 +26,6 @@ export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
    unlockAllCapturaScenes?: boolean;
 | 
			
		||||
    unlockAllDecoRecipes?: boolean;
 | 
			
		||||
    fullyStockedVendors?: boolean;
 | 
			
		||||
    baroAlwaysAvailable?: boolean;
 | 
			
		||||
    baroFullyStocked?: boolean;
 | 
			
		||||
    unlockAllProfitTakerStages?: boolean;
 | 
			
		||||
    skipClanKeyCrafting?: boolean;
 | 
			
		||||
    noDojoRoomBuildStage?: boolean;
 | 
			
		||||
    noDojoDecoBuildStage?: boolean;
 | 
			
		||||
@ -37,12 +33,6 @@ export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
    noDojoResearchCosts?: boolean;
 | 
			
		||||
    noDojoResearchTime?: boolean;
 | 
			
		||||
    fastClanAscension?: boolean;
 | 
			
		||||
    missionsCanGiveAllRelics?: boolean;
 | 
			
		||||
    exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
 | 
			
		||||
    flawlessRelicsAlwaysGiveSilverReward?: boolean;
 | 
			
		||||
    radiantRelicsAlwaysGiveGoldReward?: boolean;
 | 
			
		||||
    unlockAllSimarisResearchEntries?: boolean;
 | 
			
		||||
    disableDailyTribute?: boolean;
 | 
			
		||||
    spoofMasteryRank?: number;
 | 
			
		||||
    relicRewardItemCountMultiplier?: number;
 | 
			
		||||
    nightwaveStandingMultiplier?: number;
 | 
			
		||||
@ -57,6 +47,9 @@ export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
        resourceBoost?: boolean;
 | 
			
		||||
        tennoLiveRelay?: boolean;
 | 
			
		||||
        baroTennoConRelay?: boolean;
 | 
			
		||||
        baroAlwaysAvailable?: boolean;
 | 
			
		||||
        baroFullyStocked?: boolean;
 | 
			
		||||
        varziaFullyStocked?: boolean;
 | 
			
		||||
        wolfHunt?: boolean;
 | 
			
		||||
        orphixVenom?: boolean;
 | 
			
		||||
        longShadow?: boolean;
 | 
			
		||||
@ -83,10 +76,9 @@ export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
        duviriOverride?: string;
 | 
			
		||||
        nightwaveOverride?: string;
 | 
			
		||||
        allTheFissures?: string;
 | 
			
		||||
        varziaOverride?: string;
 | 
			
		||||
        circuitGameModes?: string[];
 | 
			
		||||
        darvoStockMultiplier?: number;
 | 
			
		||||
        varziaOverride?: string;
 | 
			
		||||
        varziaFullyStocked?: boolean;
 | 
			
		||||
    };
 | 
			
		||||
    dev?: {
 | 
			
		||||
        keepVendorsExpired?: boolean;
 | 
			
		||||
@ -94,6 +86,7 @@ export interface IConfig extends IConfigRemovedOptions {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const configRemovedOptionsKeys = [
 | 
			
		||||
    "skipAllDialogue",
 | 
			
		||||
    "infiniteCredits",
 | 
			
		||||
    "infinitePlatinum",
 | 
			
		||||
    "infiniteEndo",
 | 
			
		||||
@ -110,6 +103,8 @@ export const configRemovedOptionsKeys = [
 | 
			
		||||
    "unlockDoubleCapacityPotatoesEverywhere",
 | 
			
		||||
    "unlockExilusEverywhere",
 | 
			
		||||
    "unlockArcanesEverywhere",
 | 
			
		||||
    "unlockAllProfitTakerStages",
 | 
			
		||||
    "unlockAllSimarisResearchEntries",
 | 
			
		||||
    "noDailyStandingLimits",
 | 
			
		||||
    "noDailyFocusLimit",
 | 
			
		||||
    "noArgonCrystalDecay",
 | 
			
		||||
@ -120,12 +115,15 @@ export const configRemovedOptionsKeys = [
 | 
			
		||||
    "syndicateMissionsRepeatable",
 | 
			
		||||
    "instantFinishRivenChallenge",
 | 
			
		||||
    "instantResourceExtractorDrones",
 | 
			
		||||
    "noResourceExtractorDronesDamage"
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
type IConfigRemovedOptions = {
 | 
			
		||||
    [K in (typeof configRemovedOptionsKeys)[number]]?: boolean;
 | 
			
		||||
};
 | 
			
		||||
    "noResourceExtractorDronesDamage",
 | 
			
		||||
    "baroAlwaysAvailable",
 | 
			
		||||
    "baroFullyStocked",
 | 
			
		||||
    "missionsCanGiveAllRelics",
 | 
			
		||||
    "exceptionalRelicsAlwaysGiveBronzeReward",
 | 
			
		||||
    "flawlessRelicsAlwaysGiveSilverReward",
 | 
			
		||||
    "radiantRelicsAlwaysGiveGoldReward",
 | 
			
		||||
    "disableDailyTribute"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const configPath = path.join(repoDir, args.configPath ?? "config.json");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,13 @@
 | 
			
		||||
import chokidar from "chokidar";
 | 
			
		||||
import { logger } from "../utils/logger.ts";
 | 
			
		||||
import { config, configPath, configRemovedOptionsKeys, loadConfig, syncConfigWithDatabase } from "./configService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    config,
 | 
			
		||||
    configPath,
 | 
			
		||||
    configRemovedOptionsKeys,
 | 
			
		||||
    loadConfig,
 | 
			
		||||
    syncConfigWithDatabase,
 | 
			
		||||
    type IConfig
 | 
			
		||||
} from "./configService.ts";
 | 
			
		||||
import { saveConfig, shouldReloadConfig } from "./configWriterService.ts";
 | 
			
		||||
import { getWebPorts, startWebServer, stopWebServer } from "./webService.ts";
 | 
			
		||||
import { sendWsBroadcast } from "./wsService.ts";
 | 
			
		||||
@ -35,9 +42,11 @@ chokidar.watch(configPath).on("change", () => {
 | 
			
		||||
export const validateConfig = (): void => {
 | 
			
		||||
    let modified = false;
 | 
			
		||||
    for (const key of configRemovedOptionsKeys) {
 | 
			
		||||
        if (config[key] !== undefined) {
 | 
			
		||||
            logger.debug(`Spotted removed option ${key} with value ${config[key]} in config.json.`);
 | 
			
		||||
            delete config[key];
 | 
			
		||||
        if (config[key as keyof IConfig] !== undefined) {
 | 
			
		||||
            logger.debug(
 | 
			
		||||
                `Spotted removed option ${key} with value ${String(config[key as keyof IConfig])} in config.json.`
 | 
			
		||||
            );
 | 
			
		||||
            delete config[key as keyof IConfig];
 | 
			
		||||
            modified = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
 | 
			
		||||
    // Baro
 | 
			
		||||
    const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
 | 
			
		||||
    const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
 | 
			
		||||
    const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
 | 
			
		||||
    const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
 | 
			
		||||
    if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
 | 
			
		||||
        newEventMessages.push({
 | 
			
		||||
            sndr: "/Lotus/Language/G1Quests/VoidTraderName",
 | 
			
		||||
 | 
			
		||||
@ -689,6 +689,7 @@ export const addItem = async (
 | 
			
		||||
    // Path-based duck typing
 | 
			
		||||
    switch (typeName.substr(1).split("/")[1]) {
 | 
			
		||||
        case "Powersuits":
 | 
			
		||||
            if (typeName.endsWith("AugmentCard")) break;
 | 
			
		||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
			
		||||
                default: {
 | 
			
		||||
                    return {
 | 
			
		||||
@ -773,6 +774,10 @@ export const addItem = async (
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "Skins": {
 | 
			
		||||
                    return addSkin(inventory, typeName);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
@ -869,6 +874,26 @@ export const addItem = async (
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case "Weapons": {
 | 
			
		||||
            if (typeName.substr(1).split("/")[4] == "MeleeTrees") break;
 | 
			
		||||
            const productCategory = typeName.substr(1).split("/")[3];
 | 
			
		||||
            switch (productCategory) {
 | 
			
		||||
                case "Pistols":
 | 
			
		||||
                case "LongGuns":
 | 
			
		||||
                case "Melee": {
 | 
			
		||||
                    const inventoryChanges = addEquipment(inventory, productCategory, typeName);
 | 
			
		||||
                    return {
 | 
			
		||||
                        ...inventoryChanges,
 | 
			
		||||
                        ...occupySlot(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            productCategoryToInventoryBin(productCategory) ?? InventorySlot.WEAPONS,
 | 
			
		||||
                            premiumPurchase
 | 
			
		||||
                        )
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    throw new Error(`unable to add item: ${typeName}`);
 | 
			
		||||
};
 | 
			
		||||
@ -1321,6 +1346,10 @@ export const addStanding = (
 | 
			
		||||
    let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
 | 
			
		||||
    const syndicateMeta = ExportSyndicates[syndicateTag];
 | 
			
		||||
 | 
			
		||||
    if (inventory.gainNoNegativeSyndicateStanding) {
 | 
			
		||||
        gainedStanding = Math.max(gainedStanding, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!syndicate) {
 | 
			
		||||
        syndicate =
 | 
			
		||||
            inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0, Title: 0 }) - 1];
 | 
			
		||||
 | 
			
		||||
@ -201,10 +201,29 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
            inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
 | 
			
		||||
        }
 | 
			
		||||
        if (inventoryUpdates.RewardInfo.NemesisHenchmenKills && inventory.Nemesis) {
 | 
			
		||||
            inventory.Nemesis.HenchmenKilled += inventoryUpdates.RewardInfo.NemesisHenchmenKills;
 | 
			
		||||
            let HenchmenKilledMultiplier = 1;
 | 
			
		||||
            switch (inventory.Nemesis.Faction) {
 | 
			
		||||
                case "FC_GRINEER":
 | 
			
		||||
                    HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMulptiplierGrineer ?? 1;
 | 
			
		||||
                    break;
 | 
			
		||||
                case "FC_CORPUS":
 | 
			
		||||
                    HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMulptiplierCorpus ?? 1;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            inventory.Nemesis.HenchmenKilled +=
 | 
			
		||||
                inventoryUpdates.RewardInfo.NemesisHenchmenKills * HenchmenKilledMultiplier;
 | 
			
		||||
        }
 | 
			
		||||
        if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
 | 
			
		||||
            inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
 | 
			
		||||
            let HintProgressMultiplier = 1;
 | 
			
		||||
            switch (inventory.Nemesis.Faction) {
 | 
			
		||||
                case "FC_GRINEER":
 | 
			
		||||
                    HintProgressMultiplier = inventory.nemesisHintProgressMultiplierGrineer ?? 1;
 | 
			
		||||
                    break;
 | 
			
		||||
                case "FC_CORPUS":
 | 
			
		||||
                    HintProgressMultiplier = inventory.nemesisHintProgressMultiplierCorpus ?? 1;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress * HintProgressMultiplier;
 | 
			
		||||
            if (inventory.Nemesis.Faction != "FC_INFESTATION" && inventory.Nemesis.Hints.length != 3) {
 | 
			
		||||
                const progressNeeded = [35, 60, 100][inventory.Nemesis.Hints.length];
 | 
			
		||||
                if (inventory.Nemesis.HintProgress >= progressNeeded) {
 | 
			
		||||
@ -347,8 +366,10 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "PlayerSkillGains": {
 | 
			
		||||
                inventory.PlayerSkills.LPP_SPACE += value.LPP_SPACE ?? 0;
 | 
			
		||||
                inventory.PlayerSkills.LPP_DRIFTER += value.LPP_DRIFTER ?? 0;
 | 
			
		||||
                inventory.PlayerSkills.LPP_SPACE +=
 | 
			
		||||
                    (value.LPP_SPACE ?? 0) * (inventory.playerSkillGainsMultiplierSpace ?? 1);
 | 
			
		||||
                inventory.PlayerSkills.LPP_DRIFTER +=
 | 
			
		||||
                    (value.LPP_DRIFTER ?? 0) * (inventory.playerSkillGainsMultiplierDrifter ?? 1);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "CustomMarkers": {
 | 
			
		||||
@ -775,6 +796,11 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
            }
 | 
			
		||||
            case "InvasionProgress": {
 | 
			
		||||
                for (const clientProgress of value) {
 | 
			
		||||
                    if (inventory.finishInvasionsInOneMission) {
 | 
			
		||||
                        clientProgress.Delta *= 3;
 | 
			
		||||
                        clientProgress.AttackerScore *= 3;
 | 
			
		||||
                        clientProgress.DefenderScore *= 3;
 | 
			
		||||
                    }
 | 
			
		||||
                    const dbProgress = inventory.QualifyingInvasions.find(x =>
 | 
			
		||||
                        x.invasionId.equals(clientProgress._id.$oid)
 | 
			
		||||
                    );
 | 
			
		||||
@ -838,6 +864,8 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                    const att: string[] = [];
 | 
			
		||||
                    let countedAtt: ITypeCount[] | undefined;
 | 
			
		||||
 | 
			
		||||
                    const extraWeaponCheat = inventory.nemesisExtraWeapon ?? 0; // 0 means no extra weapon and token
 | 
			
		||||
 | 
			
		||||
                    if (value.killed) {
 | 
			
		||||
                        if (
 | 
			
		||||
                            value.weaponLoc &&
 | 
			
		||||
@ -847,6 +875,20 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                            giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
 | 
			
		||||
                            att.push(weaponType);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (extraWeaponCheat >= 1) {
 | 
			
		||||
                            for (let i = 0; i < extraWeaponCheat; i++) {
 | 
			
		||||
                                const randomIndex = Math.floor(Math.random() * manifest.weapons.length);
 | 
			
		||||
                                const randomWeapon = manifest.weapons[randomIndex];
 | 
			
		||||
                                giveNemesisWeaponRecipe(
 | 
			
		||||
                                    inventory,
 | 
			
		||||
                                    randomWeapon,
 | 
			
		||||
                                    value.nemesisName,
 | 
			
		||||
                                    value.weaponLoc,
 | 
			
		||||
                                    profile
 | 
			
		||||
                                );
 | 
			
		||||
                                att.push(randomWeapon);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        //if (value.petLoc) {
 | 
			
		||||
                        if (profile.petHead) {
 | 
			
		||||
                            giveNemesisPetRecipe(inventory, value.nemesisName, profile);
 | 
			
		||||
@ -889,7 +931,7 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                            countedAtt = [
 | 
			
		||||
                                {
 | 
			
		||||
                                    ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
 | 
			
		||||
                                    ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp)
 | 
			
		||||
                                    ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp) * (extraWeaponCheat + 1)
 | 
			
		||||
                                }
 | 
			
		||||
                            ];
 | 
			
		||||
                            addMiscItems(inventory, countedAtt);
 | 
			
		||||
@ -1323,6 +1365,21 @@ export const addMissionRewards = async (
 | 
			
		||||
    ) {
 | 
			
		||||
        const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
 | 
			
		||||
        MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
 | 
			
		||||
 | 
			
		||||
        if ((inventory.extraRelicRewards ?? 0) >= 1) {
 | 
			
		||||
            for (let i = 0; i != inventory.extraRelicRewards; ++i) {
 | 
			
		||||
                //give a relic that will be removed later in crackRelic()
 | 
			
		||||
                const miscItemChanges = [
 | 
			
		||||
                    {
 | 
			
		||||
                        ItemType: voidTearWave.Participants[0].VoidProjection,
 | 
			
		||||
                        ItemCount: 1
 | 
			
		||||
                    }
 | 
			
		||||
                ];
 | 
			
		||||
                addMiscItems(inventory, miscItemChanges);
 | 
			
		||||
                const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
 | 
			
		||||
                MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (strippedItems) {
 | 
			
		||||
@ -1417,9 +1474,14 @@ export const addMissionRewards = async (
 | 
			
		||||
 | 
			
		||||
            if (inventory.Nemesis.Faction == "FC_INFESTATION") {
 | 
			
		||||
                inventory.Nemesis.MissionCount += 1;
 | 
			
		||||
                let antivirusGain = 5;
 | 
			
		||||
                antivirusGain *= inventory.nemesisAntivirusGainMultiplier ?? 1;
 | 
			
		||||
                inventory.Nemesis.HenchmenKilled = Math.min(inventory.Nemesis.HenchmenKilled + antivirusGain, 95); // 5 progress per mission until 95
 | 
			
		||||
 | 
			
		||||
                inventoryChanges.Nemesis.MissionCount ??= 0;
 | 
			
		||||
                inventoryChanges.Nemesis.MissionCount += 1;
 | 
			
		||||
                inventoryChanges.Nemesis.HenchmenKilled ??= 0;
 | 
			
		||||
                inventoryChanges.Nemesis.HenchmenKilled = inventory.Nemesis.HenchmenKilled;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes;
 | 
			
		||||
@ -1473,7 +1535,7 @@ export const addMissionRewards = async (
 | 
			
		||||
                    if (vault) {
 | 
			
		||||
                        currentJob = vault;
 | 
			
		||||
                        if (jobType.endsWith("VaultBounty")) {
 | 
			
		||||
                            currentJob.xpAmounts = [currentJob.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)];
 | 
			
		||||
                            currentJob.xpAmounts[rewardInfo.JobTier!] = currentJob.xpAmounts.reduce((s, a) => s + a, 0);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@ -1490,6 +1552,7 @@ export const addMissionRewards = async (
 | 
			
		||||
                        medallionAmount = Math.floor(endlessJob.xpAmounts[index] * (1 + 0.15000001 * excess));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (typeof medallionAmount === "number" && !isNaN(medallionAmount)) {
 | 
			
		||||
                    await addItem(inventory, "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", medallionAmount);
 | 
			
		||||
                    MissionRewards.push({
 | 
			
		||||
                        StoreItem: "/Lotus/StoreItems/Types/Items/Deimos/EntratiFragmentUncommonB",
 | 
			
		||||
@ -1499,6 +1562,12 @@ export const addMissionRewards = async (
 | 
			
		||||
                    logger.debug(
 | 
			
		||||
                        `Giving ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    logger.warning(
 | 
			
		||||
                        `${jobType} tried to give ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
 | 
			
		||||
                    );
 | 
			
		||||
                    logger.warning(`currentJob`, { currentJob: currentJob });
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                const specialCase = [
 | 
			
		||||
                    { endings: ["Heists/HeistProfitTakerBountyOne"], stage: 2, amount: 1000 },
 | 
			
		||||
@ -1829,6 +1898,10 @@ function getRandomMissionDrops(
 | 
			
		||||
                    ItemCount: 10
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            drops.push({
 | 
			
		||||
                StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
 | 
			
		||||
                ItemCount: 10
 | 
			
		||||
            });
 | 
			
		||||
            rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"];
 | 
			
		||||
        } else if (RewardInfo.T == 19) {
 | 
			
		||||
            if (config.worldState?.eightClaw) {
 | 
			
		||||
@ -1837,6 +1910,10 @@ function getRandomMissionDrops(
 | 
			
		||||
                    ItemCount: 15
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            drops.push({
 | 
			
		||||
                StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
 | 
			
		||||
                ItemCount: 15
 | 
			
		||||
            });
 | 
			
		||||
            rewardManifests = [
 | 
			
		||||
                "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
 | 
			
		||||
            ];
 | 
			
		||||
@ -2194,7 +2271,7 @@ function getRandomMissionDrops(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.missionsCanGiveAllRelics) {
 | 
			
		||||
    if (inventory.missionsCanGiveAllRelics) {
 | 
			
		||||
        for (const drop of drops) {
 | 
			
		||||
            const itemType = fromStoreItem(drop.StoreItem);
 | 
			
		||||
            if (itemType in ExportRelics) {
 | 
			
		||||
 | 
			
		||||
@ -2630,15 +2630,22 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const thermiaFracturesCycleDay = day % 32;
 | 
			
		||||
    const isThermiaFracturesActive = thermiaFracturesCycleDay < 14;
 | 
			
		||||
    // Thermia Fractures activates for 14 days, with alternating 4 and 3-day breaks
 | 
			
		||||
    const thermiaFracturesCycleDay = day % 35;
 | 
			
		||||
    const isThermiaFracturesActive =
 | 
			
		||||
        thermiaFracturesCycleDay < 14 || (thermiaFracturesCycleDay >= 18 && thermiaFracturesCycleDay < 32);
 | 
			
		||||
    const activeThermiaFracturesCycleDay =
 | 
			
		||||
        thermiaFracturesCycleDay - (thermiaFracturesCycleDay < 14 ? 0 : thermiaFracturesCycleDay < 18 ? 14 : 32);
 | 
			
		||||
 | 
			
		||||
    if (config.worldState?.thermiaFracturesOverride ?? isThermiaFracturesActive) {
 | 
			
		||||
        const activeStartDay = day - thermiaFracturesCycleDay;
 | 
			
		||||
        const activeStartDay = day - activeThermiaFracturesCycleDay;
 | 
			
		||||
 | 
			
		||||
        const count = config.worldState?.thermiaFracturesProgressOverride ?? 0;
 | 
			
		||||
        const activation = config.worldState?.thermiaFracturesOverride ? 1740416400000 : getSortieTime(activeStartDay);
 | 
			
		||||
        const expiry = config.worldState?.thermiaFracturesOverride ? 2000000000000 : getSortieTime(activeStartDay + 14);
 | 
			
		||||
 | 
			
		||||
        // If we push it, the game may show the event even tho it's not activated yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2721)
 | 
			
		||||
        if (timeMs >= activation) {
 | 
			
		||||
            worldState.Goals.push({
 | 
			
		||||
                _id: { $oid: "5c7cb0d00000000000000000" },
 | 
			
		||||
                Activation: { $date: { $numberLong: activation.toString() } },
 | 
			
		||||
@ -2710,6 +2717,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Nightwave Challenges
 | 
			
		||||
    const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
 | 
			
		||||
@ -2933,7 +2941,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
    {
 | 
			
		||||
        const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
 | 
			
		||||
        const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
 | 
			
		||||
        const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
 | 
			
		||||
        const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
 | 
			
		||||
        const baroEnd = baroStart + unixTimesInMs.day * 14;
 | 
			
		||||
        const baroNode = ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4];
 | 
			
		||||
        const vt: IVoidTrader = {
 | 
			
		||||
@ -2946,7 +2954,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
        };
 | 
			
		||||
        worldState.VoidTraders.push(vt);
 | 
			
		||||
        if (isBeforeNextExpectedWorldStateRefresh(timeMs, baroActualStart)) {
 | 
			
		||||
            if (config.baroFullyStocked) {
 | 
			
		||||
            if (config.worldState?.baroFullyStocked) {
 | 
			
		||||
                fullyStockBaro(vt);
 | 
			
		||||
            } else {
 | 
			
		||||
                const rng = new SRng(new SRng(baroIndex).randomInt(0, 100_000));
 | 
			
		||||
 | 
			
		||||
@ -21,12 +21,14 @@ export type InventoryDatabaseEquipment = {
 | 
			
		||||
 | 
			
		||||
// Fields specific to SNS
 | 
			
		||||
export interface IAccountCheats {
 | 
			
		||||
    skipAllDialogue?: boolean;
 | 
			
		||||
    dontSubtractPurchaseCreditCost?: boolean;
 | 
			
		||||
    dontSubtractPurchasePlatinumCost?: boolean;
 | 
			
		||||
    dontSubtractPurchaseItemCost?: boolean;
 | 
			
		||||
    dontSubtractPurchaseStandingCost?: boolean;
 | 
			
		||||
    dontSubtractVoidTraces?: boolean;
 | 
			
		||||
    dontSubtractConsumables?: boolean;
 | 
			
		||||
    finishInvasionsInOneMission?: boolean;
 | 
			
		||||
    infiniteCredits?: boolean;
 | 
			
		||||
    infinitePlatinum?: boolean;
 | 
			
		||||
    infiniteEndo?: boolean;
 | 
			
		||||
@ -48,6 +50,28 @@ export interface IAccountCheats {
 | 
			
		||||
    claimingBlueprintRefundsIngredients?: boolean;
 | 
			
		||||
    instantResourceExtractorDrones?: boolean;
 | 
			
		||||
    noResourceExtractorDronesDamage?: boolean;
 | 
			
		||||
    missionsCanGiveAllRelics?: boolean;
 | 
			
		||||
    exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
 | 
			
		||||
    flawlessRelicsAlwaysGiveSilverReward?: boolean;
 | 
			
		||||
    radiantRelicsAlwaysGiveGoldReward?: boolean;
 | 
			
		||||
    disableDailyTribute?: boolean;
 | 
			
		||||
    gainNoNegativeSyndicateStanding?: boolean;
 | 
			
		||||
    nemesisAlwaysCorrect?: boolean;
 | 
			
		||||
    nemesisHenchmenKillsMulptiplierGrineer?: number;
 | 
			
		||||
    nemesisHenchmenKillsMulptiplierCorpus?: number;
 | 
			
		||||
    nemesisAntivirusGainMultiplier?: number;
 | 
			
		||||
    nemesisHintProgressMultiplierGrineer?: number;
 | 
			
		||||
    nemesisHintProgressMultiplierCorpus?: number;
 | 
			
		||||
    nemesisWeaponFusionMultiplier?: number;
 | 
			
		||||
    nemesisExtraWeapon?: number;
 | 
			
		||||
    extraMissionRewards?: number;
 | 
			
		||||
    playerSkillGainsMultiplierSpace?: number;
 | 
			
		||||
    playerSkillGainsMultiplierDrifter?: number;
 | 
			
		||||
    extraRelicRewards?: number;
 | 
			
		||||
    crackRelicForPlatinum?: boolean;
 | 
			
		||||
    relicPlatinumCommon?: number;
 | 
			
		||||
    relicPlatinumUncommon?: number;
 | 
			
		||||
    relicPlatinumRare?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IInventoryDatabase
 | 
			
		||||
@ -374,6 +398,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    CrewMembers: ICrewMemberClient[];
 | 
			
		||||
    LotusCustomization?: ILotusCustomization;
 | 
			
		||||
    UseAdultOperatorLoadout?: boolean;
 | 
			
		||||
    OperatorCustomizationSlotPurchases?: number;
 | 
			
		||||
    NemesisAbandonedRewards: string[];
 | 
			
		||||
    LastInventorySync?: IOid;
 | 
			
		||||
    NextRefill?: IMongoDate;
 | 
			
		||||
 | 
			
		||||
@ -92,13 +92,37 @@
 | 
			
		||||
            <div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
 | 
			
		||||
                <p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
 | 
			
		||||
                <div class="card mb-3">
 | 
			
		||||
                    <h5 class="card-header" data-loc="inventory_addItems"></h5>
 | 
			
		||||
                    <div class="card-header">
 | 
			
		||||
                        <ul class="nav nav-tabs card-header-tabs">
 | 
			
		||||
                            <li class="nav-item">
 | 
			
		||||
                                <button class="nav-link" id="miscItems-tab" data-bs-toggle="tab" data-bs-target="#miscItems-tab-content" data-loc="inventory_addItems"></button>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li class="nav-item">
 | 
			
		||||
                                <button class="nav-link" id="typeName-tab" data-bs-toggle="tab" data-bs-target="#typeName-tab-content" data-loc="inventory_addItemByItemType"></button>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <div class="tab-content">
 | 
			
		||||
                            <div class="tab-pane" id="miscItems-tab-content">
 | 
			
		||||
                                <form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;">
 | 
			
		||||
                                    <input class="form-control" id="miscitem-count" type="number" value="1" />
 | 
			
		||||
                                    <input class="form-control w-50" id="miscitem-type" list="datalist-miscitems" />
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="tab-pane" id="typeName-tab-content">
 | 
			
		||||
                                <form class="card-body" onsubmit="addItemByItemType();return false;">
 | 
			
		||||
                                    <p data-loc="inventory_addItemByItemType_warning"></p>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="typeName-type" />
 | 
			
		||||
                                        <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row g-3 mb-3">
 | 
			
		||||
                    <div class="col-md-3">
 | 
			
		||||
                        <div class="card">
 | 
			
		||||
@ -647,6 +671,10 @@
 | 
			
		||||
                        <div class="card">
 | 
			
		||||
                            <h5 class="card-header" data-loc="cheats_account"></h5>
 | 
			
		||||
                            <div class="card-body" id="account-cheats">
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="skipAllDialogue" />
 | 
			
		||||
                                    <label class="form-check-label" for="skipAllDialogue" data-loc="cheats_skipAllDialogue"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="dontSubtractPurchaseCreditCost" />
 | 
			
		||||
                                    <label class="form-check-label" for="dontSubtractPurchaseCreditCost" data-loc="cheats_dontSubtractPurchaseCreditCost"></label>
 | 
			
		||||
@ -755,14 +783,150 @@
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="claimingBlueprintRefundsIngredients" />
 | 
			
		||||
                                    <label class="form-check-label" for="claimingBlueprintRefundsIngredients" data-loc="cheats_claimingBlueprintRefundsIngredients"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                 <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="missionsCanGiveAllRelics" />
 | 
			
		||||
                                    <label class="form-check-label" for="missionsCanGiveAllRelics" data-loc="cheats_missionsCanGiveAllRelics"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="exceptionalRelicsAlwaysGiveBronzeReward" />
 | 
			
		||||
                                    <label class="form-check-label" for="exceptionalRelicsAlwaysGiveBronzeReward" data-loc="cheats_exceptionalRelicsAlwaysGiveBronzeReward"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="flawlessRelicsAlwaysGiveSilverReward" />
 | 
			
		||||
                                    <label class="form-check-label" for="flawlessRelicsAlwaysGiveSilverReward" data-loc="cheats_flawlessRelicsAlwaysGiveSilverReward"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="radiantRelicsAlwaysGiveGoldReward" />
 | 
			
		||||
                                    <label class="form-check-label" for="radiantRelicsAlwaysGiveGoldReward" data-loc="cheats_radiantRelicsAlwaysGiveGoldReward"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="disableDailyTribute" />
 | 
			
		||||
                                    <label class="form-check-label" for="disableDailyTribute" data-loc="cheats_disableDailyTribute"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="finishInvasionsInOneMission" />
 | 
			
		||||
                                    <label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="gainNoNegativeSyndicateStanding" />
 | 
			
		||||
                                    <label class="form-check-label" for="gainNoNegativeSyndicateStanding" data-loc="cheats_gainNoNegativeSyndicateStanding"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="nemesisAlwaysCorrect" />
 | 
			
		||||
                                    <label class="form-check-label" for="nemesisAlwaysCorrect" data-loc="cheats_nemesisAlwaysCorrect"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisHenchmenKillsMultiplierGrineer" data-loc="cheats_nemesisHenchmenKillsMultiplierGrineer"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisHenchmenKillsMultiplierGrineer" type="number" min="-1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisHenchmenKillsMultiplierCorpus" data-loc="cheats_nemesisHenchmenKillsMultiplierCorpus"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisHenchmenKillsMultiplierCorpus" type="number" min="-1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisAntivirusGainMultiplier" data-loc="cheats_nemesisAntivirusGainMultiplier"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisAntivirusGainMultiplier" type="number" min="-1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisHintProgressMultiplierGrineer" data-loc="cheats_nemesisHintProgressMultiplierGrineer"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisHintProgressMultiplierGrineer" type="number" min="-1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisHintProgressMultiplierCorpus" data-loc="cheats_nemesisHintProgressMultiplierCorpus"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisHintProgressMultiplierCorpus" type="number" min="-1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisWeaponFusionMultiplier" data-loc="cheats_nemesisWeaponFusionMultiplier"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisWeaponFusionMultiplier" type="number" min="1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="nemesisExtraWeapon" data-loc="cheats_nemesisExtraWeapon"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="nemesisExtraWeapon" type="number" min="0" max="65535" data-default="0" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="extraMissionRewards" data-loc="cheats_extraMissionRewards"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="extraMissionRewards" type="number" min="0" max="65535" data-default="0" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="playerSkillGainsMultiplierSpace" data-loc="cheats_playerSkillGainsMultiplierSpace"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="playerSkillGainsMultiplierSpace" type="number" min="1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="playerSkillGainsMultiplierDrifter" data-loc="cheats_playerSkillGainsMultiplierDrifter"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="playerSkillGainsMultiplierDrifter" type="number" min="1" max="65535" data-default="1" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="extraRelicRewards" data-loc="cheats_extraRelicRewards"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="extraRelicRewards" type="number" min="0" max="65535" data-default="0" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="crackRelicForPlatinum" />
 | 
			
		||||
                                    <label class="form-check-label" for="crackRelicForPlatinum" data-loc="cheats_crackRelicForPlatinum"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="relicPlatinumCommon" data-loc="cheats_relicPlatinumCommon"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="relicPlatinumCommon" type="number" min="0" max="65535" data-default="2" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="relicPlatinumUncommon" data-loc="cheats_relicPlatinumUncommon"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="relicPlatinumUncommon" type="number" min="0" max="65535" data-default="5" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="relicPlatinumRare" data-loc="cheats_relicPlatinumRare"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input class="form-control" id="relicPlatinumRare" type="number" min="0" max="65535" data-default="12" />
 | 
			
		||||
                                        <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <div class="mt-2 mb-2 d-flex flex-wrap gap-2">
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(unlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(markAllAsRead);" data-loc="cheats_markAllAsRead"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(addMissingHelminthRecipes);" data-loc="cheats_addMissingSubsumedAbilities"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="doIntrinsicsUnlockAll();" data-loc="cheats_intrinsicsUnlockAll"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(unlockAllProfitTakerStages);" data-loc="cheats_unlockAllProfitTakerStages"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(unlockAllSimarisResearchEntries);" data-loc="cheats_unlockAllSimarisResearchEntries"></button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>
 | 
			
		||||
@ -786,10 +950,6 @@
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="skipTutorial" />
 | 
			
		||||
                                        <label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="skipAllDialogue" />
 | 
			
		||||
                                        <label class="form-check-label" for="skipAllDialogue" data-loc="cheats_skipAllDialogue"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllScans" />
 | 
			
		||||
                                        <label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
 | 
			
		||||
@ -822,18 +982,6 @@
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
 | 
			
		||||
                                        <label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="baroAlwaysAvailable" />
 | 
			
		||||
                                        <label class="form-check-label" for="baroAlwaysAvailable" data-loc="cheats_baroAlwaysAvailable"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="baroFullyStocked" />
 | 
			
		||||
                                        <label class="form-check-label" for="baroFullyStocked" data-loc="cheats_baroFullyStocked"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllProfitTakerStages" />
 | 
			
		||||
                                        <label class="form-check-label" for="unlockAllProfitTakerStages" data-loc="cheats_unlockAllProfitTakerStages"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
 | 
			
		||||
                                        <label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
 | 
			
		||||
@ -862,30 +1010,6 @@
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="fastClanAscension" />
 | 
			
		||||
                                        <label class="form-check-label" for="fastClanAscension" data-loc="cheats_fastClanAscension"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="missionsCanGiveAllRelics" />
 | 
			
		||||
                                        <label class="form-check-label" for="missionsCanGiveAllRelics" data-loc="cheats_missionsCanGiveAllRelics"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="exceptionalRelicsAlwaysGiveBronzeReward" />
 | 
			
		||||
                                        <label class="form-check-label" for="exceptionalRelicsAlwaysGiveBronzeReward" data-loc="cheats_exceptionalRelicsAlwaysGiveBronzeReward"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="flawlessRelicsAlwaysGiveSilverReward" />
 | 
			
		||||
                                        <label class="form-check-label" for="flawlessRelicsAlwaysGiveSilverReward" data-loc="cheats_flawlessRelicsAlwaysGiveSilverReward"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="radiantRelicsAlwaysGiveGoldReward" />
 | 
			
		||||
                                        <label class="form-check-label" for="radiantRelicsAlwaysGiveGoldReward" data-loc="cheats_radiantRelicsAlwaysGiveGoldReward"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllSimarisResearchEntries" />
 | 
			
		||||
                                        <label class="form-check-label" for="unlockAllSimarisResearchEntries" data-loc="cheats_unlockAllSimarisResearchEntries"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="disableDailyTribute" />
 | 
			
		||||
                                        <label class="form-check-label" for="disableDailyTribute" data-loc="cheats_disableDailyTribute"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
 | 
			
		||||
                                        <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
 | 
			
		||||
                                        <div class="input-group">
 | 
			
		||||
@ -933,6 +1057,14 @@
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="worldState.baroTennoConRelay" />
 | 
			
		||||
                                    <label class="form-check-label" for="worldState.baroTennoConRelay" data-loc="worldState_baroTennoConRelay"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="worldState.baroAlwaysAvailable" />
 | 
			
		||||
                                    <label class="form-check-label" for="worldState.baroAlwaysAvailable" data-loc="cheats_baroAlwaysAvailable"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="worldState.baroFullyStocked" />
 | 
			
		||||
                                    <label class="form-check-label" for="worldState.baroFullyStocked" data-loc="cheats_baroFullyStocked"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input class="form-check-input" type="checkbox" id="worldState.varziaFullyStocked" />
 | 
			
		||||
                                    <label class="form-check-label" for="worldState.varziaFullyStocked" data-loc="worldState_varziaFullyStocked"></label>
 | 
			
		||||
 | 
			
		||||
@ -649,6 +649,14 @@ function updateInventory() {
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            // Populate inventory route
 | 
			
		||||
 | 
			
		||||
            document.getElementById("typeName-tab").classList.remove("active");
 | 
			
		||||
            document.getElementById("typeName-tab-content").classList.remove("active", "show");
 | 
			
		||||
            document.getElementById("typeName-type").value = "";
 | 
			
		||||
 | 
			
		||||
            document.getElementById("miscItems-tab").classList.add("active");
 | 
			
		||||
            document.getElementById("miscItems-tab-content").classList.add("active", "show");
 | 
			
		||||
 | 
			
		||||
            ["RegularCredits", "PremiumCredits", "FusionPoints", "PrimeTokens"].forEach(currency => {
 | 
			
		||||
                document.getElementById(currency + "-owned").textContent = loc("currency_owned")
 | 
			
		||||
                    .split("|COUNT|")
 | 
			
		||||
@ -1495,7 +1503,11 @@ function updateInventory() {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            for (const elm of accountCheats) {
 | 
			
		||||
                if (elm.type === "checkbox") {
 | 
			
		||||
                    elm.checked = !!data[elm.id];
 | 
			
		||||
                } else if (elm.type === "number") {
 | 
			
		||||
                    elm.value = data[elm.id] !== undefined ? data[elm.id] : elm.getAttribute("data-default") || "";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
@ -1685,7 +1697,7 @@ function doAcquireEvolution() {
 | 
			
		||||
    setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$(document).on("input", "input[list]", function () {
 | 
			
		||||
$(document).on("input", "input", function () {
 | 
			
		||||
    $(this).removeClass("is-invalid");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -2008,6 +2020,35 @@ function doAcquireMiscItems() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addItemByItemType() {
 | 
			
		||||
    const ItemType = document.getElementById("typeName-type").value;
 | 
			
		||||
    // Must start with "/Lotus/", contain only A–Z letters, no "//", and not end with "/"
 | 
			
		||||
    if (!ItemType || !/^\/Lotus\/(?:[A-Za-z]+(?:\/[A-Za-z]+)*)$/.test(ItemType)) {
 | 
			
		||||
        $("#typeName-type").addClass("is-invalid").focus();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    revalidateAuthz().then(() => {
 | 
			
		||||
        $.post({
 | 
			
		||||
            url: "/custom/addItems?" + window.authz,
 | 
			
		||||
            contentType: "application/json",
 | 
			
		||||
            data: JSON.stringify([
 | 
			
		||||
                {
 | 
			
		||||
                    ItemType,
 | 
			
		||||
                    ItemCount: 1
 | 
			
		||||
                }
 | 
			
		||||
            ])
 | 
			
		||||
        })
 | 
			
		||||
            .done(function (_, _, jqXHR) {
 | 
			
		||||
                if (jqXHR.status === 200) {
 | 
			
		||||
                    updateInventory();
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .fail(function () {
 | 
			
		||||
                $("#typeName-type").addClass("is-invalid").focus();
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function doAcquireRiven() {
 | 
			
		||||
    let fingerprint;
 | 
			
		||||
    try {
 | 
			
		||||
@ -2330,15 +2371,16 @@ function doIntrinsicsUnlockAll() {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
 | 
			
		||||
document.querySelectorAll("#account-cheats input[type=checkbox], #account-cheats input[type=number]").forEach(elm => {
 | 
			
		||||
    elm.onchange = function () {
 | 
			
		||||
        revalidateAuthz().then(() => {
 | 
			
		||||
            const value = elm.type === "checkbox" ? elm.checked : elm.value;
 | 
			
		||||
            $.post({
 | 
			
		||||
                url: "/custom/setAccountCheat?" + window.authz /*+ "&wsid=" + wsid*/,
 | 
			
		||||
                contentType: "application/json",
 | 
			
		||||
                data: JSON.stringify({
 | 
			
		||||
                    key: elm.id,
 | 
			
		||||
                    value: elm.checked
 | 
			
		||||
                    value: value
 | 
			
		||||
                })
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
@ -2713,12 +2755,24 @@ async function doMaxPlexus() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function doUnlockAllMissions() {
 | 
			
		||||
async function unlockAllMissions() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/completeAllMissions?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockAllMissions_ok"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function unlockAllProfitTakerStages() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function unlockAllSimarisResearchEntries() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
 | 
			
		||||
    toast(loc("cheats_unlockSucc"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const importSamples = {
 | 
			
		||||
    maxFocus: {
 | 
			
		||||
        FocusUpgrades: [
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `Cheats`,
 | 
			
		||||
    navbar_import: `Importieren`,
 | 
			
		||||
    inventory_addItems: `Gegenstände hinzufügen`,
 | 
			
		||||
    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_suits: `Warframes`,
 | 
			
		||||
    inventory_longGuns: `Primärwaffen`,
 | 
			
		||||
    inventory_pistols: `Sekundärwaffen`,
 | 
			
		||||
@ -210,6 +212,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_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
@ -238,6 +241,24 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
 | 
			
		||||
    cheats_changeButton: `Ändern`,
 | 
			
		||||
    cheats_markAllAsRead: `Posteingang als gelesen markieren`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
 | 
			
		||||
    cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
 | 
			
		||||
    cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
 | 
			
		||||
    cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
 | 
			
		||||
    cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
 | 
			
		||||
 | 
			
		||||
    worldState: `Weltstatus`,
 | 
			
		||||
    worldState_creditBoost: `Event Booster: Credit`,
 | 
			
		||||
 | 
			
		||||
@ -78,6 +78,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `Cheats`,
 | 
			
		||||
    navbar_import: `Import`,
 | 
			
		||||
    inventory_addItems: `Add Items`,
 | 
			
		||||
    inventory_addItemByItemType: `Raw`,
 | 
			
		||||
    inventory_addItemByItemType_warning: `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_suits: `Warframes`,
 | 
			
		||||
    inventory_longGuns: `Primary Weapons`,
 | 
			
		||||
    inventory_pistols: `Secondary Weapons`,
 | 
			
		||||
@ -209,6 +211,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Baro Fully Stocked`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_unlockSucc: `Successfully unlocked.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
 | 
			
		||||
@ -237,6 +240,24 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Supported syndicate`,
 | 
			
		||||
    cheats_changeButton: `Change`,
 | 
			
		||||
    cheats_markAllAsRead: `Mark Inbox As Read`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `Finish Invasions in One Mission`,
 | 
			
		||||
    cheats_gainNoNegativeSyndicateStanding: `Gain No Negative Syndicate Standing`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `Any Guess is Correct`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `Rage Progess Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `Rage Progess Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `Antivirus Progress Multiplier`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `Hint Progress Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `Hint Progress Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `Nemesis Weapon Fusion Multiplier`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
 | 
			
		||||
    cheats_extraMissionRewards: `Extra Mission Rewards`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `Intrinsics Gains Multiplier (Space)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `Intrinsics Gains Multiplier (Drifter)`,
 | 
			
		||||
    cheats_extraRelicRewards: `Extra Relic Rewards`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `Crack Relic for Platinum`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `Platinum on Common Rewards`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `Platinum on Uncommon Rewards`,
 | 
			
		||||
    cheats_relicPlatinumRare: `Platinum on Rare Rewards`,
 | 
			
		||||
 | 
			
		||||
    worldState: `World State`,
 | 
			
		||||
    worldState_creditBoost: `Credit Boost`,
 | 
			
		||||
 | 
			
		||||
@ -79,6 +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_suits: `Warframes`,
 | 
			
		||||
    inventory_longGuns: `Armas primarias`,
 | 
			
		||||
    inventory_pistols: `Armas secundarias`,
 | 
			
		||||
@ -210,6 +212,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_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`,
 | 
			
		||||
@ -238,6 +241,24 @@ 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_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
 | 
			
		||||
    cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
 | 
			
		||||
    cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
 | 
			
		||||
    cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
 | 
			
		||||
 | 
			
		||||
    worldState: `Estado del mundo`,
 | 
			
		||||
    worldState_creditBoost: `Potenciador de Créditos`,
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `Cheats`,
 | 
			
		||||
    navbar_import: `Importer`,
 | 
			
		||||
    inventory_addItems: `Ajouter des items`,
 | 
			
		||||
    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_suits: `Warframes`,
 | 
			
		||||
    inventory_longGuns: `Armes principales`,
 | 
			
		||||
    inventory_pistols: `Armes secondaires`,
 | 
			
		||||
@ -210,6 +212,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_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`,
 | 
			
		||||
@ -238,6 +241,24 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Allégeance`,
 | 
			
		||||
    cheats_changeButton: `Changer`,
 | 
			
		||||
    cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
 | 
			
		||||
    cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
 | 
			
		||||
    cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
 | 
			
		||||
    cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
 | 
			
		||||
    cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
 | 
			
		||||
 | 
			
		||||
    worldState: `Carte Solaire`,
 | 
			
		||||
    worldState_creditBoost: `Booster de Crédit`,
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `Читы`,
 | 
			
		||||
    navbar_import: `Импорт`,
 | 
			
		||||
    inventory_addItems: `Добавить предметы`,
 | 
			
		||||
    inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
 | 
			
		||||
    inventory_addItemByItemType_warning: `Используйте эту функцию на свой страх и риск. Она может повредить ваш инвентарь, и в случае проблем вам придётся удалять предметы вручную.`,
 | 
			
		||||
    inventory_suits: `Варфреймы`,
 | 
			
		||||
    inventory_longGuns: `Основное оружие`,
 | 
			
		||||
    inventory_pistols: `Вторичное оружие`,
 | 
			
		||||
@ -210,6 +212,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Баро полностью укомплектован`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
 | 
			
		||||
    cheats_unlockSucc: `Успешно разблокировано.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
 | 
			
		||||
@ -238,6 +241,24 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
 | 
			
		||||
    cheats_changeButton: `Изменить`,
 | 
			
		||||
    cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`,
 | 
			
		||||
    cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
 | 
			
		||||
    cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
 | 
			
		||||
    cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
 | 
			
		||||
    cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
 | 
			
		||||
 | 
			
		||||
    worldState: `Состояние мира`,
 | 
			
		||||
    worldState_creditBoost: `Глобальный бустер Кредитов`,
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `Чити`,
 | 
			
		||||
    navbar_import: `Імпорт`,
 | 
			
		||||
    inventory_addItems: `Додати предмети`,
 | 
			
		||||
    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_suits: `Ворфрейми`,
 | 
			
		||||
    inventory_longGuns: `Основна зброя`,
 | 
			
		||||
    inventory_pistols: `Допоміжна зброя`,
 | 
			
		||||
@ -210,6 +212,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `Баро повністю укомплектований`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
 | 
			
		||||
    cheats_unlockSucc: `Успішно розблоковано.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
 | 
			
		||||
@ -238,6 +241,24 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Підтримуваний синдикат`,
 | 
			
		||||
    cheats_changeButton: `Змінити`,
 | 
			
		||||
    cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`,
 | 
			
		||||
    cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
 | 
			
		||||
    cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
 | 
			
		||||
    cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
 | 
			
		||||
    cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
 | 
			
		||||
 | 
			
		||||
    worldState: `Стан світу`,
 | 
			
		||||
    worldState_creditBoost: `Глобальне посилення Кредитів`,
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,8 @@ dict = {
 | 
			
		||||
    navbar_cheats: `作弊选项`,
 | 
			
		||||
    navbar_import: `导入`,
 | 
			
		||||
    inventory_addItems: `添加物品`,
 | 
			
		||||
    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_suits: `战甲`,
 | 
			
		||||
    inventory_longGuns: `主要武器`,
 | 
			
		||||
    inventory_pistols: `次要武器`,
 | 
			
		||||
@ -210,6 +212,7 @@ dict = {
 | 
			
		||||
    cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
 | 
			
		||||
    cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
 | 
			
		||||
    cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
 | 
			
		||||
@ -238,6 +241,24 @@ dict = {
 | 
			
		||||
    cheats_changeSupportedSyndicate: `支持的集团`,
 | 
			
		||||
    cheats_changeButton: `更改`,
 | 
			
		||||
    cheats_markAllAsRead: `收件箱全部标记为已读`,
 | 
			
		||||
    cheats_finishInvasionsInOneMission: `一场任务完成整场入侵`,
 | 
			
		||||
    cheats_gainNoNegativeSyndicateStanding: `集团声望不倒扣不掉段`,
 | 
			
		||||
    cheats_nemesisAlwaysCorrect: `玄骸密码总是正确`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierGrineer: `玄骸怒气倍率 (Grineer)`,
 | 
			
		||||
    cheats_nemesisHenchmenKillsMultiplierCorpus: `玄骸怒气倍率 (Corpus)`,
 | 
			
		||||
    cheats_nemesisAntivirusGainMultiplier: `杀毒进度倍率 (科腐者)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierGrineer: `解密进度倍率 (Grineer)`,
 | 
			
		||||
    cheats_nemesisHintProgressMultiplierCorpus: `解密进度倍率 (Corpus)`,
 | 
			
		||||
    cheats_nemesisWeaponFusionMultiplier: `玄骸武器效价融合倍率`,
 | 
			
		||||
    cheats_nemesisExtraWeapon: `额外玄骸武器/代币 (0为禁用)`,
 | 
			
		||||
    cheats_extraMissionRewards: `额外任务奖励`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierSpace: `內源之力获取倍率 (九重天)`,
 | 
			
		||||
    cheats_playerSkillGainsMultiplierDrifter: `內源之力获取倍率 (漂泊者)`,
 | 
			
		||||
    cheats_extraRelicRewards: `额外遗物奖励`,
 | 
			
		||||
    cheats_crackRelicForPlatinum: `打开遗物时获得白金`,
 | 
			
		||||
    cheats_relicPlatinumCommon: `普通奖励的白金`,
 | 
			
		||||
    cheats_relicPlatinumUncommon: `罕见奖励的白金`,
 | 
			
		||||
    cheats_relicPlatinumRare: `稀有奖励的白金`,
 | 
			
		||||
 | 
			
		||||
    worldState: `世界状态配置`,
 | 
			
		||||
    worldState_creditBoost: `现金加成`,
 | 
			
		||||
 | 
			
		||||
@ -105,9 +105,6 @@
 | 
			
		||||
 | 
			
		||||
    /* Completeness */
 | 
			
		||||
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
 | 
			
		||||
    "skipLibCheck": true /* Skip type checking all .d.ts files. */,
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": ["./*"]
 | 
			
		||||
    }
 | 
			
		||||
    "skipLibCheck": true /* Skip type checking all .d.ts files. */
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user