merge upstream
This commit is contained in:
		
						commit
						cfff91ca5d
					
				@ -69,6 +69,7 @@
 | 
			
		||||
    "affinityBoost": false,
 | 
			
		||||
    "resourceBoost": false,
 | 
			
		||||
    "starDays": true,
 | 
			
		||||
    "galleonOfGhouls": 0,
 | 
			
		||||
    "eidolonOverride": "",
 | 
			
		||||
    "vallisOverride": "",
 | 
			
		||||
    "duviriOverride": "",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										80
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										80
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -13,6 +13,7 @@
 | 
			
		||||
        "@types/morgan": "^1.9.9",
 | 
			
		||||
        "@types/websocket": "^1.0.10",
 | 
			
		||||
        "@types/ws": "^8.18.1",
 | 
			
		||||
        "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
			
		||||
        "crc-32": "^1.2.2",
 | 
			
		||||
        "express": "^5",
 | 
			
		||||
        "json-with-bigint": "^3.4.4",
 | 
			
		||||
@ -21,7 +22,7 @@
 | 
			
		||||
        "ncp": "^2.0.0",
 | 
			
		||||
        "typescript": "^5.5",
 | 
			
		||||
        "undici": "^7.10.0",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.69",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.71",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
        "winston": "^3.17.0",
 | 
			
		||||
        "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
@ -30,7 +31,6 @@
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^8.28.0",
 | 
			
		||||
        "@typescript/native-preview": "^7.0.0-dev.20250523.1",
 | 
			
		||||
        "chokidar": "^4.0.3",
 | 
			
		||||
        "eslint": "^8",
 | 
			
		||||
        "eslint-plugin-prettier": "^5.2.5",
 | 
			
		||||
@ -605,10 +605,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-CgdgP/gmyaMThY7Fho19nDaTVryn9QV/zD/6w1KfDCn3M4Rq4WvkSc7Ob1ohc4V1XjCSIzg6Ul+HbLEc7xvV4Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-7781zmsKURCHknc37H4U4la4kZduyxmmUshZLBzNhPHhV5DKo++K8MF69kxhRG3/vS4HBhozf0YI0mZMIbkSDA==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "tsgo": "bin/tsgo.js"
 | 
			
		||||
@ -617,23 +616,22 @@
 | 
			
		||||
        "node": ">=20.6.0"
 | 
			
		||||
      },
 | 
			
		||||
      "optionalDependencies": {
 | 
			
		||||
        "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250523.1",
 | 
			
		||||
        "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250523.1",
 | 
			
		||||
        "@typescript/native-preview-linux-arm": "7.0.0-dev.20250523.1",
 | 
			
		||||
        "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250523.1",
 | 
			
		||||
        "@typescript/native-preview-linux-x64": "7.0.0-dev.20250523.1",
 | 
			
		||||
        "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250523.1",
 | 
			
		||||
        "@typescript/native-preview-win32-x64": "7.0.0-dev.20250523.1"
 | 
			
		||||
        "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250625.1",
 | 
			
		||||
        "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250625.1",
 | 
			
		||||
        "@typescript/native-preview-linux-arm": "7.0.0-dev.20250625.1",
 | 
			
		||||
        "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250625.1",
 | 
			
		||||
        "@typescript/native-preview-linux-x64": "7.0.0-dev.20250625.1",
 | 
			
		||||
        "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250625.1",
 | 
			
		||||
        "@typescript/native-preview-win32-x64": "7.0.0-dev.20250625.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-darwin-arm64": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-oWJMPD+lfH9/dvHhPSZdTv43lfyZGrn7crytefhkiQPSwP0MIUCpnDkofGP/ML1nv0xx0pwWhH+Ein88NW3LuA==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-JcLCql0O6+0iHIMllvax02kqpNtY1RUckGKomuO5kSbrOo9PsR+6r5MEcspfj47gwOl7AS0vrGhBCFFogF+KGw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -644,13 +642,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-darwin-x64": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-Yk8bJEsYsRKgRqYlwPvh7DPdgBMC/oPN60X0LWeuMLci65+4kyqF8Cv6K/W3ABc005cB4tYn4iR+9T6zipvrKw==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-0vCkk3FdS92W625JyzA8Slu/0vgkeu10fRQNfgIbf+E29DKMKnwXW56WhHSdGXAivU44Mewwc589+CbsABq3Sw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -661,13 +658,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-linux-arm": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-B+8CRIv6ebL8gzAagnJP8wml3baFV2FtFWuXYl6jlAcLGoQOh/yGdcAueZoJjJKNod4gAOl8OJoTicuC0BVIxw==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-MumU7p+09ikH/x5IOJRV6DUj6N5/0kSlI4IsAUPtpT2WGkQdDtL2CC523/94YvOfWB1/+9r01636LVCGOJ135g==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -678,13 +674,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-linux-arm64": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-IErNI08z9qE6mHaJaT6tM7il8j21ryH3DNVyFP4yz5FTKnkXFj1Kb4NcI41Q8w226LTQgBR8kNErVlbUWr7ywA==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-IgnoWQSKeoeL7Y7tvlbcDQx0nidK3UWa/bbm1zJv+AfQlAGMrEMygp+ZzocmycUCYOVM0dcIbymjoiI/QRHTng==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -695,13 +690,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-linux-x64": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-TCZtknsLUgPRaEfX9CvBZNgrHhMRZPYYZgF1Aasdv0PONv9mB8w0Xforgxoo4UFjdF5ZzOu2icgc7sKJJeu5vw==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-6fE8piqPfzPPqmQ37ewTSbm4HW0cNqOEhfLG2F37zJd4525mefhIpWvj2iCkEHWp+BDlF2dYCbB4cY2nmfrNNw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -712,13 +706,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-win32-arm64": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-bulwrkLEkoY4Jqeuvfz24RiVOiZZ7Rr9TblFqZAgZFZOnyXuhjM1jE8F1hnJFC5AghJe2HdLD3EKfabqlffrIw==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ppCkjBAFotPxL8j9Vk5cNSwMreOvAt02AMa5Hko3JQGSVA2TQCIlvTFn+SHSIWzYbzomc9j4j5WOcOR0rmAAHg==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -729,13 +722,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript/native-preview-win32-x64": {
 | 
			
		||||
      "version": "7.0.0-dev.20250523.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250523.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ztzfO0oF/rj8xO5y3SyAcigmgvgczrqobCugEWFqiYumteWZPN2MYWcNYk2k8Y5LAgg1fN1xHIg8RRSPoo6XUg==",
 | 
			
		||||
      "version": "7.0.0-dev.20250625.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250625.1.tgz",
 | 
			
		||||
      "integrity": "sha512-BsnJqso5MKAW4Y7fPmcamJ+EIrWOTqwLjeZP74NNFvTqCsA4RkITCw4NpLwD0lzrv9VsQcQ+bNwB8DrT+oDqoQ==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
@ -3396,9 +3388,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-public-export-plus": {
 | 
			
		||||
      "version": "0.5.69",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.69.tgz",
 | 
			
		||||
      "integrity": "sha512-vTU1tUzqpihzpseUSJMrM82pYbCDZCfW40jXIi+Ol9B3a3Acz0DccfP7i4eoXf7Abahu4H/sjRt/nSHLNBvLHA=="
 | 
			
		||||
      "version": "0.5.71",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.71.tgz",
 | 
			
		||||
      "integrity": "sha512-TCS2wPRsBzuURJlIMDhygAHaLsKVZ7dGuC73WZ/iMyn3gKVwA98nnaIj24D+UceWS08fwq4ilWAfUzHJd6X29A=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-riven-info": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							@ -5,8 +5,10 @@
 | 
			
		||||
  "main": "index.ts",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
 | 
			
		||||
    "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:dev": "tsc --incremental --sourceMap",
 | 
			
		||||
    "build": "tsgo --sourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:dev": "tsgo --sourceMap",
 | 
			
		||||
    "build:dev:tsc": "tsc --incremental --sourceMap",
 | 
			
		||||
    "build-and-start": "npm run build && npm run start",
 | 
			
		||||
    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
			
		||||
    "dev": "node scripts/dev.js",
 | 
			
		||||
@ -25,6 +27,7 @@
 | 
			
		||||
    "@types/morgan": "^1.9.9",
 | 
			
		||||
    "@types/websocket": "^1.0.10",
 | 
			
		||||
    "@types/ws": "^8.18.1",
 | 
			
		||||
    "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
			
		||||
    "crc-32": "^1.2.2",
 | 
			
		||||
    "express": "^5",
 | 
			
		||||
    "json-with-bigint": "^3.4.4",
 | 
			
		||||
@ -33,7 +36,7 @@
 | 
			
		||||
    "ncp": "^2.0.0",
 | 
			
		||||
    "typescript": "^5.5",
 | 
			
		||||
    "undici": "^7.10.0",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.69",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.71",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
    "winston": "^3.17.0",
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
@ -42,7 +45,6 @@
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^8.28.0",
 | 
			
		||||
    "@typescript/native-preview": "^7.0.0-dev.20250523.1",
 | 
			
		||||
    "chokidar": "^4.0.3",
 | 
			
		||||
    "eslint": "^8",
 | 
			
		||||
    "eslint-plugin-prettier": "^5.2.5",
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { ExportChallenges } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export const claimJunctionChallengeRewardController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const data = getJSONfromString<IClaimJunctionChallengeRewardRequest>(String(req.body));
 | 
			
		||||
    const challengeProgress = inventory.ChallengeProgress.find(x => x.Name == data.Challenge)!;
 | 
			
		||||
    if (challengeProgress.ReceivedJunctionReward) {
 | 
			
		||||
        throw new Error(`attempt to double-claim junction reward`);
 | 
			
		||||
    }
 | 
			
		||||
    challengeProgress.ReceivedJunctionReward = true;
 | 
			
		||||
    inventory.ClaimedJunctionChallengeRewards ??= [];
 | 
			
		||||
    inventory.ClaimedJunctionChallengeRewards.push(data.Challenge);
 | 
			
		||||
    const challengeMeta = Object.entries(ExportChallenges).find(arr => arr[0].endsWith("/" + data.Challenge))![1];
 | 
			
		||||
    const inventoryChanges = {};
 | 
			
		||||
    for (const reward of challengeMeta.countedRewards!) {
 | 
			
		||||
        combineInventoryChanges(
 | 
			
		||||
            inventoryChanges,
 | 
			
		||||
            (await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount)).InventoryChanges
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        inventoryChanges: inventoryChanges // Yeah, it's "inventoryChanges" in the response here.
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IClaimJunctionChallengeRewardRequest {
 | 
			
		||||
    Challenge: string;
 | 
			
		||||
}
 | 
			
		||||
@ -22,7 +22,8 @@ import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
 | 
			
		||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
			
		||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
			
		||||
import { Ship } from "@/src/models/shipModel";
 | 
			
		||||
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { Inbox } from "@/src/models/inboxModel";
 | 
			
		||||
 | 
			
		||||
export const inventoryController: RequestHandler = async (request, response) => {
 | 
			
		||||
    const account = await getAccountForRequest(request);
 | 
			
		||||
@ -128,13 +129,21 @@ export const getInventoryResponse = async (
 | 
			
		||||
    xpBasedLevelCapDisabled: boolean,
 | 
			
		||||
    buildLabel: string | undefined
 | 
			
		||||
): Promise<IInventoryClient> => {
 | 
			
		||||
    const [inventoryWithLoadOutPresets, ships] = await Promise.all([
 | 
			
		||||
    const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
 | 
			
		||||
        inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
 | 
			
		||||
        Ship.find({ ShipOwnerId: inventory.accountOwnerId })
 | 
			
		||||
        Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
 | 
			
		||||
        Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
 | 
			
		||||
    ]);
 | 
			
		||||
    const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
 | 
			
		||||
    inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
 | 
			
		||||
 | 
			
		||||
    // In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
 | 
			
		||||
    if (latestMessage) {
 | 
			
		||||
        inventoryResponse.Mailbox = {
 | 
			
		||||
            LastInboxId: toOid(latestMessage._id)
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.infiniteCredits) {
 | 
			
		||||
        inventoryResponse.RegularCredits = 999999999;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { getInventory, addRecipes } from "@/src/services/inventoryService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { ExportRecipes } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "Recipes");
 | 
			
		||||
    const allHelminthRecipes = Object.keys(ExportRecipes).filter(
 | 
			
		||||
        key => ExportRecipes[key].secretIngredientAction === "SIA_WARFRAME_ABILITY"
 | 
			
		||||
    );
 | 
			
		||||
    const inventoryHelminthRecipes = inventory.Recipes.filter(recipe =>
 | 
			
		||||
        recipe.ItemType.startsWith("/Lotus/Types/Recipes/AbilityOverrides/")
 | 
			
		||||
    ).map(recipe => recipe.ItemType);
 | 
			
		||||
 | 
			
		||||
    const missingHelminthRecipes = allHelminthRecipes
 | 
			
		||||
        .filter(key => !inventoryHelminthRecipes.includes(key))
 | 
			
		||||
        .map(ItemType => ({ ItemType, ItemCount: 1 }));
 | 
			
		||||
 | 
			
		||||
    addRecipes(inventory, missingHelminthRecipes);
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
@ -12,18 +12,24 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const MissionRewards: IMissionReward[] = [];
 | 
			
		||||
    for (const [tag, node] of Object.entries(ExportRegions)) {
 | 
			
		||||
        if (!inventory.Missions.find(x => x.Tag == tag)) {
 | 
			
		||||
            inventory.Missions.push({
 | 
			
		||||
                Completes: 1,
 | 
			
		||||
                Tier: 1,
 | 
			
		||||
                Tag: tag
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        let mission = inventory.Missions.find(x => x.Tag == tag);
 | 
			
		||||
        if (!mission) {
 | 
			
		||||
            mission =
 | 
			
		||||
                inventory.Missions[
 | 
			
		||||
                    inventory.Missions.push({
 | 
			
		||||
                        Completes: 0,
 | 
			
		||||
                        Tier: 0,
 | 
			
		||||
                        Tag: tag
 | 
			
		||||
                    }) - 1
 | 
			
		||||
                ];
 | 
			
		||||
        }
 | 
			
		||||
        if (mission.Completes == 0) {
 | 
			
		||||
            mission.Completes++;
 | 
			
		||||
            if (node.missionReward) {
 | 
			
		||||
                console.log(node.missionReward);
 | 
			
		||||
                addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        mission.Tier = 1;
 | 
			
		||||
    }
 | 
			
		||||
    for (const reward of MissionRewards) {
 | 
			
		||||
        await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);
 | 
			
		||||
 | 
			
		||||
@ -21,13 +21,14 @@ import mongoose from "mongoose";
 | 
			
		||||
import { JSONStringify } from "json-with-bigint";
 | 
			
		||||
import { startWebServer } from "./services/webService";
 | 
			
		||||
 | 
			
		||||
import { validateConfig } from "@/src/services/configWatcherService";
 | 
			
		||||
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
 | 
			
		||||
import { updateWorldStateCollections } from "./services/worldStateService";
 | 
			
		||||
 | 
			
		||||
// Patch JSON.stringify to work flawlessly with Bigints.
 | 
			
		||||
JSON.stringify = JSONStringify;
 | 
			
		||||
 | 
			
		||||
validateConfig();
 | 
			
		||||
syncConfigWithDatabase();
 | 
			
		||||
 | 
			
		||||
mongoose
 | 
			
		||||
    .connect(config.mongodbUrl)
 | 
			
		||||
 | 
			
		||||
@ -27,11 +27,12 @@ export interface IMessage {
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    highPriority?: boolean;
 | 
			
		||||
    lowPrioNewPlayers?: boolean;
 | 
			
		||||
    startDate?: Date;
 | 
			
		||||
    endDate?: Date;
 | 
			
		||||
    transmission?: string;
 | 
			
		||||
    att?: string[];
 | 
			
		||||
    countedAtt?: ITypeCount[];
 | 
			
		||||
    transmission?: string;
 | 
			
		||||
    startDate?: Date;
 | 
			
		||||
    endDate?: Date;
 | 
			
		||||
    goalTag?: string;
 | 
			
		||||
    CrossPlatform?: boolean;
 | 
			
		||||
    arg?: Arg[];
 | 
			
		||||
    gifts?: IGift[];
 | 
			
		||||
@ -107,6 +108,7 @@ const messageSchema = new Schema<IMessageDatabase>(
 | 
			
		||||
        lowPrioNewPlayers: Boolean,
 | 
			
		||||
        startDate: Date,
 | 
			
		||||
        endDate: Date,
 | 
			
		||||
        goalTag: String,
 | 
			
		||||
        date: { type: Date, required: true },
 | 
			
		||||
        r: Boolean,
 | 
			
		||||
        CrossPlatform: Boolean,
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Document, HydratedDocument, Model, Schema, Types, model } from "mongoose";
 | 
			
		||||
import { Document, Model, Schema, Types, model } from "mongoose";
 | 
			
		||||
import {
 | 
			
		||||
    IFlavourItem,
 | 
			
		||||
    IRawUpgrade,
 | 
			
		||||
@ -7,7 +7,6 @@ import {
 | 
			
		||||
    IBooster,
 | 
			
		||||
    IInventoryClient,
 | 
			
		||||
    ISlots,
 | 
			
		||||
    IMailboxDatabase,
 | 
			
		||||
    IDuviriInfo,
 | 
			
		||||
    IPendingRecipeDatabase,
 | 
			
		||||
    IPendingRecipeClient,
 | 
			
		||||
@ -54,7 +53,6 @@ import {
 | 
			
		||||
    IUpgradeDatabase,
 | 
			
		||||
    ICrewShipMemberDatabase,
 | 
			
		||||
    ICrewShipMemberClient,
 | 
			
		||||
    IMailboxClient,
 | 
			
		||||
    TEquipmentKey,
 | 
			
		||||
    equipmentKeys,
 | 
			
		||||
    IKubrowPetDetailsDatabase,
 | 
			
		||||
@ -99,7 +97,9 @@ import {
 | 
			
		||||
    IAccolades,
 | 
			
		||||
    IHubNpcCustomization,
 | 
			
		||||
    ILotusCustomization,
 | 
			
		||||
    IEndlessXpReward
 | 
			
		||||
    IEndlessXpReward,
 | 
			
		||||
    IPersonalGoalProgressDatabase,
 | 
			
		||||
    IPersonalGoalProgressClient
 | 
			
		||||
} from "../../types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IOid } from "../../types/commonTypes";
 | 
			
		||||
import {
 | 
			
		||||
@ -371,7 +371,7 @@ FlavourItemSchema.set("toJSON", {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const MailboxSchema = new Schema<IMailboxDatabase>(
 | 
			
		||||
/*const MailboxSchema = new Schema<IMailboxDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        LastInboxId: Schema.Types.ObjectId
 | 
			
		||||
    },
 | 
			
		||||
@ -384,7 +384,7 @@ MailboxSchema.set("toJSON", {
 | 
			
		||||
        delete mailboxDatabase.__v;
 | 
			
		||||
        (returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
});*/
 | 
			
		||||
 | 
			
		||||
const DuviriInfoSchema = new Schema<IDuviriInfo>(
 | 
			
		||||
    {
 | 
			
		||||
@ -457,11 +457,35 @@ const discoveredMarkerSchema = new Schema<IDiscoveredMarker>(
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        Best: Number,
 | 
			
		||||
        Count: Number,
 | 
			
		||||
        Tag: String,
 | 
			
		||||
        goalId: Types.ObjectId
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
personalGoalProgressSchema.set("toJSON", {
 | 
			
		||||
    virtuals: true,
 | 
			
		||||
    transform(_doc, obj) {
 | 
			
		||||
        const db = obj as IPersonalGoalProgressDatabase;
 | 
			
		||||
        const client = obj as IPersonalGoalProgressClient;
 | 
			
		||||
 | 
			
		||||
        client._id = toOid(db.goalId);
 | 
			
		||||
 | 
			
		||||
        delete obj.goalId;
 | 
			
		||||
        delete obj.__v;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const challengeProgressSchema = new Schema<IChallengeProgress>(
 | 
			
		||||
    {
 | 
			
		||||
        Progress: Number,
 | 
			
		||||
        Name: String,
 | 
			
		||||
        Completed: [String]
 | 
			
		||||
        Completed: { type: [String], default: undefined },
 | 
			
		||||
        ReceivedJunctionReward: Boolean,
 | 
			
		||||
        Name: { type: String, required: true }
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
@ -1630,7 +1654,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        //CompletedJobs: [Schema.Types.Mixed],
 | 
			
		||||
 | 
			
		||||
        //Game mission\ivent score example  "Tag": "WaterFight", "Best": 170, "Count": 1258,
 | 
			
		||||
        //PersonalGoalProgress: [Schema.Types.Mixed],
 | 
			
		||||
        PersonalGoalProgress: { type: [personalGoalProgressSchema], default: undefined },
 | 
			
		||||
 | 
			
		||||
        //Setting interface Style
 | 
			
		||||
        ThemeStyle: String,
 | 
			
		||||
@ -1701,7 +1725,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        //Unknown and system
 | 
			
		||||
        DuviriInfo: DuviriInfoSchema,
 | 
			
		||||
        LastInventorySync: Schema.Types.ObjectId,
 | 
			
		||||
        Mailbox: MailboxSchema,
 | 
			
		||||
        //Mailbox: MailboxSchema,
 | 
			
		||||
        HandlerPoints: Number,
 | 
			
		||||
        ChallengesFixVersion: Number,
 | 
			
		||||
        PlayedParkourTutorial: Boolean,
 | 
			
		||||
@ -1754,7 +1778,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
 | 
			
		||||
        LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined },
 | 
			
		||||
 | 
			
		||||
        HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined }
 | 
			
		||||
        HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined },
 | 
			
		||||
 | 
			
		||||
        ClaimedJunctionChallengeRewards: { type: [String], default: undefined }
 | 
			
		||||
    },
 | 
			
		||||
    { timestamps: { createdAt: "Created", updatedAt: false } }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,7 @@ const placedDecosSchema = new Schema<IPlacedDecosDatabase>(
 | 
			
		||||
        Pos: [Number],
 | 
			
		||||
        Rot: [Number],
 | 
			
		||||
        Scale: Number,
 | 
			
		||||
        Sockets: Number,
 | 
			
		||||
        PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined }
 | 
			
		||||
    },
 | 
			
		||||
    { id: false }
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootCo
 | 
			
		||||
import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
 | 
			
		||||
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
 | 
			
		||||
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
 | 
			
		||||
import { claimJunctionChallengeRewardController } from "@/src/controllers/api/claimJunctionChallengeRewardController";
 | 
			
		||||
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
 | 
			
		||||
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
 | 
			
		||||
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
 | 
			
		||||
@ -237,6 +238,7 @@ apiRouter.post("/artifacts.php", artifactsController);
 | 
			
		||||
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
 | 
			
		||||
apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
 | 
			
		||||
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
 | 
			
		||||
apiRouter.post("/claimJunctionChallengeReward.php", claimJunctionChallengeRewardController);
 | 
			
		||||
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
 | 
			
		||||
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
 | 
			
		||||
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAl
 | 
			
		||||
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
 | 
			
		||||
import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController";
 | 
			
		||||
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
 | 
			
		||||
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
 | 
			
		||||
 | 
			
		||||
import { createAccountController } from "@/src/controllers/custom/createAccountController";
 | 
			
		||||
import { createMessageController } from "@/src/controllers/custom/createMessageController";
 | 
			
		||||
@ -42,6 +43,7 @@ customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
 | 
			
		||||
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
 | 
			
		||||
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
 | 
			
		||||
customRouter.get("/completeAllMissions", completeAllMissionsController);
 | 
			
		||||
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
 | 
			
		||||
 | 
			
		||||
customRouter.post("/createAccount", createAccountController);
 | 
			
		||||
customRouter.post("/createMessage", createMessageController);
 | 
			
		||||
 | 
			
		||||
@ -76,6 +76,7 @@ export interface IConfig {
 | 
			
		||||
        affinityBoost?: boolean;
 | 
			
		||||
        resourceBoost?: boolean;
 | 
			
		||||
        starDays?: boolean;
 | 
			
		||||
        galleonOfGhouls?: number;
 | 
			
		||||
        eidolonOverride?: string;
 | 
			
		||||
        vallisOverride?: string;
 | 
			
		||||
        duviriOverride?: string;
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import fsPromises from "fs/promises";
 | 
			
		||||
import { logger } from "../utils/logger";
 | 
			
		||||
import { config, configPath, loadConfig } from "./configService";
 | 
			
		||||
import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
 | 
			
		||||
import { Inbox } from "../models/inboxModel";
 | 
			
		||||
 | 
			
		||||
let amnesia = false;
 | 
			
		||||
fs.watchFile(configPath, (now, then) => {
 | 
			
		||||
@ -22,6 +23,7 @@ fs.watchFile(configPath, (now, then) => {
 | 
			
		||||
            process.exit(1);
 | 
			
		||||
        }
 | 
			
		||||
        validateConfig();
 | 
			
		||||
        syncConfigWithDatabase();
 | 
			
		||||
 | 
			
		||||
        const webPorts = getWebPorts();
 | 
			
		||||
        if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
 | 
			
		||||
@ -51,6 +53,15 @@ export const validateConfig = (): void => {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (
 | 
			
		||||
        config.worldState?.galleonOfGhouls &&
 | 
			
		||||
        config.worldState.galleonOfGhouls != 1 &&
 | 
			
		||||
        config.worldState.galleonOfGhouls != 2 &&
 | 
			
		||||
        config.worldState.galleonOfGhouls != 3
 | 
			
		||||
    ) {
 | 
			
		||||
        config.worldState.galleonOfGhouls = 0;
 | 
			
		||||
        modified = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (modified) {
 | 
			
		||||
        logger.info(`Updating config file to fix some issues with it.`);
 | 
			
		||||
        void saveConfig();
 | 
			
		||||
@ -61,3 +72,10 @@ export const saveConfig = async (): Promise<void> => {
 | 
			
		||||
    amnesia = true;
 | 
			
		||||
    await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const syncConfigWithDatabase = (): void => {
 | 
			
		||||
    // Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
 | 
			
		||||
    if (!config.worldState?.galleonOfGhouls) {
 | 
			
		||||
        void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {}); // For some reason, I can't just do `Inbox.deleteMany(...)`; it needs this whole circus.
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -54,6 +54,22 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
 | 
			
		||||
    if (config.worldState?.galleonOfGhouls) {
 | 
			
		||||
        if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
 | 
			
		||||
            newEventMessages.push({
 | 
			
		||||
                sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
 | 
			
		||||
                sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
 | 
			
		||||
                msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
 | 
			
		||||
                icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
 | 
			
		||||
                transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
 | 
			
		||||
                att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
 | 
			
		||||
                startDate: new Date(),
 | 
			
		||||
                goalTag: "GalleonRobbery"
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (newEventMessages.length === 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,7 @@ import {
 | 
			
		||||
import { updateQuestKey } from "@/src/services/questService";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService";
 | 
			
		||||
import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService";
 | 
			
		||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
 | 
			
		||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
@ -609,6 +609,47 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                inventoryChanges.RegularCredits -= value;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "GoalProgress": {
 | 
			
		||||
                for (const uploadProgress of value) {
 | 
			
		||||
                    const goal = getWorldState().Goals.find(x => x._id.$oid == uploadProgress._id.$oid);
 | 
			
		||||
                    if (goal && goal.Personal) {
 | 
			
		||||
                        inventory.PersonalGoalProgress ??= [];
 | 
			
		||||
                        const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid));
 | 
			
		||||
                        if (goalProgress) {
 | 
			
		||||
                            goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
 | 
			
		||||
                            goalProgress.Count += uploadProgress.Count;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            inventory.PersonalGoalProgress.push({
 | 
			
		||||
                                Best: uploadProgress.Best,
 | 
			
		||||
                                Count: uploadProgress.Count,
 | 
			
		||||
                                Tag: goal.Tag,
 | 
			
		||||
                                goalId: new Types.ObjectId(goal._id.$oid)
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            if (
 | 
			
		||||
                                goal.Reward &&
 | 
			
		||||
                                goal.Reward.items &&
 | 
			
		||||
                                goal.MissionKeyName &&
 | 
			
		||||
                                goal.MissionKeyName in goalMessagesByKey
 | 
			
		||||
                            ) {
 | 
			
		||||
                                // Send reward via inbox
 | 
			
		||||
                                const info = goalMessagesByKey[goal.MissionKeyName];
 | 
			
		||||
                                await createMessage(inventory.accountOwnerId, [
 | 
			
		||||
                                    {
 | 
			
		||||
                                        sndr: info.sndr,
 | 
			
		||||
                                        msg: info.msg,
 | 
			
		||||
                                        att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)),
 | 
			
		||||
                                        sub: info.sub,
 | 
			
		||||
                                        icon: info.icon,
 | 
			
		||||
                                        highPriority: true
 | 
			
		||||
                                    }
 | 
			
		||||
                                ]);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "InvasionProgress": {
 | 
			
		||||
                for (const clientProgress of value) {
 | 
			
		||||
                    const dbProgress = inventory.QualifyingInvasions.find(x =>
 | 
			
		||||
@ -962,6 +1003,14 @@ export const addMissionRewards = async (
 | 
			
		||||
 | 
			
		||||
    let missionCompletionCredits = 0;
 | 
			
		||||
    //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
 | 
			
		||||
 | 
			
		||||
    if (rewardInfo.goalId) {
 | 
			
		||||
        const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
 | 
			
		||||
        if (goal?.MissionKeyName) {
 | 
			
		||||
            levelKeyName = goal.MissionKeyName;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (levelKeyName) {
 | 
			
		||||
        const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
 | 
			
		||||
        //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
 | 
			
		||||
@ -1978,3 +2027,24 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } =>
 | 
			
		||||
    }
 | 
			
		||||
    return { nodes, buddies };
 | 
			
		||||
};*/
 | 
			
		||||
 | 
			
		||||
const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string; icon: string }> = {
 | 
			
		||||
    "/Lotus/Types/Keys/GalleonRobberyAlert": {
 | 
			
		||||
        sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
 | 
			
		||||
        msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgA",
 | 
			
		||||
        sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleA",
 | 
			
		||||
        icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
 | 
			
		||||
    },
 | 
			
		||||
    "/Lotus/Types/Keys/GalleonRobberyAlertB": {
 | 
			
		||||
        sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
 | 
			
		||||
        msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgB",
 | 
			
		||||
        sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleB",
 | 
			
		||||
        icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
 | 
			
		||||
    },
 | 
			
		||||
    "/Lotus/Types/Keys/GalleonRobberyAlertC": {
 | 
			
		||||
        sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
 | 
			
		||||
        msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC",
 | 
			
		||||
        sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC",
 | 
			
		||||
        icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -107,6 +107,16 @@ export class SRng {
 | 
			
		||||
        return arr[this.randomInt(0, arr.length - 1)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    randomElementPop<T>(arr: T[]): T | undefined {
 | 
			
		||||
        if (arr.length != 0) {
 | 
			
		||||
            const index = this.randomInt(0, arr.length - 1);
 | 
			
		||||
            const elm = arr[index];
 | 
			
		||||
            arr.splice(index, 1);
 | 
			
		||||
            return elm;
 | 
			
		||||
        }
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    randomFloat(): number {
 | 
			
		||||
        this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
 | 
			
		||||
        return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
 | 
			
		||||
 | 
			
		||||
@ -8,11 +8,12 @@ import {
 | 
			
		||||
} from "@/src/types/shipTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { addShipDecorations, getInventory } from "./inventoryService";
 | 
			
		||||
import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService";
 | 
			
		||||
import { config } from "./configService";
 | 
			
		||||
import { Guild } from "../models/guildModel";
 | 
			
		||||
import { hasGuildPermission } from "./guildService";
 | 
			
		||||
import { GuildPermission } from "../types/guildTypes";
 | 
			
		||||
import { ExportResources } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export const setShipCustomizations = async (
 | 
			
		||||
    accountId: string,
 | 
			
		||||
@ -101,6 +102,7 @@ export const handleSetShipDecorations = async (
 | 
			
		||||
            Pos: placedDecoration.Pos,
 | 
			
		||||
            Rot: placedDecoration.Rot,
 | 
			
		||||
            Scale: placedDecoration.Scale,
 | 
			
		||||
            Sockets: placedDecoration.Sockets,
 | 
			
		||||
            _id: placedDecoration.MoveId
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -116,12 +118,19 @@ export const handleSetShipDecorations = async (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (placedDecoration.RemoveId) {
 | 
			
		||||
        roomToPlaceIn.PlacedDecos.pull({ _id: placedDecoration.RemoveId });
 | 
			
		||||
        const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId));
 | 
			
		||||
        const deco = roomToPlaceIn.PlacedDecos[decoIndex];
 | 
			
		||||
        roomToPlaceIn.PlacedDecos.splice(decoIndex, 1);
 | 
			
		||||
        await personalRooms.save();
 | 
			
		||||
 | 
			
		||||
        if (!config.unlockAllShipDecorations) {
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: 1 }]);
 | 
			
		||||
            const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
 | 
			
		||||
            if (deco.Sockets !== undefined) {
 | 
			
		||||
                addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
 | 
			
		||||
            } else {
 | 
			
		||||
                addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
 | 
			
		||||
            }
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -134,7 +143,14 @@ export const handleSetShipDecorations = async (
 | 
			
		||||
    } else {
 | 
			
		||||
        if (!config.unlockAllShipDecorations) {
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: -1 }]);
 | 
			
		||||
            const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
 | 
			
		||||
            if (placedDecoration.Sockets !== undefined) {
 | 
			
		||||
                addFusionTreasures(inventory, [
 | 
			
		||||
                    { ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }
 | 
			
		||||
                ]);
 | 
			
		||||
            } else {
 | 
			
		||||
                addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
 | 
			
		||||
            }
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -148,6 +164,7 @@ export const handleSetShipDecorations = async (
 | 
			
		||||
        Pos: placedDecoration.Pos,
 | 
			
		||||
        Rot: placedDecoration.Rot,
 | 
			
		||||
        Scale: placedDecoration.Scale,
 | 
			
		||||
        Sockets: placedDecoration.Sockets,
 | 
			
		||||
        _id: decoId
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
 | 
			
		||||
    SORTIE_BOSS_VOR: "SolNode108"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const eidolonJobs = [
 | 
			
		||||
const eidolonJobs: readonly string[] = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
 | 
			
		||||
@ -117,14 +117,14 @@ const eidolonJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const eidolonNarmerJobs = [
 | 
			
		||||
const eidolonNarmerJobs: readonly string[] = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const venusJobs = [
 | 
			
		||||
const venusJobs: readonly string[] = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
 | 
			
		||||
@ -150,14 +150,14 @@ const venusJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const venusNarmerJobs = [
 | 
			
		||||
const venusNarmerJobs: readonly string[] = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const microplanetJobs = [
 | 
			
		||||
const microplanetJobs: readonly string[] = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
 | 
			
		||||
@ -167,7 +167,7 @@ const microplanetJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const microplanetEndlessJobs = [
 | 
			
		||||
const microplanetEndlessJobs: readonly string[] = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
 | 
			
		||||
    "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
 | 
			
		||||
@ -498,6 +498,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        const rng = new SRng(seed);
 | 
			
		||||
        const pool = [...eidolonJobs];
 | 
			
		||||
        syndicateMissions.push({
 | 
			
		||||
            _id: {
 | 
			
		||||
                $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
 | 
			
		||||
@ -509,7 +510,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
            Nodes: [],
 | 
			
		||||
            Jobs: [
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(eidolonJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 0,
 | 
			
		||||
                    minEnemyLevel: 5,
 | 
			
		||||
@ -517,7 +518,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(eidolonJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 1,
 | 
			
		||||
                    minEnemyLevel: 10,
 | 
			
		||||
@ -525,7 +526,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(eidolonJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 2,
 | 
			
		||||
                    minEnemyLevel: 20,
 | 
			
		||||
@ -533,7 +534,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(eidolonJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 3,
 | 
			
		||||
                    minEnemyLevel: 30,
 | 
			
		||||
@ -541,7 +542,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(eidolonJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 5,
 | 
			
		||||
                    minEnemyLevel: 40,
 | 
			
		||||
@ -549,7 +550,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(eidolonJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 10,
 | 
			
		||||
                    minEnemyLevel: 100,
 | 
			
		||||
@ -570,6 +571,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        const rng = new SRng(seed);
 | 
			
		||||
        const pool = [...venusJobs];
 | 
			
		||||
        syndicateMissions.push({
 | 
			
		||||
            _id: {
 | 
			
		||||
                $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
 | 
			
		||||
@ -581,7 +583,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
            Nodes: [],
 | 
			
		||||
            Jobs: [
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(venusJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 0,
 | 
			
		||||
                    minEnemyLevel: 5,
 | 
			
		||||
@ -589,7 +591,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(venusJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 1,
 | 
			
		||||
                    minEnemyLevel: 10,
 | 
			
		||||
@ -597,7 +599,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(venusJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 2,
 | 
			
		||||
                    minEnemyLevel: 20,
 | 
			
		||||
@ -605,7 +607,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(venusJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 3,
 | 
			
		||||
                    minEnemyLevel: 30,
 | 
			
		||||
@ -613,7 +615,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(venusJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 5,
 | 
			
		||||
                    minEnemyLevel: 40,
 | 
			
		||||
@ -621,7 +623,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(venusJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 10,
 | 
			
		||||
                    minEnemyLevel: 100,
 | 
			
		||||
@ -642,6 +644,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        const rng = new SRng(seed);
 | 
			
		||||
        const pool = [...microplanetJobs];
 | 
			
		||||
        syndicateMissions.push({
 | 
			
		||||
            _id: {
 | 
			
		||||
                $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
 | 
			
		||||
@ -653,7 +656,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
            Nodes: [],
 | 
			
		||||
            Jobs: [
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(microplanetJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 0,
 | 
			
		||||
                    minEnemyLevel: 5,
 | 
			
		||||
@ -661,7 +664,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 3, 12, 18)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(microplanetJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
 | 
			
		||||
                    masteryReq: 1,
 | 
			
		||||
                    minEnemyLevel: 15,
 | 
			
		||||
@ -678,7 +681,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: [14, 14, 14]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(microplanetJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
 | 
			
		||||
                    masteryReq: 2,
 | 
			
		||||
                    minEnemyLevel: 30,
 | 
			
		||||
@ -686,7 +689,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 4, 72, 88)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(microplanetJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
 | 
			
		||||
                    masteryReq: 3,
 | 
			
		||||
                    minEnemyLevel: 40,
 | 
			
		||||
@ -694,7 +697,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
 | 
			
		||||
                    xpAmounts: generateXpAmounts(rng, 5, 115, 135)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    jobType: rng.randomElement(microplanetJobs),
 | 
			
		||||
                    jobType: rng.randomElementPop(pool),
 | 
			
		||||
                    rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
 | 
			
		||||
                    masteryReq: 10,
 | 
			
		||||
                    minEnemyLevel: 100,
 | 
			
		||||
@ -1146,6 +1149,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
            Node: "SolarisUnitedHub1"
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    // The client gets kinda confused when multiple goals have the same tag, so considering these mutually exclusive.
 | 
			
		||||
    if (config.worldState?.galleonOfGhouls == 1) {
 | 
			
		||||
        worldState.Goals.push({
 | 
			
		||||
            _id: { $oid: "6814ddf00000000000000000" },
 | 
			
		||||
            Activation: { $date: { $numberLong: "1746198000000" } },
 | 
			
		||||
            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
			
		||||
            Count: 0,
 | 
			
		||||
            Goal: 1,
 | 
			
		||||
            Success: 0,
 | 
			
		||||
            Personal: true,
 | 
			
		||||
            Bounty: true,
 | 
			
		||||
            ClampNodeScores: true,
 | 
			
		||||
            Node: "EventNode19",
 | 
			
		||||
            MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlert",
 | 
			
		||||
            Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
 | 
			
		||||
            Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
 | 
			
		||||
            Tag: "GalleonRobbery",
 | 
			
		||||
            Reward: {
 | 
			
		||||
                items: [
 | 
			
		||||
                    "/Lotus/StoreItems/Types/Recipes/Weapons/GrnChainSawTonfaBlueprint",
 | 
			
		||||
                    "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else if (config.worldState?.galleonOfGhouls == 2) {
 | 
			
		||||
        worldState.Goals.push({
 | 
			
		||||
            _id: { $oid: "681e18700000000000000000" },
 | 
			
		||||
            Activation: { $date: { $numberLong: "1746802800000" } },
 | 
			
		||||
            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
			
		||||
            Count: 0,
 | 
			
		||||
            Goal: 1,
 | 
			
		||||
            Success: 0,
 | 
			
		||||
            Personal: true,
 | 
			
		||||
            Bounty: true,
 | 
			
		||||
            ClampNodeScores: true,
 | 
			
		||||
            Node: "EventNode28",
 | 
			
		||||
            MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB",
 | 
			
		||||
            Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
 | 
			
		||||
            Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
 | 
			
		||||
            Tag: "GalleonRobbery",
 | 
			
		||||
            Reward: {
 | 
			
		||||
                items: [
 | 
			
		||||
                    "/Lotus/StoreItems/Types/Recipes/Weapons/MortiforShieldAndSwordBlueprint",
 | 
			
		||||
                    "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else if (config.worldState?.galleonOfGhouls == 3) {
 | 
			
		||||
        worldState.Goals.push({
 | 
			
		||||
            _id: { $oid: "682752f00000000000000000" },
 | 
			
		||||
            Activation: { $date: { $numberLong: "1747407600000" } },
 | 
			
		||||
            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
			
		||||
            Count: 0,
 | 
			
		||||
            Goal: 1,
 | 
			
		||||
            Success: 0,
 | 
			
		||||
            Personal: true,
 | 
			
		||||
            Bounty: true,
 | 
			
		||||
            ClampNodeScores: true,
 | 
			
		||||
            Node: "EventNode19",
 | 
			
		||||
            MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertC",
 | 
			
		||||
            Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
 | 
			
		||||
            Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
 | 
			
		||||
            Tag: "GalleonRobbery",
 | 
			
		||||
            Reward: {
 | 
			
		||||
                items: [
 | 
			
		||||
                    "/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle",
 | 
			
		||||
                    "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Nightwave Challenges
 | 
			
		||||
    const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
 | 
			
		||||
 | 
			
		||||
@ -56,6 +56,7 @@ export interface IInventoryDatabase
 | 
			
		||||
            | "QualifyingInvasions"
 | 
			
		||||
            | "LastInventorySync"
 | 
			
		||||
            | "EndlessXP"
 | 
			
		||||
            | "PersonalGoalProgress"
 | 
			
		||||
            | TEquipmentKey
 | 
			
		||||
        >,
 | 
			
		||||
        InventoryDatabaseEquipment {
 | 
			
		||||
@ -63,7 +64,7 @@ export interface IInventoryDatabase
 | 
			
		||||
    Created: Date;
 | 
			
		||||
    TrainingDate: Date;
 | 
			
		||||
    LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
 | 
			
		||||
    Mailbox?: IMailboxDatabase;
 | 
			
		||||
    //Mailbox?: IMailboxDatabase;
 | 
			
		||||
    GuildId?: Types.ObjectId;
 | 
			
		||||
    PendingRecipes: IPendingRecipeDatabase[];
 | 
			
		||||
    QuestKeys: IQuestKeyDatabase[];
 | 
			
		||||
@ -95,6 +96,7 @@ export interface IInventoryDatabase
 | 
			
		||||
    QualifyingInvasions: IInvasionProgressDatabase[];
 | 
			
		||||
    LastInventorySync?: Types.ObjectId;
 | 
			
		||||
    EndlessXP?: IEndlessXpProgressDatabase[];
 | 
			
		||||
    PersonalGoalProgress?: IPersonalGoalProgressDatabase[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IQuestKeyDatabase {
 | 
			
		||||
@ -150,9 +152,9 @@ export interface IMailboxClient {
 | 
			
		||||
    LastInboxId: IOid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IMailboxDatabase {
 | 
			
		||||
/*export interface IMailboxDatabase {
 | 
			
		||||
    LastInboxId: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
export type TSolarMapRegion =
 | 
			
		||||
    | "Earth"
 | 
			
		||||
@ -306,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    HWIDProtectEnabled?: boolean;
 | 
			
		||||
    //KubrowPetPrints: IKubrowPetPrint[];
 | 
			
		||||
    AlignmentReplay?: IAlignment;
 | 
			
		||||
    //PersonalGoalProgress: IPersonalGoalProgress[];
 | 
			
		||||
    PersonalGoalProgress?: IPersonalGoalProgressClient[];
 | 
			
		||||
    ThemeStyle: string;
 | 
			
		||||
    ThemeBackground: string;
 | 
			
		||||
    ThemeSounds: string;
 | 
			
		||||
@ -378,6 +380,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    LockedWeaponGroup?: ILockedWeaponGroupClient;
 | 
			
		||||
    HubNpcCustomizations?: IHubNpcCustomization[];
 | 
			
		||||
    Ship?: IOrbiter; // U22 and below, response only
 | 
			
		||||
    ClaimedJunctionChallengeRewards?: string[]; // U39
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IAffiliation {
 | 
			
		||||
@ -446,8 +449,9 @@ export interface IVendorPurchaseHistoryEntryDatabase {
 | 
			
		||||
 | 
			
		||||
export interface IChallengeProgress {
 | 
			
		||||
    Progress: number;
 | 
			
		||||
    Name: string;
 | 
			
		||||
    Completed?: string[];
 | 
			
		||||
    ReceivedJunctionReward?: boolean; // U39
 | 
			
		||||
    Name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICollectibleEntry {
 | 
			
		||||
@ -1015,13 +1019,17 @@ export interface IPeriodicMissionCompletionResponse extends Omit<IPeriodicMissio
 | 
			
		||||
    date: IMongoDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IPersonalGoalProgress {
 | 
			
		||||
export interface IPersonalGoalProgressClient {
 | 
			
		||||
    Best: number;
 | 
			
		||||
    Count: number;
 | 
			
		||||
    Tag: string;
 | 
			
		||||
    Best?: number;
 | 
			
		||||
    _id: IOid;
 | 
			
		||||
    ReceivedClanReward0?: boolean;
 | 
			
		||||
    ReceivedClanReward1?: boolean;
 | 
			
		||||
    //ReceivedClanReward0?: boolean;
 | 
			
		||||
    //ReceivedClanReward1?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IPersonalGoalProgressDatabase extends Omit<IPersonalGoalProgressClient, "_id"> {
 | 
			
		||||
    goalId: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IPersonalTechProjectDatabase {
 | 
			
		||||
 | 
			
		||||
@ -139,6 +139,14 @@ export type IMissionInventoryUpdateRequest = {
 | 
			
		||||
    };
 | 
			
		||||
    wagerTier?: number; // the index
 | 
			
		||||
    creditsFee?: number; // the index
 | 
			
		||||
    GoalProgress?: {
 | 
			
		||||
        _id: IOid;
 | 
			
		||||
        Count: number;
 | 
			
		||||
        Best: number;
 | 
			
		||||
        Tag: string;
 | 
			
		||||
        IsMultiProgress: boolean;
 | 
			
		||||
        MultiProgress: unknown[];
 | 
			
		||||
    }[];
 | 
			
		||||
    InvasionProgress?: IInvasionProgressClient[];
 | 
			
		||||
    ConquestMissionsCompleted?: number;
 | 
			
		||||
    duviriSuitSelection?: string;
 | 
			
		||||
@ -156,6 +164,8 @@ export type IMissionInventoryUpdateRequest = {
 | 
			
		||||
 | 
			
		||||
export interface IRewardInfo {
 | 
			
		||||
    node: string;
 | 
			
		||||
    goalId?: string;
 | 
			
		||||
    goalManifest?: string;
 | 
			
		||||
    invasionId?: string;
 | 
			
		||||
    invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
 | 
			
		||||
    sortieId?: string;
 | 
			
		||||
 | 
			
		||||
@ -96,6 +96,7 @@ export interface IPlacedDecosDatabase {
 | 
			
		||||
    Pos: [number, number, number];
 | 
			
		||||
    Rot: [number, number, number];
 | 
			
		||||
    Scale?: number;
 | 
			
		||||
    Sockets?: number;
 | 
			
		||||
    PictureFrameInfo?: IPictureFrameInfo;
 | 
			
		||||
    _id: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
@ -136,6 +137,7 @@ export interface IShipDecorationsRequest {
 | 
			
		||||
    MoveId?: string;
 | 
			
		||||
    OldRoom?: string;
 | 
			
		||||
    Scale?: number;
 | 
			
		||||
    Sockets?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IShipDecorationsResponse {
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { IMissionReward } from "warframe-public-export-plus";
 | 
			
		||||
import { IMongoDate, IOid } from "./commonTypes";
 | 
			
		||||
 | 
			
		||||
export interface IWorldState {
 | 
			
		||||
@ -37,11 +38,15 @@ export interface IGoal {
 | 
			
		||||
    Goal: number;
 | 
			
		||||
    Success: number;
 | 
			
		||||
    Personal: boolean;
 | 
			
		||||
    Bounty?: boolean;
 | 
			
		||||
    ClampNodeScores?: boolean;
 | 
			
		||||
    Desc: string;
 | 
			
		||||
    ToolTip: string;
 | 
			
		||||
    ToolTip?: string;
 | 
			
		||||
    Icon: string;
 | 
			
		||||
    Tag: string;
 | 
			
		||||
    Node: string;
 | 
			
		||||
    MissionKeyName?: string;
 | 
			
		||||
    Reward?: IMissionReward;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ISyndicateMissionInfo {
 | 
			
		||||
 | 
			
		||||
@ -794,6 +794,7 @@
 | 
			
		||||
                                    <button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></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>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
@ -1449,6 +1449,11 @@ function addMissingEquipment(categories) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function addMissingHelminthRecipes() {
 | 
			
		||||
    await revalidateAuthz();
 | 
			
		||||
    await fetch("/custom/addMissingHelminthBlueprints?" + window.authz);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addMissingEvolutionProgress() {
 | 
			
		||||
    const requests = [];
 | 
			
		||||
    document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
 | 
			
		||||
 | 
			
		||||
@ -181,6 +181,7 @@ dict = {
 | 
			
		||||
    cheats_account: `Account`,
 | 
			
		||||
    cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
 | 
			
		||||
    cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
 | 
			
		||||
    cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
 | 
			
		||||
    cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`,
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
 | 
			
		||||
    cheats_changeButton: `Ändern`,
 | 
			
		||||
 | 
			
		||||
@ -180,6 +180,7 @@ dict = {
 | 
			
		||||
    cheats_account: `Account`,
 | 
			
		||||
    cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,
 | 
			
		||||
    cheats_helminthUnlockAll: `Fully Level Up Helminth`,
 | 
			
		||||
    cheats_addMissingSubsumedAbilities: `Add Missing Subsumed Abilities`,
 | 
			
		||||
    cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`,
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Supported syndicate`,
 | 
			
		||||
    cheats_changeButton: `Change`,
 | 
			
		||||
 | 
			
		||||
@ -181,6 +181,7 @@ dict = {
 | 
			
		||||
    cheats_account: `Cuenta`,
 | 
			
		||||
    cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,
 | 
			
		||||
    cheats_helminthUnlockAll: `Subir al máximo el Helminto`,
 | 
			
		||||
    cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
 | 
			
		||||
    cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
 | 
			
		||||
    cheats_changeButton: `Cambiar`,
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,8 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Note : Les changements effectués ici seront appliqués lors de la syncrhonisation. Visiter la navigation appliquera les changements apportés à l'inventaire.`,
 | 
			
		||||
    general_addButton: `Ajouter`,
 | 
			
		||||
    general_bulkActions: `Action groupée`,
 | 
			
		||||
    code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
 | 
			
		||||
    code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
 | 
			
		||||
    code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
 | 
			
		||||
    code_regFail: `Enregistrement impossible. Compte existant?`,
 | 
			
		||||
    code_changeNameConfirm: `Nouveau nom du compte :`,
 | 
			
		||||
    code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`,
 | 
			
		||||
    code_archgun: `Archgun`,
 | 
			
		||||
@ -85,7 +85,7 @@ dict = {
 | 
			
		||||
    inventory_moaPets: `Moas`,
 | 
			
		||||
    inventory_kubrowPets: `Bêtes`,
 | 
			
		||||
    inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
 | 
			
		||||
    inventory_Boosters: `[UNTRANSLATED] Boosters`,
 | 
			
		||||
    inventory_Boosters: `Boosters`,
 | 
			
		||||
    inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
 | 
			
		||||
@ -100,7 +100,7 @@ dict = {
 | 
			
		||||
    inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`,
 | 
			
		||||
    inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
 | 
			
		||||
    inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`,
 | 
			
		||||
    inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`,
 | 
			
		||||
    inventory_maxPlexus: `Plexus au rang max`,
 | 
			
		||||
 | 
			
		||||
    quests_list: `Quêtes`,
 | 
			
		||||
    quests_completeAll: `Compléter toutes les quêtes`,
 | 
			
		||||
@ -135,10 +135,10 @@ dict = {
 | 
			
		||||
    cheats_infiniteRegalAya: `Aya Raffiné infini`,
 | 
			
		||||
    cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
 | 
			
		||||
    cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`,
 | 
			
		||||
    cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`,
 | 
			
		||||
    cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`,
 | 
			
		||||
    cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`,
 | 
			
		||||
    cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`,
 | 
			
		||||
    cheats_dontSubtractPurchaseCreditCost: `Ne pas retirer le coût en crédits`,
 | 
			
		||||
    cheats_dontSubtractPurchasePlatinumCost: `Ne pas retirer le coût en platines`,
 | 
			
		||||
    cheats_dontSubtractPurchaseItemCost: `Ne pas retirer le coût d'achat`,
 | 
			
		||||
    cheats_dontSubtractPurchaseStandingCost: `Ne pas retirer le coût en réputation`,
 | 
			
		||||
    cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`,
 | 
			
		||||
    cheats_dontSubtractConsumables: `Ne pas retirer de consommables`,
 | 
			
		||||
    cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
 | 
			
		||||
@ -158,11 +158,11 @@ dict = {
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
 | 
			
		||||
    cheats_noDeathMarks: `Aucune marque d'assassin`,
 | 
			
		||||
    cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
 | 
			
		||||
    cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
 | 
			
		||||
    cheats_fullyStockedVendors: `Les vendeurs ont un stock à 100%`,
 | 
			
		||||
    cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
 | 
			
		||||
    cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
 | 
			
		||||
    cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
 | 
			
		||||
    cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
 | 
			
		||||
    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`,
 | 
			
		||||
@ -173,73 +173,74 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`,
 | 
			
		||||
    cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
 | 
			
		||||
    cheats_fastClanAscension: `Ascension de clan rapide`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
 | 
			
		||||
    cheats_save: `[UNTRANSLATED] Save`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
 | 
			
		||||
    cheats_save: `Sauvegarder`,
 | 
			
		||||
    cheats_account: `Compte`,
 | 
			
		||||
    cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
 | 
			
		||||
    cheats_helminthUnlockAll: `Helminth niveau max`,
 | 
			
		||||
    cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
 | 
			
		||||
    cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Allégeance`,
 | 
			
		||||
    cheats_changeButton: `Changer`,
 | 
			
		||||
    cheats_none: `Aucun`,
 | 
			
		||||
    import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
 | 
			
		||||
    import_submit: `Soumettre`,
 | 
			
		||||
    import_samples: `[UNTRANSLATED] Samples:`,
 | 
			
		||||
    import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`,
 | 
			
		||||
    import_samples: `Echantillons :`,
 | 
			
		||||
    import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
 | 
			
		||||
 | 
			
		||||
    upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
 | 
			
		||||
    upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
 | 
			
		||||
    upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
 | 
			
		||||
    upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
 | 
			
		||||
    upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
 | 
			
		||||
    upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
 | 
			
		||||
    upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
 | 
			
		||||
    upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`,
 | 
			
		||||
    upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
 | 
			
		||||
    upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
 | 
			
		||||
    upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
 | 
			
		||||
    upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
 | 
			
		||||
    upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
 | 
			
		||||
    upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
 | 
			
		||||
    upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
 | 
			
		||||
    upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
 | 
			
		||||
    upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
 | 
			
		||||
    upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
 | 
			
		||||
    upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`,
 | 
			
		||||
    upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
 | 
			
		||||
    upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
 | 
			
		||||
    upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
 | 
			
		||||
    upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
 | 
			
		||||
    upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
 | 
			
		||||
    upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
 | 
			
		||||
    upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
 | 
			
		||||
    upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
 | 
			
		||||
    upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
 | 
			
		||||
    upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
 | 
			
		||||
    upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
 | 
			
		||||
    upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
 | 
			
		||||
    upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
 | 
			
		||||
    upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
 | 
			
		||||
    upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`,
 | 
			
		||||
    upgrade_MeleeCritDamage: `+|VAL|% de dégâts critique en mêlée`,
 | 
			
		||||
    upgrade_PrimaryStatusChance: `+|VAL|% de chance de statut sur arme primaire`,
 | 
			
		||||
    upgrade_SecondaryCritChance: `+|VAL|% de chance critique sur arme secondaire`,
 | 
			
		||||
    upgrade_WarframeAbilityDuration: `+|VAL|% de durée de pouvoir`,
 | 
			
		||||
    upgrade_WarframeAbilityStrength: `+|VAL|% de puissance de pouvoir`,
 | 
			
		||||
    upgrade_WarframeArmourMax: `+|VAL| d'armure`,
 | 
			
		||||
    upgrade_WarframeBlastProc: `+|VAL| de boucliers sur élimination avec des dégats d'explosion`,
 | 
			
		||||
    upgrade_WarframeCastingSpeed: `+|VAL|% de vitesse de lancement de pouvoir`,
 | 
			
		||||
    upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut corrosif`,
 | 
			
		||||
    upgrade_WarframeCorrosiveStack: `+|VAL| de cumuls maximum de Statut Corrosif`,
 | 
			
		||||
    upgrade_WarframeCritDamageBoost: `+|VAL|% de dégâts critique en mêlée (Doublé si l'énergie dépasse 500)`,
 | 
			
		||||
    upgrade_WarframeElectricDamage: `+|VAL1|% de dégâts électrique sur arme primaire (+|VAL2|% par fragment supplémentaire)`,
 | 
			
		||||
    upgrade_WarframeElectricDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut électrique`,
 | 
			
		||||
    upgrade_WarframeEnergyMax: `+|VAL| d'énergie max`,
 | 
			
		||||
    upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`,
 | 
			
		||||
    upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`,
 | 
			
		||||
    upgrade_WarframeHealthMax: `+|VAL| de santé`,
 | 
			
		||||
    upgrade_WarframeHPBoostFromImpact: `+|VAL1| de santé par ennemi tué avec des dégâts explosifs (Max |VAL2| de santé)`,
 | 
			
		||||
    upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`,
 | 
			
		||||
    upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`,
 | 
			
		||||
    upgrade_WarframeRegen: `+|VAL| régénération de santé/s`,
 | 
			
		||||
    upgrade_WarframeShieldMax: `+|VAL| de boucliers`,
 | 
			
		||||
    upgrade_WarframeStartingEnergy: `+|VAL|% d'énergie sur apparition`,
 | 
			
		||||
    upgrade_WarframeToxinDamage: `+|VAL|% de dégâts sur le statut poison`,
 | 
			
		||||
    upgrade_WarframeToxinHeal: `+|VAL| de santé récupérée à chaque dégât de statut poison`,
 | 
			
		||||
    upgrade_WeaponCritBoostFromHeat: `+|VAL1|% de chance critique sur arme secondaire pour chaque ennemi affecté puis tué par du feu (Max |VAL2|%)`,
 | 
			
		||||
    upgrade_AvatarAbilityRange: `+7.5% de portée de pouvoir`,
 | 
			
		||||
    upgrade_AvatarAbilityEfficiency: `+5% d'efficacité de pouvoir`,
 | 
			
		||||
    upgrade_AvatarEnergyRegen: `+0.5 de régénération d'énergie/s`,
 | 
			
		||||
    upgrade_AvatarEnemyRadar: `+5m de radar ennemi`,
 | 
			
		||||
    upgrade_AvatarLootRadar: `+7m de radar à butin`,
 | 
			
		||||
    upgrade_WeaponAmmoMax: `+15% de munitions max`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `-3% d'armure ennemi`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
 | 
			
		||||
    upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`,
 | 
			
		||||
    upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
 | 
			
		||||
    upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
 | 
			
		||||
    upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
 | 
			
		||||
    upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`,
 | 
			
		||||
    upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`,
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`,
 | 
			
		||||
    upgrade_AvatarTimeLimitIncrease: `+8s de temps de piratage`,
 | 
			
		||||
    upgrade_ElectrifyOnHack: `Electrifie les ennemis dans un rayon de 20m pendant un piratage`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`,
 | 
			
		||||
    upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`,
 | 
			
		||||
    upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `100% pour le prochain pouvoir de gagner +50% de puissance de pouvoir sur miséricorde`,
 | 
			
		||||
    upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`,
 | 
			
		||||
    upgrade_SwiftExecute: `Vitesse des miséricordes augmentée de 50%`,
 | 
			
		||||
    upgrade_OnHackInvis: `Invisible pendant 15 secondes après un piratage`,
 | 
			
		||||
 | 
			
		||||
    prettier_sucks_ass: ``
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -181,6 +181,7 @@ dict = {
 | 
			
		||||
    cheats_account: `Аккаунт`,
 | 
			
		||||
    cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,
 | 
			
		||||
    cheats_helminthUnlockAll: `Полностью улучшить Гельминта`,
 | 
			
		||||
    cheats_addMissingSubsumedAbilities: `Добавить отсутствующие поглощённые способности`,
 | 
			
		||||
    cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`,
 | 
			
		||||
    cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
 | 
			
		||||
    cheats_changeButton: `Изменить`,
 | 
			
		||||
 | 
			
		||||
@ -181,6 +181,7 @@ dict = {
 | 
			
		||||
    cheats_account: `账户`,
 | 
			
		||||
    cheats_unlockAllFocusSchools: `解锁所有专精学派`,
 | 
			
		||||
    cheats_helminthUnlockAll: `完全升级Helminth`,
 | 
			
		||||
    cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
 | 
			
		||||
    cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
 | 
			
		||||
    cheats_changeSupportedSyndicate: `支持的集团`,
 | 
			
		||||
    cheats_changeButton: `更改`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user