merge upstream

This commit is contained in:
Corvus 2025-06-25 22:03:38 -07:00
commit cfff91ca5d
32 changed files with 523 additions and 177 deletions

View File

@ -69,6 +69,7 @@
"affinityBoost": false, "affinityBoost": false,
"resourceBoost": false, "resourceBoost": false,
"starDays": true, "starDays": true,
"galleonOfGhouls": 0,
"eidolonOverride": "", "eidolonOverride": "",
"vallisOverride": "", "vallisOverride": "",
"duviriOverride": "", "duviriOverride": "",

80
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10", "@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.4.4",
@ -21,7 +22,7 @@
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"undici": "^7.10.0", "undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.69", "warframe-public-export-plus": "^0.5.71",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
@ -30,7 +31,6 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"eslint": "^8", "eslint": "^8",
"eslint-plugin-prettier": "^5.2.5", "eslint-plugin-prettier": "^5.2.5",
@ -605,10 +605,9 @@
} }
}, },
"node_modules/@typescript/native-preview": { "node_modules/@typescript/native-preview": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-CgdgP/gmyaMThY7Fho19nDaTVryn9QV/zD/6w1KfDCn3M4Rq4WvkSc7Ob1ohc4V1XjCSIzg6Ul+HbLEc7xvV4Q==", "integrity": "sha512-7781zmsKURCHknc37H4U4la4kZduyxmmUshZLBzNhPHhV5DKo++K8MF69kxhRG3/vS4HBhozf0YI0mZMIbkSDA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsgo": "bin/tsgo.js" "tsgo": "bin/tsgo.js"
@ -617,23 +616,22 @@
"node": ">=20.6.0" "node": ">=20.6.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@typescript/native-preview-darwin-arm64": "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.20250523.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-linux-arm": "7.0.0-dev.20250523.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20250625.1",
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20250523.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-linux-x64": "7.0.0-dev.20250523.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20250523.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-win32-x64": "7.0.0-dev.20250523.1" "@typescript/native-preview-win32-x64": "7.0.0-dev.20250625.1"
} }
}, },
"node_modules/@typescript/native-preview-darwin-arm64": { "node_modules/@typescript/native-preview-darwin-arm64": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-oWJMPD+lfH9/dvHhPSZdTv43lfyZGrn7crytefhkiQPSwP0MIUCpnDkofGP/ML1nv0xx0pwWhH+Ein88NW3LuA==", "integrity": "sha512-JcLCql0O6+0iHIMllvax02kqpNtY1RUckGKomuO5kSbrOo9PsR+6r5MEcspfj47gwOl7AS0vrGhBCFFogF+KGw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -644,13 +642,12 @@
} }
}, },
"node_modules/@typescript/native-preview-darwin-x64": { "node_modules/@typescript/native-preview-darwin-x64": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-Yk8bJEsYsRKgRqYlwPvh7DPdgBMC/oPN60X0LWeuMLci65+4kyqF8Cv6K/W3ABc005cB4tYn4iR+9T6zipvrKw==", "integrity": "sha512-0vCkk3FdS92W625JyzA8Slu/0vgkeu10fRQNfgIbf+E29DKMKnwXW56WhHSdGXAivU44Mewwc589+CbsABq3Sw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -661,13 +658,12 @@
} }
}, },
"node_modules/@typescript/native-preview-linux-arm": { "node_modules/@typescript/native-preview-linux-arm": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-B+8CRIv6ebL8gzAagnJP8wml3baFV2FtFWuXYl6jlAcLGoQOh/yGdcAueZoJjJKNod4gAOl8OJoTicuC0BVIxw==", "integrity": "sha512-MumU7p+09ikH/x5IOJRV6DUj6N5/0kSlI4IsAUPtpT2WGkQdDtL2CC523/94YvOfWB1/+9r01636LVCGOJ135g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -678,13 +674,12 @@
} }
}, },
"node_modules/@typescript/native-preview-linux-arm64": { "node_modules/@typescript/native-preview-linux-arm64": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-IErNI08z9qE6mHaJaT6tM7il8j21ryH3DNVyFP4yz5FTKnkXFj1Kb4NcI41Q8w226LTQgBR8kNErVlbUWr7ywA==", "integrity": "sha512-IgnoWQSKeoeL7Y7tvlbcDQx0nidK3UWa/bbm1zJv+AfQlAGMrEMygp+ZzocmycUCYOVM0dcIbymjoiI/QRHTng==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -695,13 +690,12 @@
} }
}, },
"node_modules/@typescript/native-preview-linux-x64": { "node_modules/@typescript/native-preview-linux-x64": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-TCZtknsLUgPRaEfX9CvBZNgrHhMRZPYYZgF1Aasdv0PONv9mB8w0Xforgxoo4UFjdF5ZzOu2icgc7sKJJeu5vw==", "integrity": "sha512-6fE8piqPfzPPqmQ37ewTSbm4HW0cNqOEhfLG2F37zJd4525mefhIpWvj2iCkEHWp+BDlF2dYCbB4cY2nmfrNNw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -712,13 +706,12 @@
} }
}, },
"node_modules/@typescript/native-preview-win32-arm64": { "node_modules/@typescript/native-preview-win32-arm64": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-bulwrkLEkoY4Jqeuvfz24RiVOiZZ7Rr9TblFqZAgZFZOnyXuhjM1jE8F1hnJFC5AghJe2HdLD3EKfabqlffrIw==", "integrity": "sha512-ppCkjBAFotPxL8j9Vk5cNSwMreOvAt02AMa5Hko3JQGSVA2TQCIlvTFn+SHSIWzYbzomc9j4j5WOcOR0rmAAHg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -729,13 +722,12 @@
} }
}, },
"node_modules/@typescript/native-preview-win32-x64": { "node_modules/@typescript/native-preview-win32-x64": {
"version": "7.0.0-dev.20250523.1", "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.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-ztzfO0oF/rj8xO5y3SyAcigmgvgczrqobCugEWFqiYumteWZPN2MYWcNYk2k8Y5LAgg1fN1xHIg8RRSPoo6XUg==", "integrity": "sha512-BsnJqso5MKAW4Y7fPmcamJ+EIrWOTqwLjeZP74NNFvTqCsA4RkITCw4NpLwD0lzrv9VsQcQ+bNwB8DrT+oDqoQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -3396,9 +3388,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.69", "version": "0.5.71",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.69.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.71.tgz",
"integrity": "sha512-vTU1tUzqpihzpseUSJMrM82pYbCDZCfW40jXIi+Ol9B3a3Acz0DccfP7i4eoXf7Abahu4H/sjRt/nSHLNBvLHA==" "integrity": "sha512-TCS2wPRsBzuURJlIMDhygAHaLsKVZ7dGuC73WZ/iMyn3gKVwA98nnaIj24D+UceWS08fwq4ilWAfUzHJd6X29A=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -5,8 +5,10 @@
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js", "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": "tsgo --sourceMap && ncp static/webui build/static/webui",
"build:dev": "tsc --incremental --sourceMap", "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": "npm run build && npm run start",
"build-and-start:bun": "npm run verify && npm run bun-run", "build-and-start:bun": "npm run verify && npm run bun-run",
"dev": "node scripts/dev.js", "dev": "node scripts/dev.js",
@ -25,6 +27,7 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10", "@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.4.4",
@ -33,7 +36,7 @@
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"undici": "^7.10.0", "undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.69", "warframe-public-export-plus": "^0.5.71",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
@ -42,7 +45,6 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"eslint": "^8", "eslint": "^8",
"eslint-plugin-prettier": "^5.2.5", "eslint-plugin-prettier": "^5.2.5",

View File

@ -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;
}

View File

@ -22,7 +22,8 @@ import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { Ship } from "@/src/models/shipModel"; 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) => { export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request); const account = await getAccountForRequest(request);
@ -128,13 +129,21 @@ export const getInventoryResponse = async (
xpBasedLevelCapDisabled: boolean, xpBasedLevelCapDisabled: boolean,
buildLabel: string | undefined buildLabel: string | undefined
): Promise<IInventoryClient> => { ): Promise<IInventoryClient> => {
const [inventoryWithLoadOutPresets, ships] = await Promise.all([ const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"), 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>(); const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>()); 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) { if (config.infiniteCredits) {
inventoryResponse.RegularCredits = 999999999; inventoryResponse.RegularCredits = 999999999;
} }

View File

@ -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();
};

View File

@ -12,18 +12,24 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const MissionRewards: IMissionReward[] = []; const MissionRewards: IMissionReward[] = [];
for (const [tag, node] of Object.entries(ExportRegions)) { for (const [tag, node] of Object.entries(ExportRegions)) {
if (!inventory.Missions.find(x => x.Tag == tag)) { let mission = inventory.Missions.find(x => x.Tag == tag);
if (!mission) {
mission =
inventory.Missions[
inventory.Missions.push({ inventory.Missions.push({
Completes: 1, Completes: 0,
Tier: 1, Tier: 0,
Tag: tag Tag: tag
}); }) - 1
];
}
if (mission.Completes == 0) {
mission.Completes++;
if (node.missionReward) { if (node.missionReward) {
console.log(node.missionReward);
addFixedLevelRewards(node.missionReward, inventory, MissionRewards); addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
} }
} }
mission.Tier = 1;
} }
for (const reward of MissionRewards) { for (const reward of MissionRewards) {
await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true); await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);

View File

@ -21,13 +21,14 @@ import mongoose from "mongoose";
import { JSONStringify } from "json-with-bigint"; import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "./services/webService"; import { startWebServer } from "./services/webService";
import { validateConfig } from "@/src/services/configWatcherService"; import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "./services/worldStateService"; import { updateWorldStateCollections } from "./services/worldStateService";
// Patch JSON.stringify to work flawlessly with Bigints. // Patch JSON.stringify to work flawlessly with Bigints.
JSON.stringify = JSONStringify; JSON.stringify = JSONStringify;
validateConfig(); validateConfig();
syncConfigWithDatabase();
mongoose mongoose
.connect(config.mongodbUrl) .connect(config.mongodbUrl)

View File

@ -27,11 +27,12 @@ export interface IMessage {
icon?: string; icon?: string;
highPriority?: boolean; highPriority?: boolean;
lowPrioNewPlayers?: boolean; lowPrioNewPlayers?: boolean;
startDate?: Date; transmission?: string;
endDate?: Date;
att?: string[]; att?: string[];
countedAtt?: ITypeCount[]; countedAtt?: ITypeCount[];
transmission?: string; startDate?: Date;
endDate?: Date;
goalTag?: string;
CrossPlatform?: boolean; CrossPlatform?: boolean;
arg?: Arg[]; arg?: Arg[];
gifts?: IGift[]; gifts?: IGift[];
@ -107,6 +108,7 @@ const messageSchema = new Schema<IMessageDatabase>(
lowPrioNewPlayers: Boolean, lowPrioNewPlayers: Boolean,
startDate: Date, startDate: Date,
endDate: Date, endDate: Date,
goalTag: String,
date: { type: Date, required: true }, date: { type: Date, required: true },
r: Boolean, r: Boolean,
CrossPlatform: Boolean, CrossPlatform: Boolean,

View File

@ -1,4 +1,4 @@
import { Document, HydratedDocument, Model, Schema, Types, model } from "mongoose"; import { Document, Model, Schema, Types, model } from "mongoose";
import { import {
IFlavourItem, IFlavourItem,
IRawUpgrade, IRawUpgrade,
@ -7,7 +7,6 @@ import {
IBooster, IBooster,
IInventoryClient, IInventoryClient,
ISlots, ISlots,
IMailboxDatabase,
IDuviriInfo, IDuviriInfo,
IPendingRecipeDatabase, IPendingRecipeDatabase,
IPendingRecipeClient, IPendingRecipeClient,
@ -54,7 +53,6 @@ import {
IUpgradeDatabase, IUpgradeDatabase,
ICrewShipMemberDatabase, ICrewShipMemberDatabase,
ICrewShipMemberClient, ICrewShipMemberClient,
IMailboxClient,
TEquipmentKey, TEquipmentKey,
equipmentKeys, equipmentKeys,
IKubrowPetDetailsDatabase, IKubrowPetDetailsDatabase,
@ -99,7 +97,9 @@ import {
IAccolades, IAccolades,
IHubNpcCustomization, IHubNpcCustomization,
ILotusCustomization, ILotusCustomization,
IEndlessXpReward IEndlessXpReward,
IPersonalGoalProgressDatabase,
IPersonalGoalProgressClient
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -371,7 +371,7 @@ FlavourItemSchema.set("toJSON", {
} }
}); });
const MailboxSchema = new Schema<IMailboxDatabase>( /*const MailboxSchema = new Schema<IMailboxDatabase>(
{ {
LastInboxId: Schema.Types.ObjectId LastInboxId: Schema.Types.ObjectId
}, },
@ -384,7 +384,7 @@ MailboxSchema.set("toJSON", {
delete mailboxDatabase.__v; delete mailboxDatabase.__v;
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId); (returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
} }
}); });*/
const DuviriInfoSchema = new Schema<IDuviriInfo>( const DuviriInfoSchema = new Schema<IDuviriInfo>(
{ {
@ -457,11 +457,35 @@ const discoveredMarkerSchema = new Schema<IDiscoveredMarker>(
{ _id: false } { _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>( const challengeProgressSchema = new Schema<IChallengeProgress>(
{ {
Progress: Number, Progress: Number,
Name: String, Completed: { type: [String], default: undefined },
Completed: [String] ReceivedJunctionReward: Boolean,
Name: { type: String, required: true }
}, },
{ _id: false } { _id: false }
); );
@ -1630,7 +1654,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//CompletedJobs: [Schema.Types.Mixed], //CompletedJobs: [Schema.Types.Mixed],
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258, //Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
//PersonalGoalProgress: [Schema.Types.Mixed], PersonalGoalProgress: { type: [personalGoalProgressSchema], default: undefined },
//Setting interface Style //Setting interface Style
ThemeStyle: String, ThemeStyle: String,
@ -1701,7 +1725,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Unknown and system //Unknown and system
DuviriInfo: DuviriInfoSchema, DuviriInfo: DuviriInfoSchema,
LastInventorySync: Schema.Types.ObjectId, LastInventorySync: Schema.Types.ObjectId,
Mailbox: MailboxSchema, //Mailbox: MailboxSchema,
HandlerPoints: Number, HandlerPoints: Number,
ChallengesFixVersion: Number, ChallengesFixVersion: Number,
PlayedParkourTutorial: Boolean, PlayedParkourTutorial: Boolean,
@ -1754,7 +1778,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined }, BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
LockedWeaponGroup: { type: lockedWeaponGroupSchema, 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 } } { timestamps: { createdAt: "Created", updatedAt: false } }
); );

View File

@ -40,6 +40,7 @@ const placedDecosSchema = new Schema<IPlacedDecosDatabase>(
Pos: [Number], Pos: [Number],
Rot: [Number], Rot: [Number],
Scale: Number, Scale: Number,
Sockets: Number,
PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined } PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined }
}, },
{ id: false } { id: false }

View File

@ -19,6 +19,7 @@ import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootCo
import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController"; import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController"; import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController"; import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
import { claimJunctionChallengeRewardController } from "@/src/controllers/api/claimJunctionChallengeRewardController";
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController"; import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
@ -237,6 +238,7 @@ apiRouter.post("/artifacts.php", artifactsController);
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController); apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
apiRouter.post("/claimJunctionChallengeReward.php", claimJunctionChallengeRewardController);
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController); apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?) apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)

View File

@ -13,6 +13,7 @@ import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAl
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController"; import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController"; import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController";
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController"; import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController"; import { createMessageController } from "@/src/controllers/custom/createMessageController";
@ -42,6 +43,7 @@ customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController); customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController); customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController); customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController); customRouter.post("/createMessage", createMessageController);

View File

@ -76,6 +76,7 @@ export interface IConfig {
affinityBoost?: boolean; affinityBoost?: boolean;
resourceBoost?: boolean; resourceBoost?: boolean;
starDays?: boolean; starDays?: boolean;
galleonOfGhouls?: number;
eidolonOverride?: string; eidolonOverride?: string;
vallisOverride?: string; vallisOverride?: string;
duviriOverride?: string; duviriOverride?: string;

View File

@ -3,6 +3,7 @@ import fsPromises from "fs/promises";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { config, configPath, loadConfig } from "./configService"; import { config, configPath, loadConfig } from "./configService";
import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService"; import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
import { Inbox } from "../models/inboxModel";
let amnesia = false; let amnesia = false;
fs.watchFile(configPath, (now, then) => { fs.watchFile(configPath, (now, then) => {
@ -22,6 +23,7 @@ fs.watchFile(configPath, (now, then) => {
process.exit(1); process.exit(1);
} }
validateConfig(); validateConfig();
syncConfigWithDatabase();
const webPorts = getWebPorts(); const webPorts = getWebPorts();
if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) { 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) { if (modified) {
logger.info(`Updating config file to fix some issues with it.`); logger.info(`Updating config file to fix some issues with it.`);
void saveConfig(); void saveConfig();
@ -61,3 +72,10 @@ export const saveConfig = async (): Promise<void> => {
amnesia = true; amnesia = true;
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2)); 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.
}
};

View File

@ -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) { if (newEventMessages.length === 0) {
return; return;
} }

View File

@ -46,7 +46,7 @@ import {
import { updateQuestKey } from "@/src/services/questService"; import { updateQuestKey } from "@/src/services/questService";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; 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 { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
@ -609,6 +609,47 @@ export const addMissionInventoryUpdates = async (
inventoryChanges.RegularCredits -= value; inventoryChanges.RegularCredits -= value;
break; 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": { case "InvasionProgress": {
for (const clientProgress of value) { for (const clientProgress of value) {
const dbProgress = inventory.QualifyingInvasions.find(x => const dbProgress = inventory.QualifyingInvasions.find(x =>
@ -962,6 +1003,14 @@ export const addMissionRewards = async (
let missionCompletionCredits = 0; let missionCompletionCredits = 0;
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display //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) { if (levelKeyName) {
const fixedLevelRewards = getLevelKeyRewards(levelKeyName); const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
@ -1978,3 +2027,24 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } =>
} }
return { nodes, buddies }; 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"
}
};

View File

@ -107,6 +107,16 @@ export class SRng {
return arr[this.randomInt(0, arr.length - 1)]; 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 { randomFloat(): number {
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn; this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645; return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;

View File

@ -8,11 +8,12 @@ import {
} from "@/src/types/shipTypes"; } from "@/src/types/shipTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addShipDecorations, getInventory } from "./inventoryService"; import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService";
import { config } from "./configService"; import { config } from "./configService";
import { Guild } from "../models/guildModel"; import { Guild } from "../models/guildModel";
import { hasGuildPermission } from "./guildService"; import { hasGuildPermission } from "./guildService";
import { GuildPermission } from "../types/guildTypes"; import { GuildPermission } from "../types/guildTypes";
import { ExportResources } from "warframe-public-export-plus";
export const setShipCustomizations = async ( export const setShipCustomizations = async (
accountId: string, accountId: string,
@ -101,6 +102,7 @@ export const handleSetShipDecorations = async (
Pos: placedDecoration.Pos, Pos: placedDecoration.Pos,
Rot: placedDecoration.Rot, Rot: placedDecoration.Rot,
Scale: placedDecoration.Scale, Scale: placedDecoration.Scale,
Sockets: placedDecoration.Sockets,
_id: placedDecoration.MoveId _id: placedDecoration.MoveId
}; };
@ -116,12 +118,19 @@ export const handleSetShipDecorations = async (
} }
if (placedDecoration.RemoveId) { 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(); await personalRooms.save();
if (!config.unlockAllShipDecorations) { if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId); 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(); await inventory.save();
} }
@ -134,7 +143,14 @@ export const handleSetShipDecorations = async (
} else { } else {
if (!config.unlockAllShipDecorations) { if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId); 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(); await inventory.save();
} }
} }
@ -148,6 +164,7 @@ export const handleSetShipDecorations = async (
Pos: placedDecoration.Pos, Pos: placedDecoration.Pos,
Rot: placedDecoration.Rot, Rot: placedDecoration.Rot,
Scale: placedDecoration.Scale, Scale: placedDecoration.Scale,
Sockets: placedDecoration.Sockets,
_id: decoId _id: decoId
}); });

View File

@ -101,7 +101,7 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
SORTIE_BOSS_VOR: "SolNode108" SORTIE_BOSS_VOR: "SolNode108"
}; };
const eidolonJobs = [ const eidolonJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss", "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap", "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab", "/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
@ -117,14 +117,14 @@ const eidolonJobs = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc" "/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/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt", "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft", "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib" "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
]; ];
const venusJobs = [ const venusJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush", "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation", "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery", "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
@ -150,14 +150,14 @@ const venusJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy" "/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/NarmerVenusCullJobAssassinate",
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate", "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense", "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation" "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
]; ];
const microplanetJobs = [ const microplanetJobs: readonly string[] = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
@ -167,7 +167,7 @@ const microplanetJobs = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty" "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
]; ];
const microplanetEndlessJobs = [ const microplanetEndlessJobs: readonly string[] = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty" "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
@ -498,6 +498,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
{ {
const rng = new SRng(seed); const rng = new SRng(seed);
const pool = [...eidolonJobs];
syndicateMissions.push({ syndicateMissions.push({
_id: { _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008" $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
@ -509,7 +510,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
@ -517,7 +518,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1000, 1500) xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 10, minEnemyLevel: 10,
@ -525,7 +526,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1750, 2250) xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 20, minEnemyLevel: 20,
@ -533,7 +534,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 4, 2500, 3000) xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 30, minEnemyLevel: 30,
@ -541,7 +542,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 3250, 3750) xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
masteryReq: 5, masteryReq: 5,
minEnemyLevel: 40, minEnemyLevel: 40,
@ -549,7 +550,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 4000, 4500) xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
masteryReq: 10, masteryReq: 10,
minEnemyLevel: 100, minEnemyLevel: 100,
@ -570,6 +571,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
{ {
const rng = new SRng(seed); const rng = new SRng(seed);
const pool = [...venusJobs];
syndicateMissions.push({ syndicateMissions.push({
_id: { _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025" $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
@ -581,7 +583,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
@ -589,7 +591,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1000, 1500) xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 10, minEnemyLevel: 10,
@ -597,7 +599,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1750, 2250) xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 20, minEnemyLevel: 20,
@ -605,7 +607,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 4, 2500, 3000) xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 30, minEnemyLevel: 30,
@ -613,7 +615,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 3250, 3750) xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
masteryReq: 5, masteryReq: 5,
minEnemyLevel: 40, minEnemyLevel: 40,
@ -621,7 +623,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 4000, 4500) xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
masteryReq: 10, masteryReq: 10,
minEnemyLevel: 100, minEnemyLevel: 100,
@ -642,6 +644,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
{ {
const rng = new SRng(seed); const rng = new SRng(seed);
const pool = [...microplanetJobs];
syndicateMissions.push({ syndicateMissions.push({
_id: { _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002" $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
@ -653,7 +656,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
@ -661,7 +664,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 12, 18) xpAmounts: generateXpAmounts(rng, 3, 12, 18)
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 15, minEnemyLevel: 15,
@ -678,7 +681,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: [14, 14, 14] xpAmounts: [14, 14, 14]
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 30, minEnemyLevel: 30,
@ -686,7 +689,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 4, 72, 88) xpAmounts: generateXpAmounts(rng, 4, 72, 88)
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`, rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 40, minEnemyLevel: 40,
@ -694,7 +697,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 115, 135) xpAmounts: generateXpAmounts(rng, 5, 115, 135)
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`, rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
masteryReq: 10, masteryReq: 10,
minEnemyLevel: 100, minEnemyLevel: 100,
@ -1146,6 +1149,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Node: "SolarisUnitedHub1" 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 // Nightwave Challenges
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel); const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);

View File

@ -56,6 +56,7 @@ export interface IInventoryDatabase
| "QualifyingInvasions" | "QualifyingInvasions"
| "LastInventorySync" | "LastInventorySync"
| "EndlessXP" | "EndlessXP"
| "PersonalGoalProgress"
| TEquipmentKey | TEquipmentKey
>, >,
InventoryDatabaseEquipment { InventoryDatabaseEquipment {
@ -63,7 +64,7 @@ export interface IInventoryDatabase
Created: Date; Created: Date;
TrainingDate: Date; TrainingDate: Date;
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
Mailbox?: IMailboxDatabase; //Mailbox?: IMailboxDatabase;
GuildId?: Types.ObjectId; GuildId?: Types.ObjectId;
PendingRecipes: IPendingRecipeDatabase[]; PendingRecipes: IPendingRecipeDatabase[];
QuestKeys: IQuestKeyDatabase[]; QuestKeys: IQuestKeyDatabase[];
@ -95,6 +96,7 @@ export interface IInventoryDatabase
QualifyingInvasions: IInvasionProgressDatabase[]; QualifyingInvasions: IInvasionProgressDatabase[];
LastInventorySync?: Types.ObjectId; LastInventorySync?: Types.ObjectId;
EndlessXP?: IEndlessXpProgressDatabase[]; EndlessXP?: IEndlessXpProgressDatabase[];
PersonalGoalProgress?: IPersonalGoalProgressDatabase[];
} }
export interface IQuestKeyDatabase { export interface IQuestKeyDatabase {
@ -150,9 +152,9 @@ export interface IMailboxClient {
LastInboxId: IOid; LastInboxId: IOid;
} }
export interface IMailboxDatabase { /*export interface IMailboxDatabase {
LastInboxId: Types.ObjectId; LastInboxId: Types.ObjectId;
} }*/
export type TSolarMapRegion = export type TSolarMapRegion =
| "Earth" | "Earth"
@ -306,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
HWIDProtectEnabled?: boolean; HWIDProtectEnabled?: boolean;
//KubrowPetPrints: IKubrowPetPrint[]; //KubrowPetPrints: IKubrowPetPrint[];
AlignmentReplay?: IAlignment; AlignmentReplay?: IAlignment;
//PersonalGoalProgress: IPersonalGoalProgress[]; PersonalGoalProgress?: IPersonalGoalProgressClient[];
ThemeStyle: string; ThemeStyle: string;
ThemeBackground: string; ThemeBackground: string;
ThemeSounds: string; ThemeSounds: string;
@ -378,6 +380,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
LockedWeaponGroup?: ILockedWeaponGroupClient; LockedWeaponGroup?: ILockedWeaponGroupClient;
HubNpcCustomizations?: IHubNpcCustomization[]; HubNpcCustomizations?: IHubNpcCustomization[];
Ship?: IOrbiter; // U22 and below, response only Ship?: IOrbiter; // U22 and below, response only
ClaimedJunctionChallengeRewards?: string[]; // U39
} }
export interface IAffiliation { export interface IAffiliation {
@ -446,8 +449,9 @@ export interface IVendorPurchaseHistoryEntryDatabase {
export interface IChallengeProgress { export interface IChallengeProgress {
Progress: number; Progress: number;
Name: string;
Completed?: string[]; Completed?: string[];
ReceivedJunctionReward?: boolean; // U39
Name: string;
} }
export interface ICollectibleEntry { export interface ICollectibleEntry {
@ -1015,13 +1019,17 @@ export interface IPeriodicMissionCompletionResponse extends Omit<IPeriodicMissio
date: IMongoDate; date: IMongoDate;
} }
export interface IPersonalGoalProgress { export interface IPersonalGoalProgressClient {
Best: number;
Count: number; Count: number;
Tag: string; Tag: string;
Best?: number;
_id: IOid; _id: IOid;
ReceivedClanReward0?: boolean; //ReceivedClanReward0?: boolean;
ReceivedClanReward1?: boolean; //ReceivedClanReward1?: boolean;
}
export interface IPersonalGoalProgressDatabase extends Omit<IPersonalGoalProgressClient, "_id"> {
goalId: Types.ObjectId;
} }
export interface IPersonalTechProjectDatabase { export interface IPersonalTechProjectDatabase {

View File

@ -139,6 +139,14 @@ export type IMissionInventoryUpdateRequest = {
}; };
wagerTier?: number; // the index wagerTier?: number; // the index
creditsFee?: number; // the index creditsFee?: number; // the index
GoalProgress?: {
_id: IOid;
Count: number;
Best: number;
Tag: string;
IsMultiProgress: boolean;
MultiProgress: unknown[];
}[];
InvasionProgress?: IInvasionProgressClient[]; InvasionProgress?: IInvasionProgressClient[];
ConquestMissionsCompleted?: number; ConquestMissionsCompleted?: number;
duviriSuitSelection?: string; duviriSuitSelection?: string;
@ -156,6 +164,8 @@ export type IMissionInventoryUpdateRequest = {
export interface IRewardInfo { export interface IRewardInfo {
node: string; node: string;
goalId?: string;
goalManifest?: string;
invasionId?: string; invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS"; invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
sortieId?: string; sortieId?: string;

View File

@ -96,6 +96,7 @@ export interface IPlacedDecosDatabase {
Pos: [number, number, number]; Pos: [number, number, number];
Rot: [number, number, number]; Rot: [number, number, number];
Scale?: number; Scale?: number;
Sockets?: number;
PictureFrameInfo?: IPictureFrameInfo; PictureFrameInfo?: IPictureFrameInfo;
_id: Types.ObjectId; _id: Types.ObjectId;
} }
@ -136,6 +137,7 @@ export interface IShipDecorationsRequest {
MoveId?: string; MoveId?: string;
OldRoom?: string; OldRoom?: string;
Scale?: number; Scale?: number;
Sockets?: number;
} }
export interface IShipDecorationsResponse { export interface IShipDecorationsResponse {

View File

@ -1,3 +1,4 @@
import { IMissionReward } from "warframe-public-export-plus";
import { IMongoDate, IOid } from "./commonTypes"; import { IMongoDate, IOid } from "./commonTypes";
export interface IWorldState { export interface IWorldState {
@ -37,11 +38,15 @@ export interface IGoal {
Goal: number; Goal: number;
Success: number; Success: number;
Personal: boolean; Personal: boolean;
Bounty?: boolean;
ClampNodeScores?: boolean;
Desc: string; Desc: string;
ToolTip: string; ToolTip?: string;
Icon: string; Icon: string;
Tag: string; Tag: string;
Node: string; Node: string;
MissionKeyName?: string;
Reward?: IMissionReward;
} }
export interface ISyndicateMissionInfo { export interface ISyndicateMissionInfo {

View File

@ -794,6 +794,7 @@
<button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button> <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="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
<button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></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="doIntrinsicsUnlockAll();" data-loc="cheats_intrinsicsUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button> <button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
</div> </div>

View File

@ -1449,6 +1449,11 @@ function addMissingEquipment(categories) {
} }
} }
async function addMissingHelminthRecipes() {
await revalidateAuthz();
await fetch("/custom/addMissingHelminthBlueprints?" + window.authz);
}
function addMissingEvolutionProgress() { function addMissingEvolutionProgress() {
const requests = []; const requests = [];
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => { document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {

View File

@ -181,6 +181,7 @@ dict = {
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
cheats_helminthUnlockAll: `Helminth vollständig aufleveln`, cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`, cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`,
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`, cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`, cheats_changeButton: `Ändern`,

View File

@ -180,6 +180,7 @@ dict = {
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,
cheats_helminthUnlockAll: `Fully Level Up Helminth`, cheats_helminthUnlockAll: `Fully Level Up Helminth`,
cheats_addMissingSubsumedAbilities: `Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`, cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`,
cheats_changeSupportedSyndicate: `Supported syndicate`, cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`, cheats_changeButton: `Change`,

View File

@ -181,6 +181,7 @@ dict = {
cheats_account: `Cuenta`, cheats_account: `Cuenta`,
cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,
cheats_helminthUnlockAll: `Subir al máximo el Helminto`, cheats_helminthUnlockAll: `Subir al máximo el Helminto`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`, cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
cheats_changeSupportedSyndicate: `Sindicatos disponibles`, cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`, cheats_changeButton: `Cambiar`,

View File

@ -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_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_addButton: `Ajouter`,
general_bulkActions: `Action groupée`, general_bulkActions: `Action groupée`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, code_regFail: `Enregistrement impossible. Compte existant?`,
code_changeNameConfirm: `Nouveau nom du compte :`, code_changeNameConfirm: `Nouveau nom du compte :`,
code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`, code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`,
code_archgun: `Archgun`, code_archgun: `Archgun`,
@ -85,7 +85,7 @@ dict = {
inventory_moaPets: `Moas`, inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bêtes`, inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `Progrès de l'évolution Incarnon`, inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
inventory_Boosters: `[UNTRANSLATED] Boosters`, inventory_Boosters: `Boosters`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
@ -100,7 +100,7 @@ dict = {
inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`, inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon 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_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`, quests_completeAll: `Compléter toutes les quêtes`,
@ -135,10 +135,10 @@ dict = {
cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteRegalAya: `Aya Raffiné infini`,
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`, cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`,
cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, cheats_dontSubtractPurchaseCreditCost: `Ne pas retirer le coût en crédits`,
cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, cheats_dontSubtractPurchasePlatinumCost: `Ne pas retirer le coût en platines`,
cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, cheats_dontSubtractPurchaseItemCost: `Ne pas retirer le coût d'achat`,
cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractPurchaseStandingCost: `Ne pas retirer le coût en réputation`,
cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`, cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`,
cheats_dontSubtractConsumables: `Ne pas retirer de consommables`, cheats_dontSubtractConsumables: `Ne pas retirer de consommables`,
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
@ -158,11 +158,11 @@ dict = {
cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`, cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, 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_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, 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_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`, 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_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`,
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `Ascension de clan rapide`, cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
cheats_save: `[UNTRANSLATED] Save`, cheats_save: `Sauvegarder`,
cheats_account: `Compte`, cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
cheats_helminthUnlockAll: `Helminth niveau max`, cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Inhérences niveau max`, cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_changeSupportedSyndicate: `Allégeance`, cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`, cheats_changeButton: `Changer`,
cheats_none: `Aucun`, 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_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_submit: `Soumettre`,
import_samples: `[UNTRANSLATED] Samples:`, import_samples: `Echantillons :`,
import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, 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_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, upgrade_MeleeCritDamage: `+|VAL|% de dégâts critique en mêlée`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, upgrade_PrimaryStatusChance: `+|VAL|% de chance de statut sur arme primaire`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, upgrade_SecondaryCritChance: `+|VAL|% de chance critique sur arme secondaire`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityDuration: `+|VAL|% de durée de pouvoir`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeAbilityStrength: `+|VAL|% de puissance de pouvoir`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, upgrade_WarframeArmourMax: `+|VAL| d'armure`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeBlastProc: `+|VAL| de boucliers sur élimination avec des dégats d'explosion`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCastingSpeed: `+|VAL|% de vitesse de lancement de pouvoir`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut corrosif`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, upgrade_WarframeCorrosiveStack: `+|VAL| de cumuls maximum de Statut Corrosif`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, upgrade_WarframeCritDamageBoost: `+|VAL|% de dégâts critique en mêlée (Doublé si l'énergie dépasse 500)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, upgrade_WarframeElectricDamage: `+|VAL1|% de dégâts électrique sur arme primaire (+|VAL2|% par fragment supplémentaire)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, upgrade_WarframeElectricDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut électrique`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, upgrade_WarframeEnergyMax: `+|VAL| d'énergie max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, upgrade_WarframeHealthMax: `+|VAL| de santé`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, upgrade_WarframeHPBoostFromImpact: `+|VAL1| de santé par ennemi tué avec des dégâts explosifs (Max |VAL2| de santé)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, upgrade_WarframeRegen: `+|VAL| régénération de santé/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, upgrade_WarframeShieldMax: `+|VAL| de boucliers`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, upgrade_WarframeStartingEnergy: `+|VAL|% d'énergie sur apparition`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, upgrade_WarframeToxinDamage: `+|VAL|% de dégâts sur le statut poison`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, upgrade_WarframeToxinHeal: `+|VAL| de santé récupérée à chaque dégât de statut poison`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, upgrade_WeaponCritBoostFromHeat: `+|VAL1|% de chance critique sur arme secondaire pour chaque ennemi affecté puis tué par du feu (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, upgrade_AvatarAbilityRange: `+7.5% de portée de pouvoir`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, upgrade_AvatarAbilityEfficiency: `+5% d'efficacité de pouvoir`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, upgrade_AvatarEnergyRegen: `+0.5 de régénération d'énergie/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, upgrade_AvatarEnemyRadar: `+5m de radar ennemi`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, upgrade_AvatarLootRadar: `+7m de radar à butin`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, upgrade_WeaponAmmoMax: `+15% de munitions max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, upgrade_EnemyArmorReductionAura: `-3% d'armure ennemi`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, 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_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, upgrade_AvatarTimeLimitIncrease: `+8s de temps de piratage`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, upgrade_ElectrifyOnHack: `Electrifie les ennemis dans un rayon de 20m pendant un piratage`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, upgrade_OnExecutionDrainPower: `100% pour le prochain pouvoir de gagner +50% de puissance de pouvoir sur miséricorde`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`,
upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, upgrade_SwiftExecute: `Vitesse des miséricordes augmentée de 50%`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, upgrade_OnHackInvis: `Invisible pendant 15 secondes après un piratage`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -181,6 +181,7 @@ dict = {
cheats_account: `Аккаунт`, cheats_account: `Аккаунт`,
cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,
cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, cheats_helminthUnlockAll: `Полностью улучшить Гельминта`,
cheats_addMissingSubsumedAbilities: `Добавить отсутствующие поглощённые способности`,
cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`, cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`,
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`, cheats_changeButton: `Изменить`,

View File

@ -181,6 +181,7 @@ dict = {
cheats_account: `账户`, cheats_account: `账户`,
cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_unlockAllFocusSchools: `解锁所有专精学派`,
cheats_helminthUnlockAll: `完全升级Helminth`, cheats_helminthUnlockAll: `完全升级Helminth`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `所有内源之力最大等级`, cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`, cheats_changeButton: `更改`,