diff --git a/config.json.example b/config.json.example index d75d619f..11da5729 100644 --- a/config.json.example +++ b/config.json.example @@ -69,6 +69,7 @@ "affinityBoost": false, "resourceBoost": false, "starDays": true, + "galleonOfGhouls": 0, "eidolonOverride": "", "vallisOverride": "", "duviriOverride": "", diff --git a/package-lock.json b/package-lock.json index d3d0207d..f9ab11d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index ce1614ba..ed49ea11 100644 --- a/package.json +++ b/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", diff --git a/src/controllers/api/claimJunctionChallengeRewardController.ts b/src/controllers/api/claimJunctionChallengeRewardController.ts new file mode 100644 index 00000000..849126cb --- /dev/null +++ b/src/controllers/api/claimJunctionChallengeRewardController.ts @@ -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(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; +} diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index ecc32ec3..3880186e 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -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 => { - 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(); inventoryResponse.Ships = ships.map(x => x.toJSON()); + // 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; } diff --git a/src/controllers/custom/addMissingHelminthBlueprintsController.ts b/src/controllers/custom/addMissingHelminthBlueprintsController.ts new file mode 100644 index 00000000..4de501fe --- /dev/null +++ b/src/controllers/custom/addMissingHelminthBlueprintsController.ts @@ -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(); +}; diff --git a/src/controllers/custom/completeAllMissionsController.ts b/src/controllers/custom/completeAllMissionsController.ts index 2e7ac2fb..f6ab486d 100644 --- a/src/controllers/custom/completeAllMissionsController.ts +++ b/src/controllers/custom/completeAllMissionsController.ts @@ -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); diff --git a/src/index.ts b/src/index.ts index 7afd9387..f9d671a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) diff --git a/src/models/inboxModel.ts b/src/models/inboxModel.ts index d339707e..139e8b44 100644 --- a/src/models/inboxModel.ts +++ b/src/models/inboxModel.ts @@ -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( lowPrioNewPlayers: Boolean, startDate: Date, endDate: Date, + goalTag: String, date: { type: Date, required: true }, r: Boolean, CrossPlatform: Boolean, diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 0daeb2c1..cd597bb4 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -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( +/*const MailboxSchema = new Schema( { 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( { @@ -457,11 +457,35 @@ const discoveredMarkerSchema = new Schema( { _id: false } ); +const personalGoalProgressSchema = new Schema( + { + 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( { 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( //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( //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( 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 } } ); diff --git a/src/models/personalRoomsModel.ts b/src/models/personalRoomsModel.ts index 9a19d06b..20f94a9a 100644 --- a/src/models/personalRoomsModel.ts +++ b/src/models/personalRoomsModel.ts @@ -40,6 +40,7 @@ const placedDecosSchema = new Schema( Pos: [Number], Rot: [Number], Scale: Number, + Sockets: Number, PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined } }, { id: false } diff --git a/src/routes/api.ts b/src/routes/api.ts index 63df81d0..2a3255cf 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -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?) diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 35d89d4d..5ed4906e 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -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); diff --git a/src/services/configService.ts b/src/services/configService.ts index 404e4b49..fe74a584 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -76,6 +76,7 @@ export interface IConfig { affinityBoost?: boolean; resourceBoost?: boolean; starDays?: boolean; + galleonOfGhouls?: number; eidolonOverride?: string; vallisOverride?: string; duviriOverride?: string; diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index bb64d5da..8df1acd9 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -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 => { 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. + } +}; diff --git a/src/services/inboxService.ts b/src/services/inboxService.ts index cc5afc29..d623030d 100644 --- a/src/services/inboxService.ts +++ b/src/services/inboxService.ts @@ -54,6 +54,22 @@ export const createNewEventMessages = async (req: Request): Promise => { }); } + // 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; } diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 6ab8b4e9..ec3ee941 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -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 = { + "/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" + } +}; diff --git a/src/services/rngService.ts b/src/services/rngService.ts index 72379ab0..01426a3f 100644 --- a/src/services/rngService.ts +++ b/src/services/rngService.ts @@ -107,6 +107,16 @@ export class SRng { return arr[this.randomInt(0, arr.length - 1)]; } + randomElementPop(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; diff --git a/src/services/shipCustomizationsService.ts b/src/services/shipCustomizationsService.ts index 47764917..a01901fd 100644 --- a/src/services/shipCustomizationsService.ts +++ b/src/services/shipCustomizationsService.ts @@ -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 }); diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 66e07235..0a2bfd4f 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -101,7 +101,7 @@ const sortieBossNode: Record, 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); diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index c3a8a7d1..4c4a9821 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -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 { + goalId: Types.ObjectId; } export interface IPersonalTechProjectDatabase { diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index a6b3f1c1..e2e5da92 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -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; diff --git a/src/types/shipTypes.ts b/src/types/shipTypes.ts index 74eaaeae..6ac738b2 100644 --- a/src/types/shipTypes.ts +++ b/src/types/shipTypes.ts @@ -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 { diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 88544d6c..7301333f 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -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 { diff --git a/static/webui/index.html b/static/webui/index.html index 3d706ee9..caf7be01 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -794,6 +794,7 @@ + diff --git a/static/webui/script.js b/static/webui/script.js index c6c6727b..209d727e 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -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 => { diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 08b02c78..a56103b6 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -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`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index a97ee549..36d38301 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -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`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index a4e798d7..3fbb7bf1 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -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`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 26ead631..a52f5bef 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -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 écraseront celles présentes dans la base de données.`, 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: `` }; diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 5b353690..26fef974 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -181,6 +181,7 @@ dict = { cheats_account: `Аккаунт`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, + cheats_addMissingSubsumedAbilities: `Добавить отсутствующие поглощённые способности`, cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`, cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, cheats_changeButton: `Изменить`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 04463ace..e0ba1ff1 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -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: `更改`,